qemu/hw/block/onenand.c
Eduardo Habkost db1015e92e Move QOM typedefs and add missing includes
Some typedefs and macros are defined after the type check macros.
This makes it difficult to automatically replace their
definitions with OBJECT_DECLARE_TYPE.

Patch generated using:

 $ ./scripts/codeconverter/converter.py -i \
   --pattern=QOMStructTypedefSplit $(git grep -l '' -- '*.[ch]')

which will split "typdef struct { ... } TypedefName"
declarations.

Followed by:

 $ ./scripts/codeconverter/converter.py -i --pattern=MoveSymbols \
    $(git grep -l '' -- '*.[ch]')

which will:
- move the typedefs and #defines above the type check macros
- add missing #include "qom/object.h" lines if necessary

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Juan Quintela <quintela@redhat.com>
Message-Id: <20200831210740.126168-9-ehabkost@redhat.com>
Reviewed-by: Juan Quintela <quintela@redhat.com>
Message-Id: <20200831210740.126168-10-ehabkost@redhat.com>
Message-Id: <20200831210740.126168-11-ehabkost@redhat.com>
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2020-09-09 09:26:43 -04:00

873 lines
26 KiB
C

/*
* OneNAND flash memories emulation.
*
* Copyright (C) 2008 Nokia Corporation
* Written by Andrzej Zaborowski <andrew@openedhand.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/hw.h"
#include "hw/block/flash.h"
#include "hw/irq.h"
#include "hw/qdev-properties.h"
#include "sysemu/block-backend.h"
#include "exec/memory.h"
#include "hw/sysbus.h"
#include "migration/vmstate.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qom/object.h"
/* 11 for 2kB-page OneNAND ("2nd generation") and 10 for 1kB-page chips */
#define PAGE_SHIFT 11
/* Fixed */
#define BLOCK_SHIFT (PAGE_SHIFT + 6)
#define TYPE_ONE_NAND "onenand"
typedef struct OneNANDState OneNANDState;
#define ONE_NAND(obj) OBJECT_CHECK(OneNANDState, (obj), TYPE_ONE_NAND)
struct OneNANDState {
SysBusDevice parent_obj;
struct {
uint16_t man;
uint16_t dev;
uint16_t ver;
} id;
int shift;
hwaddr base;
qemu_irq intr;
qemu_irq rdy;
BlockBackend *blk;
BlockBackend *blk_cur;
uint8_t *image;
uint8_t *otp;
uint8_t *current;
MemoryRegion ram;
MemoryRegion mapped_ram;
uint8_t current_direction;
uint8_t *boot[2];
uint8_t *data[2][2];
MemoryRegion iomem;
MemoryRegion container;
int cycle;
int otpmode;
uint16_t addr[8];
uint16_t unladdr[8];
int bufaddr;
int count;
uint16_t command;
uint16_t config[2];
uint16_t status;
uint16_t intstatus;
uint16_t wpstatus;
ECCState ecc;
int density_mask;
int secs;
int secs_cur;
int blocks;
uint8_t *blockwp;
};
enum {
ONEN_BUF_BLOCK = 0,
ONEN_BUF_BLOCK2 = 1,
ONEN_BUF_DEST_BLOCK = 2,
ONEN_BUF_DEST_PAGE = 3,
ONEN_BUF_PAGE = 7,
};
enum {
ONEN_ERR_CMD = 1 << 10,
ONEN_ERR_ERASE = 1 << 11,
ONEN_ERR_PROG = 1 << 12,
ONEN_ERR_LOAD = 1 << 13,
};
enum {
ONEN_INT_RESET = 1 << 4,
ONEN_INT_ERASE = 1 << 5,
ONEN_INT_PROG = 1 << 6,
ONEN_INT_LOAD = 1 << 7,
ONEN_INT = 1 << 15,
};
enum {
ONEN_LOCK_LOCKTIGHTEN = 1 << 0,
ONEN_LOCK_LOCKED = 1 << 1,
ONEN_LOCK_UNLOCKED = 1 << 2,
};
static void onenand_mem_setup(OneNANDState *s)
{
/* XXX: We should use IO_MEM_ROMD but we broke it earlier...
* Both 0x0000 ... 0x01ff and 0x8000 ... 0x800f can be used to
* write boot commands. Also take note of the BWPS bit. */
memory_region_init(&s->container, OBJECT(s), "onenand",
0x10000 << s->shift);
memory_region_add_subregion(&s->container, 0, &s->iomem);
memory_region_init_alias(&s->mapped_ram, OBJECT(s), "onenand-mapped-ram",
&s->ram, 0x0200 << s->shift,
0xbe00 << s->shift);
memory_region_add_subregion_overlap(&s->container,
0x0200 << s->shift,
&s->mapped_ram,
1);
}
static void onenand_intr_update(OneNANDState *s)
{
qemu_set_irq(s->intr, ((s->intstatus >> 15) ^ (~s->config[0] >> 6)) & 1);
}
static int onenand_pre_save(void *opaque)
{
OneNANDState *s = opaque;
if (s->current == s->otp) {
s->current_direction = 1;
} else if (s->current == s->image) {
s->current_direction = 2;
} else {
s->current_direction = 0;
}
return 0;
}
static int onenand_post_load(void *opaque, int version_id)
{
OneNANDState *s = opaque;
switch (s->current_direction) {
case 0:
break;
case 1:
s->current = s->otp;
break;
case 2:
s->current = s->image;
break;
default:
return -1;
}
onenand_intr_update(s);
return 0;
}
static const VMStateDescription vmstate_onenand = {
.name = "onenand",
.version_id = 1,
.minimum_version_id = 1,
.pre_save = onenand_pre_save,
.post_load = onenand_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT8(current_direction, OneNANDState),
VMSTATE_INT32(cycle, OneNANDState),
VMSTATE_INT32(otpmode, OneNANDState),
VMSTATE_UINT16_ARRAY(addr, OneNANDState, 8),
VMSTATE_UINT16_ARRAY(unladdr, OneNANDState, 8),
VMSTATE_INT32(bufaddr, OneNANDState),
VMSTATE_INT32(count, OneNANDState),
VMSTATE_UINT16(command, OneNANDState),
VMSTATE_UINT16_ARRAY(config, OneNANDState, 2),
VMSTATE_UINT16(status, OneNANDState),
VMSTATE_UINT16(intstatus, OneNANDState),
VMSTATE_UINT16(wpstatus, OneNANDState),
VMSTATE_INT32(secs_cur, OneNANDState),
VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, blocks),
VMSTATE_UINT8(ecc.cp, OneNANDState),
VMSTATE_UINT16_ARRAY(ecc.lp, OneNANDState, 2),
VMSTATE_UINT16(ecc.count, OneNANDState),
VMSTATE_BUFFER_POINTER_UNSAFE(otp, OneNANDState, 0,
((64 + 2) << PAGE_SHIFT)),
VMSTATE_END_OF_LIST()
}
};
/* Hot reset (Reset OneNAND command) or warm reset (RP pin low) */
static void onenand_reset(OneNANDState *s, int cold)
{
memset(&s->addr, 0, sizeof(s->addr));
s->command = 0;
s->count = 1;
s->bufaddr = 0;
s->config[0] = 0x40c0;
s->config[1] = 0x0000;
onenand_intr_update(s);
qemu_irq_raise(s->rdy);
s->status = 0x0000;
s->intstatus = cold ? 0x8080 : 0x8010;
s->unladdr[0] = 0;
s->unladdr[1] = 0;
s->wpstatus = 0x0002;
s->cycle = 0;
s->otpmode = 0;
s->blk_cur = s->blk;
s->current = s->image;
s->secs_cur = s->secs;
if (cold) {
/* Lock the whole flash */
memset(s->blockwp, ONEN_LOCK_LOCKED, s->blocks);
if (s->blk_cur && blk_pread(s->blk_cur, 0, s->boot[0],
8 << BDRV_SECTOR_BITS) < 0) {
hw_error("%s: Loading the BootRAM failed.\n", __func__);
}
}
}
static void onenand_system_reset(DeviceState *dev)
{
OneNANDState *s = ONE_NAND(dev);
onenand_reset(s, 1);
}
static inline int onenand_load_main(OneNANDState *s, int sec, int secn,
void *dest)
{
assert(UINT32_MAX >> BDRV_SECTOR_BITS > sec);
assert(UINT32_MAX >> BDRV_SECTOR_BITS > secn);
if (s->blk_cur) {
return blk_pread(s->blk_cur, sec << BDRV_SECTOR_BITS, dest,
secn << BDRV_SECTOR_BITS) < 0;
} else if (sec + secn > s->secs_cur) {
return 1;
}
memcpy(dest, s->current + (sec << 9), secn << 9);
return 0;
}
static inline int onenand_prog_main(OneNANDState *s, int sec, int secn,
void *src)
{
int result = 0;
if (secn > 0) {
uint32_t size = secn << BDRV_SECTOR_BITS;
uint32_t offset = sec << BDRV_SECTOR_BITS;
assert(UINT32_MAX >> BDRV_SECTOR_BITS > sec);
assert(UINT32_MAX >> BDRV_SECTOR_BITS > secn);
const uint8_t *sp = (const uint8_t *)src;
uint8_t *dp = 0;
if (s->blk_cur) {
dp = g_malloc(size);
if (!dp || blk_pread(s->blk_cur, offset, dp, size) < 0) {
result = 1;
}
} else {
if (sec + secn > s->secs_cur) {
result = 1;
} else {
dp = (uint8_t *)s->current + offset;
}
}
if (!result) {
uint32_t i;
for (i = 0; i < size; i++) {
dp[i] &= sp[i];
}
if (s->blk_cur) {
result = blk_pwrite(s->blk_cur, offset, dp, size, 0) < 0;
}
}
if (dp && s->blk_cur) {
g_free(dp);
}
}
return result;
}
static inline int onenand_load_spare(OneNANDState *s, int sec, int secn,
void *dest)
{
uint8_t buf[512];
if (s->blk_cur) {
uint32_t offset = (s->secs_cur + (sec >> 5)) << BDRV_SECTOR_BITS;
if (blk_pread(s->blk_cur, offset, buf, BDRV_SECTOR_SIZE) < 0) {
return 1;
}
memcpy(dest, buf + ((sec & 31) << 4), secn << 4);
} else if (sec + secn > s->secs_cur) {
return 1;
} else {
memcpy(dest, s->current + (s->secs_cur << 9) + (sec << 4), secn << 4);
}
return 0;
}
static inline int onenand_prog_spare(OneNANDState *s, int sec, int secn,
void *src)
{
int result = 0;
if (secn > 0) {
const uint8_t *sp = (const uint8_t *)src;
uint8_t *dp = 0, *dpp = 0;
uint32_t offset = (s->secs_cur + (sec >> 5)) << BDRV_SECTOR_BITS;
assert(UINT32_MAX >> BDRV_SECTOR_BITS > s->secs_cur + (sec >> 5));
if (s->blk_cur) {
dp = g_malloc(512);
if (!dp
|| blk_pread(s->blk_cur, offset, dp, BDRV_SECTOR_SIZE) < 0) {
result = 1;
} else {
dpp = dp + ((sec & 31) << 4);
}
} else {
if (sec + secn > s->secs_cur) {
result = 1;
} else {
dpp = s->current + (s->secs_cur << 9) + (sec << 4);
}
}
if (!result) {
uint32_t i;
for (i = 0; i < (secn << 4); i++) {
dpp[i] &= sp[i];
}
if (s->blk_cur) {
result = blk_pwrite(s->blk_cur, offset, dp,
BDRV_SECTOR_SIZE, 0) < 0;
}
}
g_free(dp);
}
return result;
}
static inline int onenand_erase(OneNANDState *s, int sec, int num)
{
uint8_t *blankbuf, *tmpbuf;
blankbuf = g_malloc(512);
tmpbuf = g_malloc(512);
memset(blankbuf, 0xff, 512);
for (; num > 0; num--, sec++) {
if (s->blk_cur) {
int erasesec = s->secs_cur + (sec >> 5);
if (blk_pwrite(s->blk_cur, sec << BDRV_SECTOR_BITS, blankbuf,
BDRV_SECTOR_SIZE, 0) < 0) {
goto fail;
}
if (blk_pread(s->blk_cur, erasesec << BDRV_SECTOR_BITS, tmpbuf,
BDRV_SECTOR_SIZE) < 0) {
goto fail;
}
memcpy(tmpbuf + ((sec & 31) << 4), blankbuf, 1 << 4);
if (blk_pwrite(s->blk_cur, erasesec << BDRV_SECTOR_BITS, tmpbuf,
BDRV_SECTOR_SIZE, 0) < 0) {
goto fail;
}
} else {
if (sec + 1 > s->secs_cur) {
goto fail;
}
memcpy(s->current + (sec << 9), blankbuf, 512);
memcpy(s->current + (s->secs_cur << 9) + (sec << 4),
blankbuf, 1 << 4);
}
}
g_free(tmpbuf);
g_free(blankbuf);
return 0;
fail:
g_free(tmpbuf);
g_free(blankbuf);
return 1;
}
static void onenand_command(OneNANDState *s)
{
int b;
int sec;
void *buf;
#define SETADDR(block, page) \
sec = (s->addr[page] & 3) + \
((((s->addr[page] >> 2) & 0x3f) + \
(((s->addr[block] & 0xfff) | \
(s->addr[block] >> 15 ? \
s->density_mask : 0)) << 6)) << (PAGE_SHIFT - 9));
#define SETBUF_M() \
buf = (s->bufaddr & 8) ? \
s->data[(s->bufaddr >> 2) & 1][0] : s->boot[0]; \
buf += (s->bufaddr & 3) << 9;
#define SETBUF_S() \
buf = (s->bufaddr & 8) ? \
s->data[(s->bufaddr >> 2) & 1][1] : s->boot[1]; \
buf += (s->bufaddr & 3) << 4;
switch (s->command) {
case 0x00: /* Load single/multiple sector data unit into buffer */
SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
SETBUF_M()
if (onenand_load_main(s, sec, s->count, buf))
s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
#if 0
SETBUF_S()
if (onenand_load_spare(s, sec, s->count, buf))
s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
#endif
/* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
* or if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
* then we need two split the read/write into two chunks.
*/
s->intstatus |= ONEN_INT | ONEN_INT_LOAD;
break;
case 0x13: /* Load single/multiple spare sector into buffer */
SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
SETBUF_S()
if (onenand_load_spare(s, sec, s->count, buf))
s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
/* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
* or if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
* then we need two split the read/write into two chunks.
*/
s->intstatus |= ONEN_INT | ONEN_INT_LOAD;
break;
case 0x80: /* Program single/multiple sector data unit from buffer */
SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
SETBUF_M()
if (onenand_prog_main(s, sec, s->count, buf))
s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
#if 0
SETBUF_S()
if (onenand_prog_spare(s, sec, s->count, buf))
s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
#endif
/* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
* or if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
* then we need two split the read/write into two chunks.
*/
s->intstatus |= ONEN_INT | ONEN_INT_PROG;
break;
case 0x1a: /* Program single/multiple spare area sector from buffer */
SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
SETBUF_S()
if (onenand_prog_spare(s, sec, s->count, buf))
s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
/* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
* or if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
* then we need two split the read/write into two chunks.
*/
s->intstatus |= ONEN_INT | ONEN_INT_PROG;
break;
case 0x1b: /* Copy-back program */
SETBUF_S()
SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
if (onenand_load_main(s, sec, s->count, buf))
s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
SETADDR(ONEN_BUF_DEST_BLOCK, ONEN_BUF_DEST_PAGE)
if (onenand_prog_main(s, sec, s->count, buf))
s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
/* TODO: spare areas */
s->intstatus |= ONEN_INT | ONEN_INT_PROG;
break;
case 0x23: /* Unlock NAND array block(s) */
s->intstatus |= ONEN_INT;
/* XXX the previous (?) area should be locked automatically */
for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
if (b >= s->blocks) {
s->status |= ONEN_ERR_CMD;
break;
}
if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN)
break;
s->wpstatus = s->blockwp[b] = ONEN_LOCK_UNLOCKED;
}
break;
case 0x27: /* Unlock All NAND array blocks */
s->intstatus |= ONEN_INT;
for (b = 0; b < s->blocks; b ++) {
if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN)
break;
s->wpstatus = s->blockwp[b] = ONEN_LOCK_UNLOCKED;
}
break;
case 0x2a: /* Lock NAND array block(s) */
s->intstatus |= ONEN_INT;
for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
if (b >= s->blocks) {
s->status |= ONEN_ERR_CMD;
break;
}
if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN)
break;
s->wpstatus = s->blockwp[b] = ONEN_LOCK_LOCKED;
}
break;
case 0x2c: /* Lock-tight NAND array block(s) */
s->intstatus |= ONEN_INT;
for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
if (b >= s->blocks) {
s->status |= ONEN_ERR_CMD;
break;
}
if (s->blockwp[b] == ONEN_LOCK_UNLOCKED)
continue;
s->wpstatus = s->blockwp[b] = ONEN_LOCK_LOCKTIGHTEN;
}
break;
case 0x71: /* Erase-Verify-Read */
s->intstatus |= ONEN_INT;
break;
case 0x95: /* Multi-block erase */
qemu_irq_pulse(s->intr);
/* Fall through. */
case 0x94: /* Block erase */
sec = ((s->addr[ONEN_BUF_BLOCK] & 0xfff) |
(s->addr[ONEN_BUF_BLOCK] >> 15 ? s->density_mask : 0))
<< (BLOCK_SHIFT - 9);
if (onenand_erase(s, sec, 1 << (BLOCK_SHIFT - 9)))
s->status |= ONEN_ERR_CMD | ONEN_ERR_ERASE;
s->intstatus |= ONEN_INT | ONEN_INT_ERASE;
break;
case 0xb0: /* Erase suspend */
break;
case 0x30: /* Erase resume */
s->intstatus |= ONEN_INT | ONEN_INT_ERASE;
break;
case 0xf0: /* Reset NAND Flash core */
onenand_reset(s, 0);
break;
case 0xf3: /* Reset OneNAND */
onenand_reset(s, 0);
break;
case 0x65: /* OTP Access */
s->intstatus |= ONEN_INT;
s->blk_cur = NULL;
s->current = s->otp;
s->secs_cur = 1 << (BLOCK_SHIFT - 9);
s->addr[ONEN_BUF_BLOCK] = 0;
s->otpmode = 1;
break;
default:
s->status |= ONEN_ERR_CMD;
s->intstatus |= ONEN_INT;
qemu_log_mask(LOG_GUEST_ERROR, "unknown OneNAND command %x\n",
s->command);
}
onenand_intr_update(s);
}
static uint64_t onenand_read(void *opaque, hwaddr addr,
unsigned size)
{
OneNANDState *s = (OneNANDState *) opaque;
int offset = addr >> s->shift;
switch (offset) {
case 0x0000 ... 0xbffe:
return lduw_le_p(s->boot[0] + addr);
case 0xf000: /* Manufacturer ID */
return s->id.man;
case 0xf001: /* Device ID */
return s->id.dev;
case 0xf002: /* Version ID */
return s->id.ver;
/* TODO: get the following values from a real chip! */
case 0xf003: /* Data Buffer size */
return 1 << PAGE_SHIFT;
case 0xf004: /* Boot Buffer size */
return 0x200;
case 0xf005: /* Amount of buffers */
return 1 | (2 << 8);
case 0xf006: /* Technology */
return 0;
case 0xf100 ... 0xf107: /* Start addresses */
return s->addr[offset - 0xf100];
case 0xf200: /* Start buffer */
return (s->bufaddr << 8) | ((s->count - 1) & (1 << (PAGE_SHIFT - 10)));
case 0xf220: /* Command */
return s->command;
case 0xf221: /* System Configuration 1 */
return s->config[0] & 0xffe0;
case 0xf222: /* System Configuration 2 */
return s->config[1];
case 0xf240: /* Controller Status */
return s->status;
case 0xf241: /* Interrupt */
return s->intstatus;
case 0xf24c: /* Unlock Start Block Address */
return s->unladdr[0];
case 0xf24d: /* Unlock End Block Address */
return s->unladdr[1];
case 0xf24e: /* Write Protection Status */
return s->wpstatus;
case 0xff00: /* ECC Status */
return 0x00;
case 0xff01: /* ECC Result of main area data */
case 0xff02: /* ECC Result of spare area data */
case 0xff03: /* ECC Result of main area data */
case 0xff04: /* ECC Result of spare area data */
qemu_log_mask(LOG_UNIMP,
"onenand: ECC result registers unimplemented\n");
return 0x0000;
}
qemu_log_mask(LOG_GUEST_ERROR, "read of unknown OneNAND register 0x%x\n",
offset);
return 0;
}
static void onenand_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
OneNANDState *s = (OneNANDState *) opaque;
int offset = addr >> s->shift;
int sec;
switch (offset) {
case 0x0000 ... 0x01ff:
case 0x8000 ... 0x800f:
if (s->cycle) {
s->cycle = 0;
if (value == 0x0000) {
SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
onenand_load_main(s, sec,
1 << (PAGE_SHIFT - 9), s->data[0][0]);
s->addr[ONEN_BUF_PAGE] += 4;
s->addr[ONEN_BUF_PAGE] &= 0xff;
}
break;
}
switch (value) {
case 0x00f0: /* Reset OneNAND */
onenand_reset(s, 0);
break;
case 0x00e0: /* Load Data into Buffer */
s->cycle = 1;
break;
case 0x0090: /* Read Identification Data */
memset(s->boot[0], 0, 3 << s->shift);
s->boot[0][0 << s->shift] = s->id.man & 0xff;
s->boot[0][1 << s->shift] = s->id.dev & 0xff;
s->boot[0][2 << s->shift] = s->wpstatus & 0xff;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"unknown OneNAND boot command %" PRIx64 "\n",
value);
}
break;
case 0xf100 ... 0xf107: /* Start addresses */
s->addr[offset - 0xf100] = value;
break;
case 0xf200: /* Start buffer */
s->bufaddr = (value >> 8) & 0xf;
if (PAGE_SHIFT == 11)
s->count = (value & 3) ?: 4;
else if (PAGE_SHIFT == 10)
s->count = (value & 1) ?: 2;
break;
case 0xf220: /* Command */
if (s->intstatus & (1 << 15))
break;
s->command = value;
onenand_command(s);
break;
case 0xf221: /* System Configuration 1 */
s->config[0] = value;
onenand_intr_update(s);
qemu_set_irq(s->rdy, (s->config[0] >> 7) & 1);
break;
case 0xf222: /* System Configuration 2 */
s->config[1] = value;
break;
case 0xf241: /* Interrupt */
s->intstatus &= value;
if ((1 << 15) & ~s->intstatus)
s->status &= ~(ONEN_ERR_CMD | ONEN_ERR_ERASE |
ONEN_ERR_PROG | ONEN_ERR_LOAD);
onenand_intr_update(s);
break;
case 0xf24c: /* Unlock Start Block Address */
s->unladdr[0] = value & (s->blocks - 1);
/* For some reason we have to set the end address to by default
* be same as start because the software forgets to write anything
* in there. */
s->unladdr[1] = value & (s->blocks - 1);
break;
case 0xf24d: /* Unlock End Block Address */
s->unladdr[1] = value & (s->blocks - 1);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"write to unknown OneNAND register 0x%x\n",
offset);
}
}
static const MemoryRegionOps onenand_ops = {
.read = onenand_read,
.write = onenand_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void onenand_realize(DeviceState *dev, Error **errp)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
OneNANDState *s = ONE_NAND(dev);
uint32_t size = 1 << (24 + ((s->id.dev >> 4) & 7));
void *ram;
Error *local_err = NULL;
s->base = (hwaddr)-1;
s->rdy = NULL;
s->blocks = size >> BLOCK_SHIFT;
s->secs = size >> 9;
s->blockwp = g_malloc(s->blocks);
s->density_mask = (s->id.dev & 0x08)
? (1 << (6 + ((s->id.dev >> 4) & 7))) : 0;
memory_region_init_io(&s->iomem, OBJECT(s), &onenand_ops, s, "onenand",
0x10000 << s->shift);
if (!s->blk) {
s->image = memset(g_malloc(size + (size >> 5)),
0xff, size + (size >> 5));
} else {
if (blk_is_read_only(s->blk)) {
error_setg(errp, "Can't use a read-only drive");
return;
}
blk_set_perm(s->blk, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE,
BLK_PERM_ALL, &local_err);
if (local_err) {
error_propagate(errp, local_err);
return;
}
s->blk_cur = s->blk;
}
s->otp = memset(g_malloc((64 + 2) << PAGE_SHIFT),
0xff, (64 + 2) << PAGE_SHIFT);
memory_region_init_ram_nomigrate(&s->ram, OBJECT(s), "onenand.ram",
0xc000 << s->shift, &error_fatal);
vmstate_register_ram_global(&s->ram);
ram = memory_region_get_ram_ptr(&s->ram);
s->boot[0] = ram + (0x0000 << s->shift);
s->boot[1] = ram + (0x8000 << s->shift);
s->data[0][0] = ram + ((0x0200 + (0 << (PAGE_SHIFT - 1))) << s->shift);
s->data[0][1] = ram + ((0x8010 + (0 << (PAGE_SHIFT - 6))) << s->shift);
s->data[1][0] = ram + ((0x0200 + (1 << (PAGE_SHIFT - 1))) << s->shift);
s->data[1][1] = ram + ((0x8010 + (1 << (PAGE_SHIFT - 6))) << s->shift);
onenand_mem_setup(s);
sysbus_init_irq(sbd, &s->intr);
sysbus_init_mmio(sbd, &s->container);
vmstate_register(VMSTATE_IF(dev),
((s->shift & 0x7f) << 24)
| ((s->id.man & 0xff) << 16)
| ((s->id.dev & 0xff) << 8)
| (s->id.ver & 0xff),
&vmstate_onenand, s);
}
static Property onenand_properties[] = {
DEFINE_PROP_UINT16("manufacturer_id", OneNANDState, id.man, 0),
DEFINE_PROP_UINT16("device_id", OneNANDState, id.dev, 0),
DEFINE_PROP_UINT16("version_id", OneNANDState, id.ver, 0),
DEFINE_PROP_INT32("shift", OneNANDState, shift, 0),
DEFINE_PROP_DRIVE("drive", OneNANDState, blk),
DEFINE_PROP_END_OF_LIST(),
};
static void onenand_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = onenand_realize;
dc->reset = onenand_system_reset;
device_class_set_props(dc, onenand_properties);
}
static const TypeInfo onenand_info = {
.name = TYPE_ONE_NAND,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(OneNANDState),
.class_init = onenand_class_init,
};
static void onenand_register_types(void)
{
type_register_static(&onenand_info);
}
void *onenand_raw_otp(DeviceState *onenand_device)
{
OneNANDState *s = ONE_NAND(onenand_device);
return s->otp;
}
type_init(onenand_register_types)