diff options
author | Paolo Bonzini <pbonzini@redhat.com> | 2013-02-05 12:55:04 +0100 |
---|---|---|
committer | Paolo Bonzini <pbonzini@redhat.com> | 2013-04-08 18:13:13 +0200 |
commit | 7b2478956a1aece1c79ece8dec250ed91c09903b (patch) | |
tree | d77b056b02073b9fab5e84ce908e107df0ab54c5 /hw/block | |
parent | d7e35d4a8495bfb3aa0dfd6319fcc499f43a175c (diff) |
hw: move block devices to hw/block/, configure via default-configs/
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'hw/block')
-rw-r--r-- | hw/block/Makefile.objs | 4 | ||||
-rw-r--r-- | hw/block/onenand.c | 842 | ||||
-rw-r--r-- | hw/block/pc_sysfw.c | 273 | ||||
-rw-r--r-- | hw/block/tc58128.c | 178 |
4 files changed, 1297 insertions, 0 deletions
diff --git a/hw/block/Makefile.objs b/hw/block/Makefile.objs index 856915eb6a..e4329a08a9 100644 --- a/hw/block/Makefile.objs +++ b/hw/block/Makefile.objs @@ -6,6 +6,10 @@ common-obj-$(CONFIG_PFLASH_CFI01) += pflash_cfi01.o common-obj-$(CONFIG_PFLASH_CFI02) += pflash_cfi02.o common-obj-$(CONFIG_XEN_BACKEND) += xen_disk.o common-obj-$(CONFIG_ECC) += ecc.o +common-obj-$(CONFIG_ONENAND) += onenand.o +common-obj-$(CONFIG_PC_SYSFW) += pc_sysfw.o + +obj-$(CONFIG_SH4) += tc58128.o obj-$(CONFIG_VIRTIO) += virtio-blk.o obj-$(CONFIG_VIRTIO_BLK_DATA_PLANE) += dataplane/ diff --git a/hw/block/onenand.c b/hw/block/onenand.c new file mode 100644 index 0000000000..8b511a79dc --- /dev/null +++ b/hw/block/onenand.c @@ -0,0 +1,842 @@ +/* + * 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-common.h" +#include "hw/hw.h" +#include "hw/block/flash.h" +#include "hw/irq.h" +#include "sysemu/blockdev.h" +#include "exec/memory.h" +#include "exec/address-spaces.h" +#include "hw/sysbus.h" +#include "qemu/error-report.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) + +typedef struct { + SysBusDevice busdev; + struct { + uint16_t man; + uint16_t dev; + uint16_t ver; + } id; + int shift; + hwaddr base; + qemu_irq intr; + qemu_irq rdy; + BlockDriverState *bdrv; + BlockDriverState *bdrv_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; +} OneNANDState; + +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, "onenand", 0x10000 << s->shift); + memory_region_add_subregion(&s->container, 0, &s->iomem); + memory_region_init_alias(&s->mapped_ram, "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 void 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; + } +} + +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, + .minimum_version_id_old = 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->bdrv_cur = s->bdrv; + 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->bdrv_cur && bdrv_read(s->bdrv_cur, 0, s->boot[0], 8) < 0) { + hw_error("%s: Loading the BootRAM failed.\n", __func__); + } + } +} + +static void onenand_system_reset(DeviceState *dev) +{ + onenand_reset(FROM_SYSBUS(OneNANDState, SYS_BUS_DEVICE(dev)), 1); +} + +static inline int onenand_load_main(OneNANDState *s, int sec, int secn, + void *dest) +{ + if (s->bdrv_cur) + return bdrv_read(s->bdrv_cur, sec, dest, secn) < 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 = (uint32_t)secn * 512; + const uint8_t *sp = (const uint8_t *)src; + uint8_t *dp = 0; + if (s->bdrv_cur) { + dp = g_malloc(size); + if (!dp || bdrv_read(s->bdrv_cur, sec, dp, secn) < 0) { + result = 1; + } + } else { + if (sec + secn > s->secs_cur) { + result = 1; + } else { + dp = (uint8_t *)s->current + (sec << 9); + } + } + if (!result) { + uint32_t i; + for (i = 0; i < size; i++) { + dp[i] &= sp[i]; + } + if (s->bdrv_cur) { + result = bdrv_write(s->bdrv_cur, sec, dp, secn) < 0; + } + } + if (dp && s->bdrv_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->bdrv_cur) { + if (bdrv_read(s->bdrv_cur, s->secs_cur + (sec >> 5), buf, 1) < 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; + if (s->bdrv_cur) { + dp = g_malloc(512); + if (!dp || bdrv_read(s->bdrv_cur, + s->secs_cur + (sec >> 5), + dp, 1) < 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->bdrv_cur) { + result = bdrv_write(s->bdrv_cur, s->secs_cur + (sec >> 5), + dp, 1) < 0; + } + } + if (dp) { + g_free(dp); + } + } + return result; +} + +static inline int onenand_erase(OneNANDState *s, int sec, int num) +{ + uint8_t *blankbuf, *tmpbuf; + blankbuf = g_malloc(512); + if (!blankbuf) { + return 1; + } + tmpbuf = g_malloc(512); + if (!tmpbuf) { + g_free(blankbuf); + return 1; + } + memset(blankbuf, 0xff, 512); + for (; num > 0; num--, sec++) { + if (s->bdrv_cur) { + int erasesec = s->secs_cur + (sec >> 5); + if (bdrv_write(s->bdrv_cur, sec, blankbuf, 1) < 0) { + goto fail; + } + if (bdrv_read(s->bdrv_cur, erasesec, tmpbuf, 1) < 0) { + goto fail; + } + memcpy(tmpbuf + ((sec & 31) << 4), blankbuf, 1 << 4); + if (bdrv_write(s->bdrv_cur, erasesec, tmpbuf, 1) < 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 (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 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->bdrv_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; + fprintf(stderr, "%s: unknown OneNAND command %x\n", + __func__, 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 ... 0xc000: + 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 */ + hw_error("%s: imeplement ECC\n", __FUNCTION__); + return 0x0000; + } + + fprintf(stderr, "%s: unknown OneNAND register %x\n", + __FUNCTION__, 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: + fprintf(stderr, "%s: unknown OneNAND boot command %"PRIx64"\n", + __FUNCTION__, 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: + fprintf(stderr, "%s: unknown OneNAND register %x\n", + __FUNCTION__, offset); + } +} + +static const MemoryRegionOps onenand_ops = { + .read = onenand_read, + .write = onenand_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int onenand_initfn(SysBusDevice *dev) +{ + OneNANDState *s = (OneNANDState *)dev; + uint32_t size = 1 << (24 + ((s->id.dev >> 4) & 7)); + void *ram; + 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, &onenand_ops, s, "onenand", + 0x10000 << s->shift); + if (!s->bdrv) { + s->image = memset(g_malloc(size + (size >> 5)), + 0xff, size + (size >> 5)); + } else { + if (bdrv_is_read_only(s->bdrv)) { + error_report("Can't use a read-only drive"); + return -1; + } + s->bdrv_cur = s->bdrv; + } + s->otp = memset(g_malloc((64 + 2) << PAGE_SHIFT), + 0xff, (64 + 2) << PAGE_SHIFT); + memory_region_init_ram(&s->ram, "onenand.ram", 0xc000 << s->shift); + 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(dev, &s->intr); + sysbus_init_mmio(dev, &s->container); + vmstate_register(&dev->qdev, + ((s->shift & 0x7f) << 24) + | ((s->id.man & 0xff) << 16) + | ((s->id.dev & 0xff) << 8) + | (s->id.ver & 0xff), + &vmstate_onenand, s); + return 0; +} + +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, bdrv), + DEFINE_PROP_END_OF_LIST(), +}; + +static void onenand_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = onenand_initfn; + dc->reset = onenand_system_reset; + dc->props = onenand_properties; +} + +static const TypeInfo onenand_info = { + .name = "onenand", + .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) +{ + return FROM_SYSBUS(OneNANDState, SYS_BUS_DEVICE(onenand_device))->otp; +} + +type_init(onenand_register_types) diff --git a/hw/block/pc_sysfw.c b/hw/block/pc_sysfw.c new file mode 100644 index 0000000000..0d95c8a926 --- /dev/null +++ b/hw/block/pc_sysfw.c @@ -0,0 +1,273 @@ +/* + * QEMU PC System Firmware + * + * Copyright (c) 2003-2004 Fabrice Bellard + * Copyright (c) 2011-2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "sysemu/blockdev.h" +#include "qemu/error-report.h" +#include "hw/sysbus.h" +#include "hw/hw.h" +#include "hw/i386/pc.h" +#include "hw/boards.h" +#include "hw/loader.h" +#include "sysemu/sysemu.h" +#include "hw/block/flash.h" +#include "sysemu/kvm.h" + +#define BIOS_FILENAME "bios.bin" + +typedef struct PcSysFwDevice { + SysBusDevice busdev; + uint8_t rom_only; +} PcSysFwDevice; + +static void pc_isa_bios_init(MemoryRegion *rom_memory, + MemoryRegion *flash_mem, + int ram_size) +{ + int isa_bios_size; + MemoryRegion *isa_bios; + uint64_t flash_size; + void *flash_ptr, *isa_bios_ptr; + + flash_size = memory_region_size(flash_mem); + + /* map the last 128KB of the BIOS in ISA space */ + isa_bios_size = flash_size; + if (isa_bios_size > (128 * 1024)) { + isa_bios_size = 128 * 1024; + } + isa_bios = g_malloc(sizeof(*isa_bios)); + memory_region_init_ram(isa_bios, "isa-bios", isa_bios_size); + vmstate_register_ram_global(isa_bios); + memory_region_add_subregion_overlap(rom_memory, + 0x100000 - isa_bios_size, + isa_bios, + 1); + + /* copy ISA rom image from top of flash memory */ + flash_ptr = memory_region_get_ram_ptr(flash_mem); + isa_bios_ptr = memory_region_get_ram_ptr(isa_bios); + memcpy(isa_bios_ptr, + ((uint8_t*)flash_ptr) + (flash_size - isa_bios_size), + isa_bios_size); + + memory_region_set_readonly(isa_bios, true); +} + +static void pc_fw_add_pflash_drv(void) +{ + QemuOpts *opts; + QEMUMachine *machine; + char *filename; + + if (bios_name == NULL) { + bios_name = BIOS_FILENAME; + } + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (!filename) { + error_report("Can't open BIOS image %s", bios_name); + exit(1); + } + + opts = drive_add(IF_PFLASH, -1, filename, "readonly=on"); + + g_free(filename); + + if (opts == NULL) { + return; + } + + machine = find_default_machine(); + if (machine == NULL) { + return; + } + + if (!drive_init(opts, machine->block_default_type)) { + qemu_opts_del(opts); + } +} + +static void pc_system_flash_init(MemoryRegion *rom_memory, + DriveInfo *pflash_drv) +{ + BlockDriverState *bdrv; + int64_t size; + hwaddr phys_addr; + int sector_bits, sector_size; + pflash_t *system_flash; + MemoryRegion *flash_mem; + + bdrv = pflash_drv->bdrv; + size = bdrv_getlength(pflash_drv->bdrv); + sector_bits = 12; + sector_size = 1 << sector_bits; + + if ((size % sector_size) != 0) { + fprintf(stderr, + "qemu: PC system firmware (pflash) must be a multiple of 0x%x\n", + sector_size); + exit(1); + } + + phys_addr = 0x100000000ULL - size; + system_flash = pflash_cfi01_register(phys_addr, NULL, "system.flash", size, + bdrv, sector_size, size >> sector_bits, + 1, 0x0000, 0x0000, 0x0000, 0x0000, 0); + flash_mem = pflash_cfi01_get_memory(system_flash); + + pc_isa_bios_init(rom_memory, flash_mem, size); +} + +static void old_pc_system_rom_init(MemoryRegion *rom_memory) +{ + char *filename; + MemoryRegion *bios, *isa_bios; + int bios_size, isa_bios_size; + int ret; + + /* BIOS load */ + if (bios_name == NULL) { + bios_name = BIOS_FILENAME; + } + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (filename) { + bios_size = get_image_size(filename); + } else { + bios_size = -1; + } + if (bios_size <= 0 || + (bios_size % 65536) != 0) { + goto bios_error; + } + bios = g_malloc(sizeof(*bios)); + memory_region_init_ram(bios, "pc.bios", bios_size); + vmstate_register_ram_global(bios); + memory_region_set_readonly(bios, true); + ret = rom_add_file_fixed(bios_name, (uint32_t)(-bios_size), -1); + if (ret != 0) { + bios_error: + fprintf(stderr, "qemu: could not load PC BIOS '%s'\n", bios_name); + exit(1); + } + if (filename) { + g_free(filename); + } + + /* map the last 128KB of the BIOS in ISA space */ + isa_bios_size = bios_size; + if (isa_bios_size > (128 * 1024)) { + isa_bios_size = 128 * 1024; + } + isa_bios = g_malloc(sizeof(*isa_bios)); + memory_region_init_alias(isa_bios, "isa-bios", bios, + bios_size - isa_bios_size, isa_bios_size); + memory_region_add_subregion_overlap(rom_memory, + 0x100000 - isa_bios_size, + isa_bios, + 1); + memory_region_set_readonly(isa_bios, true); + + /* map all the bios at the top of memory */ + memory_region_add_subregion(rom_memory, + (uint32_t)(-bios_size), + bios); +} + +void pc_system_firmware_init(MemoryRegion *rom_memory) +{ + DriveInfo *pflash_drv; + PcSysFwDevice *sysfw_dev; + + sysfw_dev = (PcSysFwDevice*) qdev_create(NULL, "pc-sysfw"); + + qdev_init_nofail(DEVICE(sysfw_dev)); + + if (sysfw_dev->rom_only) { + old_pc_system_rom_init(rom_memory); + return; + } + + pflash_drv = drive_get(IF_PFLASH, 0, 0); + + /* Currently KVM cannot execute from device memory. + Use old rom based firmware initialization for KVM. */ + if (kvm_enabled()) { + if (pflash_drv != NULL) { + fprintf(stderr, "qemu: pflash cannot be used with kvm enabled\n"); + exit(1); + } else { + sysfw_dev->rom_only = 1; + old_pc_system_rom_init(rom_memory); + return; + } + } + + /* If a pflash drive is not found, then create one using + the bios filename. */ + if (pflash_drv == NULL) { + pc_fw_add_pflash_drv(); + pflash_drv = drive_get(IF_PFLASH, 0, 0); + } + + if (pflash_drv != NULL) { + pc_system_flash_init(rom_memory, pflash_drv); + } else { + fprintf(stderr, "qemu: PC system firmware (pflash) not available\n"); + exit(1); + } +} + +static Property pcsysfw_properties[] = { + DEFINE_PROP_UINT8("rom_only", PcSysFwDevice, rom_only, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static int pcsysfw_init(DeviceState *dev) +{ + return 0; +} + +static void pcsysfw_class_init (ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS (klass); + + dc->desc = "PC System Firmware"; + dc->init = pcsysfw_init; + dc->props = pcsysfw_properties; +} + +static const TypeInfo pcsysfw_info = { + .name = "pc-sysfw", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof (PcSysFwDevice), + .class_init = pcsysfw_class_init, +}; + +static void pcsysfw_register (void) +{ + type_register_static (&pcsysfw_info); +} + +type_init (pcsysfw_register); + diff --git a/hw/block/tc58128.c b/hw/block/tc58128.c new file mode 100644 index 0000000000..a3929d444f --- /dev/null +++ b/hw/block/tc58128.c @@ -0,0 +1,178 @@ +#include "hw/hw.h" +#include "hw/sh4/sh.h" +#include "hw/loader.h" + +#define CE1 0x0100 +#define CE2 0x0200 +#define RE 0x0400 +#define WE 0x0800 +#define ALE 0x1000 +#define CLE 0x2000 +#define RDY1 0x4000 +#define RDY2 0x8000 +#define RDY(n) ((n) == 0 ? RDY1 : RDY2) + +typedef enum { WAIT, READ1, READ2, READ3 } state_t; + +typedef struct { + uint8_t *flash_contents; + state_t state; + uint32_t address; + uint8_t address_cycle; +} tc58128_dev; + +static tc58128_dev tc58128_devs[2]; + +#define FLASH_SIZE (16*1024*1024) + +static void init_dev(tc58128_dev * dev, const char *filename) +{ + int ret, blocks; + + dev->state = WAIT; + dev->flash_contents = g_malloc(FLASH_SIZE); + memset(dev->flash_contents, 0xff, FLASH_SIZE); + if (filename) { + /* Load flash image skipping the first block */ + ret = load_image(filename, dev->flash_contents + 528 * 32); + if (ret < 0) { + fprintf(stderr, "ret=%d\n", ret); + fprintf(stderr, "qemu: could not load flash image %s\n", + filename); + exit(1); + } else { + /* Build first block with number of blocks */ + blocks = (ret + 528 * 32 - 1) / (528 * 32); + dev->flash_contents[0] = blocks & 0xff; + dev->flash_contents[1] = (blocks >> 8) & 0xff; + dev->flash_contents[2] = (blocks >> 16) & 0xff; + dev->flash_contents[3] = (blocks >> 24) & 0xff; + fprintf(stderr, "loaded %d bytes for %s into flash\n", ret, + filename); + } + } +} + +static void handle_command(tc58128_dev * dev, uint8_t command) +{ + switch (command) { + case 0xff: + fprintf(stderr, "reset flash device\n"); + dev->state = WAIT; + break; + case 0x00: + fprintf(stderr, "read mode 1\n"); + dev->state = READ1; + dev->address_cycle = 0; + break; + case 0x01: + fprintf(stderr, "read mode 2\n"); + dev->state = READ2; + dev->address_cycle = 0; + break; + case 0x50: + fprintf(stderr, "read mode 3\n"); + dev->state = READ3; + dev->address_cycle = 0; + break; + default: + fprintf(stderr, "unknown flash command 0x%02x\n", command); + abort(); + } +} + +static void handle_address(tc58128_dev * dev, uint8_t data) +{ + switch (dev->state) { + case READ1: + case READ2: + case READ3: + switch (dev->address_cycle) { + case 0: + dev->address = data; + if (dev->state == READ2) + dev->address |= 0x100; + else if (dev->state == READ3) + dev->address |= 0x200; + break; + case 1: + dev->address += data * 528 * 0x100; + break; + case 2: + dev->address += data * 528; + fprintf(stderr, "address pointer in flash: 0x%08x\n", + dev->address); + break; + default: + /* Invalid data */ + abort(); + } + dev->address_cycle++; + break; + default: + abort(); + } +} + +static uint8_t handle_read(tc58128_dev * dev) +{ +#if 0 + if (dev->address % 0x100000 == 0) + fprintf(stderr, "reading flash at address 0x%08x\n", dev->address); +#endif + return dev->flash_contents[dev->address++]; +} + +/* We never mark the device as busy, so interrupts cannot be triggered + XXXXX */ + +static int tc58128_cb(uint16_t porta, uint16_t portb, + uint16_t * periph_pdtra, uint16_t * periph_portadir, + uint16_t * periph_pdtrb, uint16_t * periph_portbdir) +{ + int dev; + + if ((porta & CE1) == 0) + dev = 0; + else if ((porta & CE2) == 0) + dev = 1; + else + return 0; /* No device selected */ + + if ((porta & RE) && (porta & WE)) { + /* Nothing to do, assert ready and return to input state */ + *periph_portadir &= 0xff00; + *periph_portadir |= RDY(dev); + *periph_pdtra |= RDY(dev); + return 1; + } + + if (porta & CLE) { + /* Command */ + assert((porta & WE) == 0); + handle_command(&tc58128_devs[dev], porta & 0x00ff); + } else if (porta & ALE) { + assert((porta & WE) == 0); + handle_address(&tc58128_devs[dev], porta & 0x00ff); + } else if ((porta & RE) == 0) { + *periph_portadir |= 0x00ff; + *periph_pdtra &= 0xff00; + *periph_pdtra |= handle_read(&tc58128_devs[dev]); + } else { + abort(); + } + return 1; +} + +static sh7750_io_device tc58128 = { + RE | WE, /* Port A triggers */ + 0, /* Port B triggers */ + tc58128_cb /* Callback */ +}; + +int tc58128_init(struct SH7750State *s, const char *zone1, const char *zone2) +{ + init_dev(&tc58128_devs[0], zone1); + init_dev(&tc58128_devs[1], zone2); + return sh7750_register_io_device(s, &tc58128); +} |