From 4c0e167c9dd354fa161b3e8d315077c34ecaf33a Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Thu, 26 Jan 2012 11:43:47 +0000 Subject: [PATCH 1/5] Add xgmac ethernet model This adds very basic support for the xgmac ethernet core. Missing things include: - statistics counters - WoL support - rx checksum offload - chained descriptors (only linear descriptor ring) - broadcast and multicast handling Signed-off-by: Rob Herring Signed-off-by: Mark Langsdorf Signed-off-by: Peter Maydell --- Makefile.target | 1 + hw/xgmac.c | 421 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 422 insertions(+) create mode 100644 hw/xgmac.c diff --git a/Makefile.target b/Makefile.target index e554d333a7..063ec54ded 100644 --- a/Makefile.target +++ b/Makefile.target @@ -340,6 +340,7 @@ obj-arm-y += arm_l2x0.o obj-arm-y += arm_mptimer.o obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o obj-arm-y += pl061.o +obj-arm-y += xgmac.o obj-arm-y += arm-semi.o obj-arm-y += pxa2xx.o pxa2xx_pic.o pxa2xx_gpio.o pxa2xx_timer.o pxa2xx_dma.o obj-arm-y += pxa2xx_lcd.o pxa2xx_mmci.o pxa2xx_pcmcia.o pxa2xx_keypad.o diff --git a/hw/xgmac.c b/hw/xgmac.c new file mode 100644 index 0000000000..be63a7d562 --- /dev/null +++ b/hw/xgmac.c @@ -0,0 +1,421 @@ +/* + * QEMU model of XGMAC Ethernet. + * + * derived from the Xilinx AXI-Ethernet by Edgar E. Iglesias. + * + * Copyright (c) 2011 Calxeda, Inc. + * + * 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 "sysbus.h" +#include "qemu-char.h" +#include "qemu-log.h" +#include "net.h" +#include "net/checksum.h" + +#ifdef DEBUG_XGMAC +#define DEBUGF_BRK(message, args...) do { \ + fprintf(stderr, (message), ## args); \ + } while (0) +#else +#define DEBUGF_BRK(message, args...) do { } while (0) +#endif + +#define XGMAC_CONTROL 0x00000000 /* MAC Configuration */ +#define XGMAC_FRAME_FILTER 0x00000001 /* MAC Frame Filter */ +#define XGMAC_FLOW_CTRL 0x00000006 /* MAC Flow Control */ +#define XGMAC_VLAN_TAG 0x00000007 /* VLAN Tags */ +#define XGMAC_VERSION 0x00000008 /* Version */ +/* VLAN tag for insertion or replacement into tx frames */ +#define XGMAC_VLAN_INCL 0x00000009 +#define XGMAC_LPI_CTRL 0x0000000a /* LPI Control and Status */ +#define XGMAC_LPI_TIMER 0x0000000b /* LPI Timers Control */ +#define XGMAC_TX_PACE 0x0000000c /* Transmit Pace and Stretch */ +#define XGMAC_VLAN_HASH 0x0000000d /* VLAN Hash Table */ +#define XGMAC_DEBUG 0x0000000e /* Debug */ +#define XGMAC_INT_STATUS 0x0000000f /* Interrupt and Control */ +/* HASH table registers */ +#define XGMAC_HASH(n) ((0x00000300/4) + (n)) +#define XGMAC_NUM_HASH 16 +/* Operation Mode */ +#define XGMAC_OPMODE (0x00000400/4) +/* Remote Wake-Up Frame Filter */ +#define XGMAC_REMOTE_WAKE (0x00000700/4) +/* PMT Control and Status */ +#define XGMAC_PMT (0x00000704/4) + +#define XGMAC_ADDR_HIGH(reg) (0x00000010+((reg) * 2)) +#define XGMAC_ADDR_LOW(reg) (0x00000011+((reg) * 2)) + +#define DMA_BUS_MODE 0x000003c0 /* Bus Mode */ +#define DMA_XMT_POLL_DEMAND 0x000003c1 /* Transmit Poll Demand */ +#define DMA_RCV_POLL_DEMAND 0x000003c2 /* Received Poll Demand */ +#define DMA_RCV_BASE_ADDR 0x000003c3 /* Receive List Base */ +#define DMA_TX_BASE_ADDR 0x000003c4 /* Transmit List Base */ +#define DMA_STATUS 0x000003c5 /* Status Register */ +#define DMA_CONTROL 0x000003c6 /* Ctrl (Operational Mode) */ +#define DMA_INTR_ENA 0x000003c7 /* Interrupt Enable */ +#define DMA_MISSED_FRAME_CTR 0x000003c8 /* Missed Frame Counter */ +/* Receive Interrupt Watchdog Timer */ +#define DMA_RI_WATCHDOG_TIMER 0x000003c9 +#define DMA_AXI_BUS 0x000003ca /* AXI Bus Mode */ +#define DMA_AXI_STATUS 0x000003cb /* AXI Status */ +#define DMA_CUR_TX_DESC_ADDR 0x000003d2 /* Current Host Tx Descriptor */ +#define DMA_CUR_RX_DESC_ADDR 0x000003d3 /* Current Host Rx Descriptor */ +#define DMA_CUR_TX_BUF_ADDR 0x000003d4 /* Current Host Tx Buffer */ +#define DMA_CUR_RX_BUF_ADDR 0x000003d5 /* Current Host Rx Buffer */ +#define DMA_HW_FEATURE 0x000003d6 /* Enabled Hardware Features */ + +/* DMA Status register defines */ +#define DMA_STATUS_GMI 0x08000000 /* MMC interrupt */ +#define DMA_STATUS_GLI 0x04000000 /* GMAC Line interface int */ +#define DMA_STATUS_EB_MASK 0x00380000 /* Error Bits Mask */ +#define DMA_STATUS_EB_TX_ABORT 0x00080000 /* Error Bits - TX Abort */ +#define DMA_STATUS_EB_RX_ABORT 0x00100000 /* Error Bits - RX Abort */ +#define DMA_STATUS_TS_MASK 0x00700000 /* Transmit Process State */ +#define DMA_STATUS_TS_SHIFT 20 +#define DMA_STATUS_RS_MASK 0x000e0000 /* Receive Process State */ +#define DMA_STATUS_RS_SHIFT 17 +#define DMA_STATUS_NIS 0x00010000 /* Normal Interrupt Summary */ +#define DMA_STATUS_AIS 0x00008000 /* Abnormal Interrupt Summary */ +#define DMA_STATUS_ERI 0x00004000 /* Early Receive Interrupt */ +#define DMA_STATUS_FBI 0x00002000 /* Fatal Bus Error Interrupt */ +#define DMA_STATUS_ETI 0x00000400 /* Early Transmit Interrupt */ +#define DMA_STATUS_RWT 0x00000200 /* Receive Watchdog Timeout */ +#define DMA_STATUS_RPS 0x00000100 /* Receive Process Stopped */ +#define DMA_STATUS_RU 0x00000080 /* Receive Buffer Unavailable */ +#define DMA_STATUS_RI 0x00000040 /* Receive Interrupt */ +#define DMA_STATUS_UNF 0x00000020 /* Transmit Underflow */ +#define DMA_STATUS_OVF 0x00000010 /* Receive Overflow */ +#define DMA_STATUS_TJT 0x00000008 /* Transmit Jabber Timeout */ +#define DMA_STATUS_TU 0x00000004 /* Transmit Buffer Unavailable */ +#define DMA_STATUS_TPS 0x00000002 /* Transmit Process Stopped */ +#define DMA_STATUS_TI 0x00000001 /* Transmit Interrupt */ + +/* DMA Control register defines */ +#define DMA_CONTROL_ST 0x00002000 /* Start/Stop Transmission */ +#define DMA_CONTROL_SR 0x00000002 /* Start/Stop Receive */ +#define DMA_CONTROL_DFF 0x01000000 /* Disable flush of rx frames */ + +struct desc { + uint32_t ctl_stat; + uint16_t buffer1_size; + uint16_t buffer2_size; + uint32_t buffer1_addr; + uint32_t buffer2_addr; + uint32_t ext_stat; + uint32_t res[3]; +}; + +#define R_MAX 0x400 + +typedef struct RxTxStats { + uint64_t rx_bytes; + uint64_t tx_bytes; + + uint64_t rx; + uint64_t rx_bcast; + uint64_t rx_mcast; +} RxTxStats; + +typedef struct XgmacState { + SysBusDevice busdev; + MemoryRegion iomem; + qemu_irq sbd_irq; + qemu_irq pmt_irq; + qemu_irq mci_irq; + NICState *nic; + NICConf conf; + + struct RxTxStats stats; + uint32_t regs[R_MAX]; +} XgmacState; + +const VMStateDescription vmstate_rxtx_stats = { + .name = "xgmac_stats", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(rx_bytes, RxTxStats), + VMSTATE_UINT64(tx_bytes, RxTxStats), + VMSTATE_UINT64(rx, RxTxStats), + VMSTATE_UINT64(rx_bcast, RxTxStats), + VMSTATE_UINT64(rx_mcast, RxTxStats), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_xgmac = { + .name = "xgmac", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(stats, XgmacState, 0, vmstate_rxtx_stats, RxTxStats), + VMSTATE_UINT32_ARRAY(regs, XgmacState, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void xgmac_read_desc(struct XgmacState *s, struct desc *d, int rx) +{ + uint32_t addr = rx ? s->regs[DMA_CUR_RX_DESC_ADDR] : + s->regs[DMA_CUR_TX_DESC_ADDR]; + cpu_physical_memory_read(addr, d, sizeof(*d)); +} + +static void xgmac_write_desc(struct XgmacState *s, struct desc *d, int rx) +{ + int reg = rx ? DMA_CUR_RX_DESC_ADDR : DMA_CUR_TX_DESC_ADDR; + uint32_t addr = s->regs[reg]; + + if (!rx && (d->ctl_stat & 0x00200000)) { + s->regs[reg] = s->regs[DMA_TX_BASE_ADDR]; + } else if (rx && (d->buffer1_size & 0x8000)) { + s->regs[reg] = s->regs[DMA_RCV_BASE_ADDR]; + } else { + s->regs[reg] += sizeof(*d); + } + cpu_physical_memory_write(addr, d, sizeof(*d)); +} + +static void xgmac_enet_send(struct XgmacState *s) +{ + struct desc bd; + int frame_size; + int len; + uint8_t frame[8192]; + uint8_t *ptr; + + ptr = frame; + frame_size = 0; + while (1) { + xgmac_read_desc(s, &bd, 0); + if ((bd.ctl_stat & 0x80000000) == 0) { + /* Run out of descriptors to transmit. */ + break; + } + len = (bd.buffer1_size & 0xfff) + (bd.buffer2_size & 0xfff); + + if ((bd.buffer1_size & 0xfff) > 2048) { + DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- " + "xgmac buffer 1 len on send > 2048 (0x%x)\n", + __func__, bd.buffer1_size & 0xfff); + } + if ((bd.buffer2_size & 0xfff) != 0) { + DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- " + "xgmac buffer 2 len on send != 0 (0x%x)\n", + __func__, bd.buffer2_size & 0xfff); + } + if (len >= sizeof(frame)) { + DEBUGF_BRK("qemu:%s: buffer overflow %d read into %zu " + "buffer\n" , __func__, len, sizeof(frame)); + DEBUGF_BRK("qemu:%s: buffer1.size=%d; buffer2.size=%d\n", + __func__, bd.buffer1_size, bd.buffer2_size); + } + + cpu_physical_memory_read(bd.buffer1_addr, ptr, len); + ptr += len; + frame_size += len; + if (bd.ctl_stat & 0x20000000) { + /* Last buffer in frame. */ + qemu_send_packet(&s->nic->nc, frame, len); + ptr = frame; + frame_size = 0; + s->regs[DMA_STATUS] |= DMA_STATUS_TI | DMA_STATUS_NIS; + } + bd.ctl_stat &= ~0x80000000; + /* Write back the modified descriptor. */ + xgmac_write_desc(s, &bd, 0); + } +} + +static void enet_update_irq(struct XgmacState *s) +{ + int stat = s->regs[DMA_STATUS] & s->regs[DMA_INTR_ENA]; + qemu_set_irq(s->sbd_irq, !!stat); +} + +static uint64_t enet_read(void *opaque, target_phys_addr_t addr, unsigned size) +{ + struct XgmacState *s = opaque; + uint64_t r = 0; + addr >>= 2; + + switch (addr) { + case XGMAC_VERSION: + r = 0x1012; + break; + default: + if (addr < ARRAY_SIZE(s->regs)) { + r = s->regs[addr]; + } + break; + } + return r; +} + +static void enet_write(void *opaque, target_phys_addr_t addr, + uint64_t value, unsigned size) +{ + struct XgmacState *s = opaque; + + addr >>= 2; + switch (addr) { + case DMA_BUS_MODE: + s->regs[DMA_BUS_MODE] = value & ~0x1; + break; + case DMA_XMT_POLL_DEMAND: + xgmac_enet_send(s); + break; + case DMA_STATUS: + s->regs[DMA_STATUS] = s->regs[DMA_STATUS] & ~value; + break; + case DMA_RCV_BASE_ADDR: + s->regs[DMA_RCV_BASE_ADDR] = s->regs[DMA_CUR_RX_DESC_ADDR] = value; + break; + case DMA_TX_BASE_ADDR: + s->regs[DMA_TX_BASE_ADDR] = s->regs[DMA_CUR_TX_DESC_ADDR] = value; + break; + default: + if (addr < ARRAY_SIZE(s->regs)) { + s->regs[addr] = value; + } + break; + } + enet_update_irq(s); +} + +static const MemoryRegionOps enet_mem_ops = { + .read = enet_read, + .write = enet_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static int eth_can_rx(VLANClientState *nc) +{ + struct XgmacState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + /* RX enabled? */ + return s->regs[DMA_CONTROL] & DMA_CONTROL_SR; +} + +static ssize_t eth_rx(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + struct XgmacState *s = DO_UPCAST(NICState, nc, nc)->opaque; + static const unsigned char sa_bcast[6] = {0xff, 0xff, 0xff, + 0xff, 0xff, 0xff}; + int unicast, broadcast, multicast; + struct desc bd; + ssize_t ret; + + unicast = ~buf[0] & 0x1; + broadcast = memcmp(buf, sa_bcast, 6) == 0; + multicast = !unicast && !broadcast; + if (size < 12) { + s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS; + ret = -1; + goto out; + } + + xgmac_read_desc(s, &bd, 1); + if ((bd.ctl_stat & 0x80000000) == 0) { + s->regs[DMA_STATUS] |= DMA_STATUS_RU | DMA_STATUS_AIS; + ret = size; + goto out; + } + + cpu_physical_memory_write(bd.buffer1_addr, buf, size); + + /* Add in the 4 bytes for crc (the real hw returns length incl crc) */ + size += 4; + bd.ctl_stat = (size << 16) | 0x300; + xgmac_write_desc(s, &bd, 1); + + s->stats.rx_bytes += size; + s->stats.rx++; + if (multicast) { + s->stats.rx_mcast++; + } else if (broadcast) { + s->stats.rx_bcast++; + } + + s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS; + ret = size; + +out: + enet_update_irq(s); + return ret; +} + +static void eth_cleanup(VLANClientState *nc) +{ + struct XgmacState *s = DO_UPCAST(NICState, nc, nc)->opaque; + s->nic = NULL; +} + +static NetClientInfo net_xgmac_enet_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = eth_can_rx, + .receive = eth_rx, + .cleanup = eth_cleanup, +}; + +static int xgmac_enet_init(SysBusDevice *dev) +{ + struct XgmacState *s = FROM_SYSBUS(typeof(*s), dev); + + memory_region_init_io(&s->iomem, &enet_mem_ops, s, "xgmac", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->sbd_irq); + sysbus_init_irq(dev, &s->pmt_irq); + sysbus_init_irq(dev, &s->mci_irq); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(&net_xgmac_enet_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + + s->regs[XGMAC_ADDR_HIGH(0)] = (s->conf.macaddr.a[5] << 8) | + s->conf.macaddr.a[4]; + s->regs[XGMAC_ADDR_LOW(0)] = (s->conf.macaddr.a[3] << 24) | + (s->conf.macaddr.a[2] << 16) | + (s->conf.macaddr.a[1] << 8) | + s->conf.macaddr.a[0]; + + return 0; +} + +static SysBusDeviceInfo xgmac_enet_info = { + .init = xgmac_enet_init, + .qdev.name = "xgmac", + .qdev.size = sizeof(struct XgmacState), + .qdev.vmsd = &vmstate_xgmac, + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(struct XgmacState, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; +static void xgmac_enet_register(void) +{ + sysbus_register_withprop(&xgmac_enet_info); +} + +device_init(xgmac_enet_register) From d9fa31a3e20155152ae653cb6b6e6bd7db126e5c Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Thu, 26 Jan 2012 11:43:47 +0000 Subject: [PATCH 2/5] ahci: add support for non-PCI based controllers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for ahci on sysbus. Signed-off-by: Rob Herring Signed-off-by: Mark Langsdorf Reviewed-by: Andreas Färber Signed-off-by: Peter Maydell --- hw/ide/ahci.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c index 0af201de2f..0309dd65b0 100644 --- a/hw/ide/ahci.c +++ b/hw/ide/ahci.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "monitor.h" #include "dma.h" @@ -1214,3 +1215,46 @@ void ahci_reset(void *opaque) ahci_reset_port(&d->ahci, i); } } + +typedef struct SysbusAHCIState { + SysBusDevice busdev; + AHCIState ahci; + uint32_t num_ports; +} SysbusAHCIState; + +static const VMStateDescription vmstate_sysbus_ahci = { + .name = "sysbus-ahci", + .unmigratable = 1, +}; + +static int sysbus_ahci_init(SysBusDevice *dev) +{ + SysbusAHCIState *s = FROM_SYSBUS(SysbusAHCIState, dev); + ahci_init(&s->ahci, &dev->qdev, s->num_ports); + + sysbus_init_mmio(dev, &s->ahci.mem); + sysbus_init_irq(dev, &s->ahci.irq); + + qemu_register_reset(ahci_reset, &s->ahci); + return 0; +} + +static SysBusDeviceInfo sysbus_ahci_info = { + .qdev.name = "sysbus-ahci", + .qdev.size = sizeof(SysbusAHCIState), + .qdev.vmsd = &vmstate_sysbus_ahci, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("num-ports", SysbusAHCIState, num_ports, 1), + DEFINE_PROP_END_OF_LIST(), + }, + .init = sysbus_ahci_init, + + +}; + +static void sysbus_ahci_register(void) +{ + sysbus_register_withprop(&sysbus_ahci_info); +} + +device_init(sysbus_ahci_register); From 9d5ba9bfbc1ad9b743b40fa4f40624437f46b287 Mon Sep 17 00:00:00 2001 From: Mark Langsdorf Date: Thu, 26 Jan 2012 11:43:48 +0000 Subject: [PATCH 3/5] arm: add secondary cpu boot callbacks to arm_boot.c Create two functions, write_secondary_boot() and secondary_cpu_reset_hook(), to allow platforms more control of how secondary CPUs are brought up. The new functions default to NULL and aren't called unless they are populated so there are no changes to existing platform models. Signed-off-by: Mark Langsdorf Signed-off-by: Peter Maydell --- hw/arm-misc.h | 17 ++++++++++++++++ hw/arm_boot.c | 54 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/hw/arm-misc.h b/hw/arm-misc.h index 6e8ae6b02e..5e5204bbf5 100644 --- a/hw/arm-misc.h +++ b/hw/arm-misc.h @@ -30,12 +30,29 @@ struct arm_boot_info { const char *kernel_cmdline; const char *initrd_filename; target_phys_addr_t loader_start; + /* multicore boards that use the default secondary core boot functions + * need to put the address of the secondary boot code, the boot reg, + * and the GIC address in the next 3 values, respectively. boards that + * have their own boot functions can use these values as they want. + */ target_phys_addr_t smp_loader_start; target_phys_addr_t smp_bootreg_addr; target_phys_addr_t smp_priv_base; int nb_cpus; int board_id; int (*atag_board)(const struct arm_boot_info *info, void *p); + /* multicore boards that use the default secondary core boot functions + * can ignore these two function calls. If the default functions won't + * work, then write_secondary_boot() should write a suitable blob of + * code mimicing the secondary CPU startup process used by the board's + * boot loader/boot ROM code, and secondary_cpu_reset_hook() should + * perform any necessary CPU reset handling and set the PC for thei + * secondary CPUs to point at this boot blob. + */ + void (*write_secondary_boot)(CPUState *env, + const struct arm_boot_info *info); + void (*secondary_cpu_reset_hook)(CPUState *env, + const struct arm_boot_info *info); /* Used internally by arm_boot.c */ int is_linux; target_phys_addr_t initrd_size; diff --git a/hw/arm_boot.c b/hw/arm_boot.c index bf509a8eb9..35ca22ff47 100644 --- a/hw/arm_boot.c +++ b/hw/arm_boot.c @@ -28,8 +28,20 @@ static uint32_t bootloader[] = { 0 /* Kernel entry point. Set by integratorcp_init. */ }; -/* Entry point for secondary CPUs. Enable interrupt controller and - Issue WFI until start address is written to system controller. */ +/* Handling for secondary CPU boot in a multicore system. + * Unlike the uniprocessor/primary CPU boot, this is platform + * dependent. The default code here is based on the secondary + * CPU boot protocol used on realview/vexpress boards, with + * some parameterisation to increase its flexibility. + * QEMU platform models for which this code is not appropriate + * should override write_secondary_boot and secondary_cpu_reset_hook + * instead. + * + * This code enables the interrupt controllers for the secondary + * CPUs and then puts all the secondary CPUs into a loop waiting + * for an interprocessor interrupt and polling a configurable + * location for the kernel secondary CPU entry point. + */ static uint32_t smpboot[] = { 0xe59f201c, /* ldr r2, privbase */ 0xe59f001c, /* ldr r0, startaddr */ @@ -44,6 +56,26 @@ static uint32_t smpboot[] = { 0 /* bootreg: Boot register address is held here */ }; +static void default_write_secondary(CPUState *env, + const struct arm_boot_info *info) +{ + int n; + smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr; + smpboot[ARRAY_SIZE(smpboot) - 2] = info->smp_priv_base; + for (n = 0; n < ARRAY_SIZE(smpboot); n++) { + smpboot[n] = tswap32(smpboot[n]); + } + rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), + info->smp_loader_start); +} + +static void default_reset_secondary(CPUState *env, + const struct arm_boot_info *info) +{ + stl_phys_notdirty(info->smp_bootreg_addr, 0); + env->regs[15] = info->smp_loader_start; +} + #define WRITE_WORD(p, value) do { \ stl_phys_notdirty(p, value); \ p += 4; \ @@ -197,8 +229,7 @@ static void do_cpu_reset(void *opaque) info->loader_start); } } else { - stl_phys_notdirty(info->smp_bootreg_addr, 0); - env->regs[15] = info->smp_loader_start; + info->secondary_cpu_reset_hook(env, info); } } } @@ -220,6 +251,13 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) exit(1); } + if (!info->secondary_cpu_reset_hook) { + info->secondary_cpu_reset_hook = default_reset_secondary; + } + if (!info->write_secondary_boot) { + info->write_secondary_boot = default_write_secondary; + } + if (info->nb_cpus == 0) info->nb_cpus = 1; @@ -273,13 +311,7 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) rom_add_blob_fixed("bootloader", bootloader, sizeof(bootloader), info->loader_start); if (info->nb_cpus > 1) { - smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr; - smpboot[ARRAY_SIZE(smpboot) - 2] = info->smp_priv_base; - for (n = 0; n < ARRAY_SIZE(smpboot); n++) { - smpboot[n] = tswap32(smpboot[n]); - } - rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), - info->smp_loader_start); + info->write_secondary_boot(env, info); } info->initrd_size = initrd_size; } From f8414cb5c8343bdacfee53ac9e255831aed882fe Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 26 Jan 2012 11:43:48 +0000 Subject: [PATCH 4/5] arm_boot: support board IDs more than 16 bits wide Support passing a board ID value to the kernel in r1 that is more than 16 bits wide. This is needed to pass the '-1 == invalid' value for boards which only support device tree booting. Signed-off-by: Peter Maydell Tested-by: Mark Langsdorf --- hw/arm_boot.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/hw/arm_boot.c b/hw/arm_boot.c index 35ca22ff47..5f163fda02 100644 --- a/hw/arm_boot.c +++ b/hw/arm_boot.c @@ -20,10 +20,10 @@ /* The worlds second smallest bootloader. Set r0-r2, then jump to kernel. */ static uint32_t bootloader[] = { 0xe3a00000, /* mov r0, #0 */ - 0xe3a01000, /* mov r1, #0x?? */ - 0xe3811c00, /* orr r1, r1, #0x??00 */ - 0xe59f2000, /* ldr r2, [pc, #0] */ - 0xe59ff000, /* ldr pc, [pc, #0] */ + 0xe59f1004, /* ldr r1, [pc, #4] */ + 0xe59f2004, /* ldr r2, [pc, #4] */ + 0xe59ff004, /* ldr pc, [pc, #4] */ + 0, /* Board ID */ 0, /* Address of kernel args. Set by integratorcp_init. */ 0 /* Kernel entry point. Set by integratorcp_init. */ }; @@ -301,8 +301,7 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) } else { initrd_size = 0; } - bootloader[1] |= info->board_id & 0xff; - bootloader[2] |= (info->board_id >> 8) & 0xff; + bootloader[4] = info->board_id; bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR; bootloader[6] = entry; for (n = 0; n < sizeof(bootloader) / 4; n++) { From 2488514cef2e952370cf8966c805259a3e9417d4 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Thu, 26 Jan 2012 11:43:49 +0000 Subject: [PATCH 5/5] arm: SoC model for Calxeda Highbank Adds support for Calxeda's Highbank SoC. Signed-off-by: Rob Herring Signed-off-by: Mark Langsdorf Signed-off-by: Peter Maydell --- Makefile.target | 1 + hw/highbank.c | 330 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 hw/highbank.c diff --git a/Makefile.target b/Makefile.target index 063ec54ded..3c850854cc 100644 --- a/Makefile.target +++ b/Makefile.target @@ -339,6 +339,7 @@ obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o obj-arm-y += arm_l2x0.o obj-arm-y += arm_mptimer.o obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o +obj-arm-y += highbank.o obj-arm-y += pl061.o obj-arm-y += xgmac.o obj-arm-y += arm-semi.o diff --git a/hw/highbank.c b/hw/highbank.c new file mode 100644 index 0000000000..136297ca7d --- /dev/null +++ b/hw/highbank.c @@ -0,0 +1,330 @@ +/* + * Calxeda Highbank SoC emulation + * + * Copyright (c) 2010-2012 Calxeda + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + * + */ + +#include "sysbus.h" +#include "arm-misc.h" +#include "primecell.h" +#include "devices.h" +#include "loader.h" +#include "net.h" +#include "sysemu.h" +#include "boards.h" +#include "sysbus.h" +#include "blockdev.h" +#include "exec-memory.h" + +#define SMP_BOOT_ADDR 0x100 +#define SMP_BOOT_REG 0x40 +#define GIC_BASE_ADDR 0xfff10000 + +#define NIRQ_GIC 160 + +/* Board init. */ +static void highbank_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + + env->cp15.c15_config_base_address = GIC_BASE_ADDR; +} + +static void hb_write_secondary(CPUState *env, const struct arm_boot_info *info) +{ + int n; + uint32_t smpboot[] = { + 0xee100fb0, /* mrc p15, 0, r0, c0, c0, 5 - read current core id */ + 0xe210000f, /* ands r0, r0, #0x0f */ + 0xe3a03040, /* mov r3, #0x40 - jump address is 0x40 + 0x10 * core id */ + 0xe0830200, /* add r0, r3, r0, lsl #4 */ + 0xe59f2018, /* ldr r2, privbase */ + 0xe3a01001, /* mov r1, #1 */ + 0xe5821100, /* str r1, [r2, #256] */ + 0xe320f003, /* wfi */ + 0xe5901000, /* ldr r1, [r0] */ + 0xe1110001, /* tst r1, r1 */ + 0x0afffffb, /* beq */ + 0xe12fff11, /* bx r1 */ + GIC_BASE_ADDR /* privbase: gic address. */ + }; + for (n = 0; n < ARRAY_SIZE(smpboot); n++) { + smpboot[n] = tswap32(smpboot[n]); + } + rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), SMP_BOOT_ADDR); +} + +static void hb_reset_secondary(CPUState *env, const struct arm_boot_info *info) +{ + switch (info->nb_cpus) { + case 4: + stl_phys_notdirty(SMP_BOOT_REG + 0x30, 0); + case 3: + stl_phys_notdirty(SMP_BOOT_REG + 0x20, 0); + case 2: + stl_phys_notdirty(SMP_BOOT_REG + 0x10, 0); + env->regs[15] = SMP_BOOT_ADDR; + break; + default: + break; + } +} + +#define NUM_REGS 0x200 +static void hb_regs_write(void *opaque, target_phys_addr_t offset, + uint64_t value, unsigned size) +{ + uint32_t *regs = opaque; + + if (offset == 0xf00) { + if (value == 1 || value == 2) { + qemu_system_reset_request(); + } else if (value == 3) { + qemu_system_shutdown_request(); + } + } + + regs[offset/4] = value; +} + +static uint64_t hb_regs_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + uint32_t *regs = opaque; + uint32_t value = regs[offset/4]; + + if ((offset == 0x100) || (offset == 0x108) || (offset == 0x10C)) { + value |= 0x30000000; + } + + return value; +} + +static const MemoryRegionOps hb_mem_ops = { + .read = hb_regs_read, + .write = hb_regs_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +typedef struct { + SysBusDevice busdev; + MemoryRegion *iomem; + uint32_t regs[NUM_REGS]; +} HighbankRegsState; + +static VMStateDescription vmstate_highbank_regs = { + .name = "highbank-regs", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, HighbankRegsState, NUM_REGS), + VMSTATE_END_OF_LIST(), + }, +}; + +static void highbank_regs_reset(DeviceState *dev) +{ + SysBusDevice *sys_dev = sysbus_from_qdev(dev); + HighbankRegsState *s = FROM_SYSBUS(HighbankRegsState, sys_dev); + + s->regs[0x40] = 0x05F20121; + s->regs[0x41] = 0x2; + s->regs[0x42] = 0x05F30121; + s->regs[0x43] = 0x05F40121; +} + +static int highbank_regs_init(SysBusDevice *dev) +{ + HighbankRegsState *s = FROM_SYSBUS(HighbankRegsState, dev); + + s->iomem = g_new(MemoryRegion, 1); + memory_region_init_io(s->iomem, &hb_mem_ops, s->regs, "highbank_regs", + 0x1000); + sysbus_init_mmio(dev, s->iomem); + + return 0; +} + +static SysBusDeviceInfo highbank_regs_info = { + .init = highbank_regs_init, + .qdev.name = "highbank-regs", + .qdev.desc = "Calxeda Highbank registers", + .qdev.size = sizeof(HighbankRegsState), + .qdev.vmsd = &vmstate_highbank_regs, + .qdev.reset = highbank_regs_reset, +}; + +static void highbank_regs_register_device(void) +{ + sysbus_register_withprop(&highbank_regs_info); +} + +device_init(highbank_regs_register_device) + +static struct arm_boot_info highbank_binfo; + +/* ram_size must be set to match the upper bound of memory in the + * device tree (linux/arch/arm/boot/dts/highbank.dts), which is + * normally 0xff900000 or -m 4089. When running this board on a + * 32-bit host, set the reg value of memory to 0xf7ff00000 in the + * device tree and pass -m 2047 to QEMU. + */ +static void highbank_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env = NULL; + DeviceState *dev; + SysBusDevice *busdev; + qemu_irq *irqp; + qemu_irq pic[128]; + int n; + qemu_irq cpu_irq[4]; + MemoryRegion *sysram; + MemoryRegion *dram; + MemoryRegion *sysmem; + char *sysboot_filename; + + if (!cpu_model) { + cpu_model = "cortex-a9"; + } + + for (n = 0; n < smp_cpus; n++) { + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + irqp = arm_pic_init_cpu(env); + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + qemu_register_reset(highbank_cpu_reset, env); + } + + sysmem = get_system_memory(); + dram = g_new(MemoryRegion, 1); + memory_region_init_ram(dram, "highbank.dram", ram_size); + /* SDRAM at address zero. */ + memory_region_add_subregion(sysmem, 0, dram); + + sysram = g_new(MemoryRegion, 1); + memory_region_init_ram(sysram, "highbank.sysram", 0x8000); + memory_region_add_subregion(sysmem, 0xfff88000, sysram); + if (bios_name != NULL) { + sysboot_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (sysboot_filename != NULL) { + uint32_t filesize = get_image_size(sysboot_filename); + if (load_image_targphys("sysram.bin", 0xfff88000, filesize) < 0) { + hw_error("Unable to load %s\n", bios_name); + } + } else { + hw_error("Unable to find %s\n", bios_name); + } + } + + dev = qdev_create(NULL, "a9mpcore_priv"); + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_prop_set_uint32(dev, "num-irq", NIRQ_GIC); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + sysbus_mmio_map(busdev, 0, GIC_BASE_ADDR); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + + for (n = 0; n < 128; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + dev = qdev_create(NULL, "l2x0"); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + sysbus_mmio_map(busdev, 0, 0xfff12000); + + dev = qdev_create(NULL, "sp804"); + qdev_prop_set_uint32(dev, "freq0", 150000000); + qdev_prop_set_uint32(dev, "freq1", 150000000); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + sysbus_mmio_map(busdev, 0, 0xfff34000); + sysbus_connect_irq(busdev, 0, pic[18]); + sysbus_create_simple("pl011", 0xfff36000, pic[20]); + + dev = qdev_create(NULL, "highbank-regs"); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + sysbus_mmio_map(busdev, 0, 0xfff3c000); + + sysbus_create_simple("pl061", 0xfff30000, pic[14]); + sysbus_create_simple("pl061", 0xfff31000, pic[15]); + sysbus_create_simple("pl061", 0xfff32000, pic[16]); + sysbus_create_simple("pl061", 0xfff33000, pic[17]); + sysbus_create_simple("pl031", 0xfff35000, pic[19]); + sysbus_create_simple("pl022", 0xfff39000, pic[23]); + + sysbus_create_simple("sysbus-ahci", 0xffe08000, pic[83]); + + if (nd_table[0].vlan) { + qemu_check_nic_model(&nd_table[0], "xgmac"); + dev = qdev_create(NULL, "xgmac"); + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, 0xfff50000); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, pic[77]); + sysbus_connect_irq(sysbus_from_qdev(dev), 1, pic[78]); + sysbus_connect_irq(sysbus_from_qdev(dev), 2, pic[79]); + + qemu_check_nic_model(&nd_table[1], "xgmac"); + dev = qdev_create(NULL, "xgmac"); + qdev_set_nic_properties(dev, &nd_table[1]); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, 0xfff51000); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, pic[80]); + sysbus_connect_irq(sysbus_from_qdev(dev), 1, pic[81]); + sysbus_connect_irq(sysbus_from_qdev(dev), 2, pic[82]); + } + + highbank_binfo.ram_size = ram_size; + highbank_binfo.kernel_filename = kernel_filename; + highbank_binfo.kernel_cmdline = kernel_cmdline; + highbank_binfo.initrd_filename = initrd_filename; + /* highbank requires a dtb in order to boot, and the dtb will override + * the board ID. The following value is ignored, so set it to -1 to be + * clear that the value is meaningless. + */ + highbank_binfo.board_id = -1; + highbank_binfo.nb_cpus = smp_cpus; + highbank_binfo.loader_start = 0; + highbank_binfo.write_secondary_boot = hb_write_secondary; + highbank_binfo.secondary_cpu_reset_hook = hb_reset_secondary; + arm_load_kernel(first_cpu, &highbank_binfo); +} + +static QEMUMachine highbank_machine = { + .name = "highbank", + .desc = "Calxeda Highbank (ECX-1000)", + .init = highbank_init, + .use_scsi = 1, + .max_cpus = 4, +}; + +static void highbank_machine_init(void) +{ + qemu_register_machine(&highbank_machine); +} + +machine_init(highbank_machine_init);