From: Chris Wulff <crwu...@gmail.com> Signed-off-by: Chris Wulff <crwu...@gmail.com> --- hw/Makefile.objs | 5 ++ hw/altera_timer.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/altera_uart.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/altera_vic.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++ hw/nios2.h | 52 +++++++++++++ hw/nios2_pic_cpu.c | 39 ++++++++++ 6 files changed, 714 insertions(+) create mode 100644 hw/altera_timer.c create mode 100644 hw/altera_uart.c create mode 100644 hw/altera_vic.c create mode 100644 hw/nios2.h create mode 100644 hw/nios2_pic_cpu.c
diff --git a/hw/Makefile.objs b/hw/Makefile.objs index af4ab0c..32275a5 100644 --- a/hw/Makefile.objs +++ b/hw/Makefile.objs @@ -69,6 +69,11 @@ common-obj-$(CONFIG_XILINX_AXI) += xilinx_axidma.o common-obj-$(CONFIG_XILINX_AXI) += xilinx_axienet.o common-obj-$(CONFIG_XILINX_AXI) += stream.o +# Altera devices +common-obj-$(CONFIG_ALTERA) += altera_vic.o +common-obj-$(CONFIG_ALTERA) += altera_uart.o +common-obj-$(CONFIG_ALTERA) += altera_timer.o + # PKUnity SoC devices common-obj-$(CONFIG_PUV3) += puv3_intc.o common-obj-$(CONFIG_PUV3) += puv3_ost.o diff --git a/hw/altera_timer.c b/hw/altera_timer.c new file mode 100644 index 0000000..9c31e59 --- /dev/null +++ b/hw/altera_timer.c @@ -0,0 +1,207 @@ +/* + * QEMU model of the Altera timer. + * + * Copyright (c) 2012 Chris Wulff <crwu...@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * <http://www.gnu.org/licenses/lgpl-2.1.html> + */ + +#include "sysbus.h" +#include "sysemu.h" +#include "ptimer.h" + +#define R_STATUS 0 +#define R_CONTROL 1 +#define R_PERIODL 2 +#define R_PERIODH 3 +#define R_SNAPL 4 +#define R_SNAPH 5 +#define R_MAX 6 + +#define STATUS_TO 0x0001 +#define STATUS_RUN 0x0002 + +#define CONTROL_ITO 0x0001 +#define CONTROL_CONT 0x0002 +#define CONTROL_START 0x0004 +#define CONTROL_STOP 0x0008 + +typedef struct AlteraTimer { + SysBusDevice busdev; + MemoryRegion mmio; + qemu_irq irq; + uint32_t freq_hz; + QEMUBH *bh; + ptimer_state *ptimer; + uint32_t regs[R_MAX]; +} AlteraTimer; + +static uint64_t timer_read(void *opaque, hwaddr addr, + unsigned int size) +{ + AlteraTimer *t = opaque; + uint64_t r = 0; + + addr >>= 2; + addr &= 0x7; + switch (addr) { + case R_STATUS: + r = t->regs[R_STATUS]; + break; + + default: + if (addr < ARRAY_SIZE(t->regs)) { + r = t->regs[addr]; + } + break; + } + + return r; +} + +static void timer_start(AlteraTimer *t) +{ + ptimer_stop(t->ptimer); + ptimer_set_count(t->ptimer, (t->regs[R_PERIODH]<<16) | t->regs[R_PERIODL]); + ptimer_run(t->ptimer, 1); +} + +static inline int timer_irq_state(AlteraTimer *t) +{ + return (t->regs[R_STATUS] & t->regs[R_CONTROL] & CONTROL_ITO) ? 1 : 0; +} + +static void timer_write(void *opaque, hwaddr addr, + uint64_t val64, unsigned int size) +{ + AlteraTimer *t = opaque; + uint32_t value = val64; + uint32_t count = 0; + int irqState = timer_irq_state(t); + + addr >>= 2; + addr &= 0x7; + switch (addr) { + case R_STATUS: + /* Writing zero clears the timeout */ + t->regs[R_STATUS] &= ~STATUS_TO; + break; + + case R_CONTROL: + t->regs[R_CONTROL] = value & (CONTROL_ITO | CONTROL_CONT); + if ((value & CONTROL_START) && + ((t->regs[R_STATUS] & STATUS_RUN) == 0)) { + timer_start(t); + } + if ((value & CONTROL_STOP) && (t->regs[R_STATUS] & STATUS_RUN)) { + ptimer_stop(t->ptimer); + } + break; + + case R_PERIODL: + case R_PERIODH: + t->regs[addr] = value & 0xFFFF; + if (t->regs[R_STATUS] & STATUS_RUN) { + timer_start(t); + } + break; + + case R_SNAPL: + case R_SNAPH: + count = ptimer_get_count(t->ptimer); + t->regs[R_SNAPL] = count & 0xFFFF; + t->regs[R_SNAPH] = (count>>16) & 0xFFFF; + break; + + default: + if (addr < ARRAY_SIZE(t->regs)) { + t->regs[addr] = value; + } + break; + } + + if (irqState != timer_irq_state(t)) { + qemu_set_irq(t->irq, timer_irq_state(t)); + } +} + +static const MemoryRegionOps timer_ops = { + .read = timer_read, + .write = timer_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4 + } +}; + +static void timer_hit(void *opaque) +{ + AlteraTimer *t = opaque; + + t->regs[R_STATUS] |= STATUS_TO; + + if (t->regs[R_CONTROL] & CONTROL_CONT) { + timer_start(t); + } + + qemu_set_irq(t->irq, timer_irq_state(t)); +} + +static int altera_timer_init(SysBusDevice *dev) +{ + AlteraTimer *t = FROM_SYSBUS(typeof(*t), dev); + + assert(t->freq_hz != 0); + + sysbus_init_irq(dev, &t->irq); + + t->bh = qemu_bh_new(timer_hit, t); + t->ptimer = ptimer_init(t->bh); + ptimer_set_freq(t->ptimer, t->freq_hz); + + memory_region_init_io(&t->mmio, &timer_ops, t, "ALTR.timer", + R_MAX * sizeof(uint32_t)); + sysbus_init_mmio(dev, &t->mmio); + return 0; +} + +static Property altera_timer_properties[] = { + DEFINE_PROP_UINT32("clock-frequency", AlteraTimer, freq_hz, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void altera_timer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = altera_timer_init; + dc->props = altera_timer_properties; +} + +static const TypeInfo altera_timer_info = { + .name = "ALTR.timer", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AlteraTimer), + .class_init = altera_timer_class_init, +}; + +static void altera_timer_register(void) +{ + type_register_static(&altera_timer_info); +} + +type_init(altera_timer_register) diff --git a/hw/altera_uart.c b/hw/altera_uart.c new file mode 100644 index 0000000..862c99e --- /dev/null +++ b/hw/altera_uart.c @@ -0,0 +1,217 @@ +/* + * QEMU model of the Altera uart. + * + * Copyright (c) 2012 Chris Wulff <crwu...@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * <http://www.gnu.org/licenses/lgpl-2.1.html> + */ + +#include "sysbus.h" +#include "qemu-char.h" + +#define R_RXDATA 0 +#define R_TXDATA 1 +#define R_STATUS 2 +#define R_CONTROL 3 +#define R_DIVISOR 4 +#define R_ENDOFPACKET 5 +#define R_MAX 6 + +#define STATUS_PE 0x0001 +#define STATUS_FE 0x0002 +#define STATUS_BRK 0x0004 +#define STATUS_ROE 0x0008 +#define STATUS_TOE 0x0010 +#define STATUS_TMT 0x0020 +#define STATUS_TRDY 0x0040 +#define STATUS_RRDY 0x0080 +#define STATUS_E 0x0100 +#define STATUS_DTCS 0x0400 +#define STATUS_CTS 0x0800 +#define STATUS_EOP 0x1000 + +#define CONTROL_IPE 0x0001 +#define CONTROL_IFE 0x0002 +#define CONTROL_IBRK 0x0004 +#define CONTROL_IROE 0x0008 +#define CONTROL_ITOE 0x0010 +#define CONTROL_ITMT 0x0020 +#define CONTROL_ITRDY 0x0040 +#define CONTROL_IRRDY 0x0080 +#define CONTROL_IE 0x0100 +#define CONTROL_TBRK 0x0200 +#define CONTROL_IDTCS 0x0400 +#define CONTROL_RTS 0x0800 +#define CONTROL_IEOP 0x1000 + +typedef struct AlteraUART { + SysBusDevice busdev; + MemoryRegion mmio; + CharDriverState *chr; + qemu_irq irq; + + uint32_t regs[R_MAX]; +} AlteraUART; + +static void uart_update_irq(AlteraUART *s) +{ + unsigned int irq; + + irq = (s->regs[R_STATUS] & s->regs[R_CONTROL] & + (STATUS_PE | STATUS_FE | STATUS_BRK | STATUS_ROE | STATUS_TOE | + STATUS_TMT | STATUS_TRDY | STATUS_RRDY | STATUS_E | STATUS_DTCS)); + irq = (irq == 0) ? 0 : 1; + qemu_set_irq(s->irq, irq); +} + +static uint64_t uart_read(void *opaque, hwaddr addr, + unsigned int size) +{ + AlteraUART *s = opaque; + uint32_t r = 0; + addr >>= 2; + addr &= 0x7; + switch (addr) { + case R_RXDATA: + r = s->regs[R_RXDATA]; + s->regs[R_STATUS] &= ~STATUS_RRDY; + uart_update_irq(s); + break; + + case R_STATUS: + r = s->regs[R_STATUS]; + s->regs[R_STATUS] &= ~(STATUS_PE | STATUS_FE | STATUS_BRK | + STATUS_ROE | STATUS_TOE | STATUS_E | + STATUS_DTCS); + uart_update_irq(s); + break; + + default: + if (addr < ARRAY_SIZE(s->regs)) { + r = s->regs[addr]; + } + break; + } + + return r; +} + +static void uart_write(void *opaque, hwaddr addr, + uint64_t val64, unsigned int size) +{ + AlteraUART *s = opaque; + uint32_t value = val64; + unsigned char ch = value; + + addr >>= 2; + addr &= 0x7; + + switch (addr) { + case R_TXDATA: + if (s->chr) { + qemu_chr_fe_write(s->chr, &ch, 1); + } + + s->regs[addr] = value; + break; + + case R_RXDATA: + case R_STATUS: + /* No writeable bits */ + break; + + default: + s->regs[addr] = value; + break; + } + uart_update_irq(s); +} + +static void uart_rx(void *opaque, const uint8_t *buf, int size) +{ + AlteraUART *s = opaque; + + s->regs[R_RXDATA] = *buf; + s->regs[R_STATUS] |= STATUS_RRDY; + + uart_update_irq(s); +} + +static int uart_can_rx(void *opaque) +{ + AlteraUART *s = opaque; + return ((s->regs[R_STATUS] & STATUS_RRDY) == 0); +} + +static void uart_event(void *opaque, int event) +{ +} + +static const MemoryRegionOps uart_ops = { + .read = uart_read, + .write = uart_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4 + } +}; + +static int altera_uart_init(SysBusDevice *dev) +{ + AlteraUART *s = FROM_SYSBUS(typeof(*s), dev); + + s->regs[R_STATUS] = STATUS_TMT | STATUS_TRDY; /* Always ready to tx */ + + sysbus_init_irq(dev, &s->irq); + + memory_region_init_io(&s->mmio, &uart_ops, s, + "ALTR.uart", R_MAX * sizeof(uint32_t)); + sysbus_init_mmio(dev, &s->mmio); + + s->chr = qemu_char_get_next_serial(); + if (s->chr) { + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); + } + + return 0; +} + +static Property altera_uart_properties[] = { + DEFINE_PROP_END_OF_LIST(), +}; + +static void altera_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = altera_uart_init; + dc->props = altera_uart_properties; +} + +static const TypeInfo altera_uart_info = { + .name = "ALTR.uart", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AlteraUART), + .class_init = altera_uart_class_init, +}; + +static void altera_uart_register(void) +{ + type_register_static(&altera_uart_info); +} + +type_init(altera_uart_register) diff --git a/hw/altera_vic.c b/hw/altera_vic.c new file mode 100644 index 0000000..9f6f8b9 --- /dev/null +++ b/hw/altera_vic.c @@ -0,0 +1,194 @@ +/* + * QEMU Altera Vectored Interrupt Controller. + * + * Copyright (c) 2012 Chris Wulff <crwu...@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * <http://www.gnu.org/licenses/lgpl-2.1.html> + */ + +#include "sysbus.h" +#include "hw.h" + +#define R_INT_CONFIG_0 0 +#define R_INT_ENABLE 32 +#define R_INT_ENABLE_SET 33 +#define R_INT_ENABLE_CLR 34 +#define R_INT_PENDING 35 +#define R_INT_RAW_STATUS 36 +#define R_SW_INTERRUPT 37 +#define R_SW_INTERRUPT_SET 38 +#define R_SW_INTERRUPT_CLR 39 +#define R_VIC_CONFIG 40 +#define R_VIC_STATUS 41 +#define R_VEC_TBL_BASE 42 +#define R_VEC_TBL_ADDR 43 +#define R_MAX 44 + +typedef struct AlteraVIC { + SysBusDevice busdev; + MemoryRegion mmio; + qemu_irq parent_irq; + + /* Runtime control registers. */ + uint32_t regs[R_MAX]; +} AlteraVIC; + +static void update_irq(AlteraVIC *pv) +{ + uint32_t i; + pv->regs[R_INT_PENDING] = (pv->regs[R_INT_RAW_STATUS] | + pv->regs[R_SW_INTERRUPT]) & + pv->regs[R_INT_ENABLE]; + + for (i = 0; i < 32; i++) { + if (pv->regs[R_INT_PENDING] & (1 << i)) { + break; + } + } + if (i == 32) { + pv->regs[R_VEC_TBL_ADDR] = 0; + pv->regs[R_VIC_STATUS] = 0; + qemu_irq_lower(pv->parent_irq); + } else { + pv->regs[R_VEC_TBL_ADDR] = pv->regs[R_VEC_TBL_BASE] + + i * (4 << (pv->regs[R_VIC_CONFIG] & 7)); + pv->regs[R_VIC_STATUS] = 0x80000000 | i; + qemu_irq_raise(pv->parent_irq); + } +} + +static uint64_t pic_read(void *opaque, hwaddr addr, + unsigned int size) +{ + AlteraVIC *pv = opaque; + uint32_t r = 0; + + addr >>= 2; + if (addr < R_MAX) { + r = pv->regs[addr]; + } + + return r; +} + +static void pic_write(void *opaque, hwaddr addr, + uint64_t val64, unsigned int size) +{ + AlteraVIC *pv = opaque; + uint32_t value = val64; + + addr >>= 2; + if (addr < R_INT_ENABLE) { + /* R_INT_CONFIG_XX */ + pv->regs[addr] = value & 0x00001FFF; + } else { + switch (addr) { + case R_INT_PENDING: + case R_INT_RAW_STATUS: + case R_VIC_STATUS: + case R_VEC_TBL_ADDR: + /* read only */ + break; + + case R_INT_ENABLE_SET: + pv->regs[R_INT_ENABLE] |= value; + break; + + case R_SW_INTERRUPT_SET: + pv->regs[R_SW_INTERRUPT] |= value; + break; + + case R_INT_ENABLE_CLR: + pv->regs[R_INT_ENABLE] &= ~value; + break; + + case R_SW_INTERRUPT_CLR: + pv->regs[R_SW_INTERRUPT] &= ~value; + break; + + case R_VIC_CONFIG: + pv->regs[addr] = value & 0x0000000F; + break; + + default: + if (addr < ARRAY_SIZE(pv->regs)) { + pv->regs[addr] = value; + } + break; + } + } + update_irq(pv); +} + +static const MemoryRegionOps pic_ops = { + .read = pic_read, + .write = pic_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4 + } +}; + +static void irq_handler(void *opaque, int irq, int level) +{ + AlteraVIC *pv = opaque; + + pv->regs[R_INT_RAW_STATUS] &= ~(1 << irq); + pv->regs[R_INT_RAW_STATUS] |= level << irq; + + update_irq(pv); +} + +static int altera_vic_init(SysBusDevice *dev) +{ + AlteraVIC *pv = FROM_SYSBUS(typeof(*pv), dev); + + qdev_init_gpio_in(&dev->qdev, irq_handler, 32); + sysbus_init_irq(dev, &pv->parent_irq); + + memset(pv->regs, 0, sizeof(uint32_t) * R_MAX); + memory_region_init_io(&pv->mmio, &pic_ops, pv, + "ALTR.vic", R_MAX * sizeof(uint32_t)); + sysbus_init_mmio(dev, &pv->mmio); + return 0; +} + +static Property altera_vic_properties[] = { + DEFINE_PROP_END_OF_LIST(), +}; + +static void altera_vic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = altera_vic_init; + dc->props = altera_vic_properties; +} + +static const TypeInfo altera_vic_info = { + .name = "ALTR.vic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AlteraVIC), + .class_init = altera_vic_class_init, +}; + +static void altera_vic_register(void) +{ + type_register_static(&altera_vic_info); +} + +type_init(altera_vic_register) diff --git a/hw/nios2.h b/hw/nios2.h new file mode 100644 index 0000000..4f1325a --- /dev/null +++ b/hw/nios2.h @@ -0,0 +1,52 @@ +/* + * Altera Nios II CPU interrupt controllers + * + * Copyright (c) 2012 Chris Wulff <crwu...@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * <http://www.gnu.org/licenses/lgpl-2.1.html> + */ + +#ifndef _NIOS2_H_INCLUDED_ +#define _NIOS2_H_INCLUDED_ + +#include "sysbus.h" + +qemu_irq *nios2_pic_init_cpu(Nios2CPU *cpu); + +static inline DeviceState * +altera_vic_create(hwaddr base, qemu_irq irq, int kind_of_intr) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "altera,vic"); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + return dev; +} + +static inline DeviceState * +altera_iic_create(Nios2CPU *cpu, qemu_irq irq, int kind_of_intr) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "altera,iic"); + qdev_prop_set_ptr(dev, "cpu", cpu); + qdev_init_nofail(dev); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + return dev; +} + +#endif /* _NIOS2_H_INCLUDED_ */ diff --git a/hw/nios2_pic_cpu.c b/hw/nios2_pic_cpu.c new file mode 100644 index 0000000..5b6d05b --- /dev/null +++ b/hw/nios2_pic_cpu.c @@ -0,0 +1,39 @@ +/* + * QEMU Altera Nios II CPU interrupt wrapper logic. + * + * Copyright (c) 2012 Chris Wulff <crwu...@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * <http://www.gnu.org/licenses/lgpl-2.1.html> + */ + +#include "hw.h" +#include "nios2.h" + +static void nios2_pic_cpu_handler(void *opaque, int irq, int level) +{ + Nios2CPU *cpu = opaque; + int type = irq ? CPU_INTERRUPT_NMI : CPU_INTERRUPT_HARD; + + if (level) { + cpu_interrupt(&cpu->env, type); + } else { + cpu_reset_interrupt(&cpu->env, type); + } +} + +qemu_irq *nios2_pic_init_cpu(Nios2CPU *cpu) +{ + return qemu_allocate_irqs(nios2_pic_cpu_handler, cpu, 2); +} -- 1.7.10.4