diff mbox series

SiFive RISC-V GPIO Device

Message ID 20190212173839.31587-1-chouteau@adacore.com (mailing list archive)
State New, archived
Headers show
Series SiFive RISC-V GPIO Device | expand

Commit Message

Fabien Chouteau Feb. 12, 2019, 5:38 p.m. UTC
QEMU model of the GPIO device on the SiFive E300 series SOCs.

The pins are not used by a board definition yet, however this
implementation can already be used to trigger GPIO interrupts from the
software by configuring a pin as both output and input.

Signed-off-by: Fabien Chouteau <chouteau@adacore.com>
---
 Makefile.objs                  |   1 +
 hw/riscv/Makefile.objs         |   1 +
 hw/riscv/sifive_e.c            |  28 ++-
 hw/riscv/sifive_gpio.c         | 388 +++++++++++++++++++++++++++++++++
 hw/riscv/trace-events          |   7 +
 include/hw/riscv/sifive_e.h    |   8 +-
 include/hw/riscv/sifive_gpio.h |  72 ++++++
 7 files changed, 501 insertions(+), 4 deletions(-)
 create mode 100644 hw/riscv/sifive_gpio.c
 create mode 100644 hw/riscv/trace-events
 create mode 100644 include/hw/riscv/sifive_gpio.h

Comments

Alistair Francis Feb. 13, 2019, 12:06 a.m. UTC | #1
On Tue, Feb 12, 2019 at 9:39 AM Fabien Chouteau <chouteau@adacore.com> wrote:
>
> QEMU model of the GPIO device on the SiFive E300 series SOCs.
>
> The pins are not used by a board definition yet, however this
> implementation can already be used to trigger GPIO interrupts from the
> software by configuring a pin as both output and input.
>
> Signed-off-by: Fabien Chouteau <chouteau@adacore.com>

Hey,

Thanks for the patch!

> ---
>  Makefile.objs                  |   1 +
>  hw/riscv/Makefile.objs         |   1 +
>  hw/riscv/sifive_e.c            |  28 ++-
>  hw/riscv/sifive_gpio.c         | 388 +++++++++++++++++++++++++++++++++
>  hw/riscv/trace-events          |   7 +
>  include/hw/riscv/sifive_e.h    |   8 +-
>  include/hw/riscv/sifive_gpio.h |  72 ++++++
>  7 files changed, 501 insertions(+), 4 deletions(-)
>  create mode 100644 hw/riscv/sifive_gpio.c
>  create mode 100644 hw/riscv/trace-events
>  create mode 100644 include/hw/riscv/sifive_gpio.h
>
> diff --git a/Makefile.objs b/Makefile.objs
> index 67a054b08a..d40eb089ae 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -184,6 +184,7 @@ trace-events-subdirs += hw/virtio
>  trace-events-subdirs += hw/watchdog
>  trace-events-subdirs += hw/xen
>  trace-events-subdirs += hw/gpio
> +trace-events-subdirs += hw/riscv
>  trace-events-subdirs += io
>  trace-events-subdirs += linux-user
>  trace-events-subdirs += migration
> diff --git a/hw/riscv/Makefile.objs b/hw/riscv/Makefile.objs
> index 1dde01d39d..ced7935371 100644
> --- a/hw/riscv/Makefile.objs
> +++ b/hw/riscv/Makefile.objs
> @@ -7,5 +7,6 @@ obj-y += sifive_plic.o
>  obj-y += sifive_test.o
>  obj-y += sifive_u.o
>  obj-y += sifive_uart.o
> +obj-y += sifive_gpio.o

I know the other RISC-V files don't do it, but this should go in the
hw/gpio directory instead of hw/riscv.

>  obj-y += spike.o
>  obj-y += virt.o
> diff --git a/hw/riscv/sifive_e.c b/hw/riscv/sifive_e.c
> index 5d9d65ff29..49c1dd986c 100644
> --- a/hw/riscv/sifive_e.c
> +++ b/hw/riscv/sifive_e.c
> @@ -146,11 +146,15 @@ static void riscv_sifive_e_soc_init(Object *obj)
>                              &error_abort);
>      object_property_set_int(OBJECT(&s->cpus), smp_cpus, "num-harts",
>                              &error_abort);
> +    sysbus_init_child_obj(obj, "riscv.sifive.e.gpio0",
> +                          &s->gpio, sizeof(s->gpio),
> +                          TYPE_SIFIVE_GPIO);
>  }
>
>  static void riscv_sifive_e_soc_realize(DeviceState *dev, Error **errp)
>  {
>      const struct MemmapEntry *memmap = sifive_e_memmap;
> +    Error *err = NULL;
>
>      SiFiveESoCState *s = RISCV_E_SOC(dev);
>      MemoryRegion *sys_mem = get_system_memory();
> @@ -184,8 +188,28 @@ static void riscv_sifive_e_soc_realize(DeviceState *dev, Error **errp)
>      sifive_mmio_emulate(sys_mem, "riscv.sifive.e.aon",
>          memmap[SIFIVE_E_AON].base, memmap[SIFIVE_E_AON].size);
>      sifive_prci_create(memmap[SIFIVE_E_PRCI].base);
> -    sifive_mmio_emulate(sys_mem, "riscv.sifive.e.gpio0",
> -        memmap[SIFIVE_E_GPIO0].base, memmap[SIFIVE_E_GPIO0].size);
> +
> +    /* GPIO */
> +
> +    object_property_set_bool(OBJECT(&s->gpio), true, "realized", &err);
> +    if (err) {
> +        error_propagate(errp, err);
> +        return;
> +    }
> +
> +    /* Map GPIO registers */
> +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio), 0, memmap[SIFIVE_E_GPIO0].base);
> +
> +    /* Pass all GPIOs to the SOC layer so they are available to the board */
> +    qdev_pass_gpios(DEVICE(&s->gpio), dev, NULL);
> +
> +    /* Connect GPIO interrupts to the PLIC */
> +    for (int i = 0; i < 32; i++) {
> +        sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio), i,
> +                           qdev_get_gpio_in(DEVICE(s->plic),
> +                                            SIFIVE_E_GPIO0_IRQ0 + i));
> +    }
> +

It's common in QEMU world to split your patch in two. One that adds
the device and then one that connects it.

In this case the patch isn't too complex so it's fine, just for future
reference.

>      sifive_uart_create(sys_mem, memmap[SIFIVE_E_UART0].base,
>          serial_hd(0), qdev_get_gpio_in(DEVICE(s->plic), SIFIVE_E_UART0_IRQ));
>      sifive_mmio_emulate(sys_mem, "riscv.sifive.e.qspi0",
> diff --git a/hw/riscv/sifive_gpio.c b/hw/riscv/sifive_gpio.c
> new file mode 100644
> index 0000000000..06bd8112d7
> --- /dev/null
> +++ b/hw/riscv/sifive_gpio.c
> @@ -0,0 +1,388 @@
> +/*
> + * sifive System-on-Chip general purpose input/output register definition
> + *
> + * Copyright 2019 AdaCore
> + *
> + * Base on nrf51_gpio.c:
> + *
> + * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
> + *
> + * This code is licensed under the GPL version 2 or later.  See
> + * the COPYING file in the top-level directory.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "hw/riscv/sifive_gpio.h"
> +#include "trace.h"
> +
> +static void update_output_irq(SIFIVEGPIOState *s)
> +{
> +

Remove the new line.

> +    uint32_t pending;
> +    uint32_t pin;
> +
> +    pending = s->high_ip & s->high_ie;
> +    pending |= s->low_ip & s->low_ie;
> +    pending |= s->rise_ip & s->rise_ie;
> +    pending |= s->fall_ip & s->fall_ie;
> +
> +    for (int i = 0; i < SIFIVE_GPIO_PINS; i++) {
> +        pin = 1 << i;
> +        qemu_set_irq(s->irq[i], (pending & pin) != 0);
> +        trace_sifive_gpio_update_output_irq(i, (pending & pin) != 0);
> +    }
> +}
> +
> +static void update_state(SIFIVEGPIOState *s)
> +{
> +    size_t i;
> +    bool prev_ival, in, in_mask, port, out_xor, pull, output_en, input_en,
> +        rise_ip, fall_ip, low_ip, high_ip, oval, actual_value, ival;
> +
> +    for (i = 0; i < SIFIVE_GPIO_PINS; i++) {
> +
> +        prev_ival = extract32(s->value, i, 1);
> +        in        = extract32(s->in, i, 1);
> +        in_mask   = extract32(s->in_mask, i, 1);
> +        port      = extract32(s->port, i, 1);
> +        out_xor   = extract32(s->out_xor, i, 1);
> +        pull      = extract32(s->pue, i, 1);
> +        output_en = extract32(s->output_en, i, 1);
> +        input_en  = extract32(s->input_en, i, 1);
> +        rise_ip   = extract32(s->rise_ip, i, 1);
> +        fall_ip   = extract32(s->fall_ip, i, 1);
> +        low_ip    = extract32(s->low_ip, i, 1);
> +        high_ip   = extract32(s->high_ip, i, 1);
> +
> +        /* Output value (IOF not supported) */
> +        oval = output_en && (port ^ out_xor);
> +
> +        /* Pin both driven externally and internally */
> +        if (output_en && in_mask) {
> +            qemu_log_mask(LOG_GUEST_ERROR, "GPIO pin %zu short circuited\n", i);
> +        }
> +
> +        if (in_mask) {
> +            /* The pin is driven by external device */
> +            actual_value = in;
> +        } else if (output_en) {
> +            /* The pin is driven by internal circuit */
> +            actual_value = oval;
> +        } else {
> +            /* Floating? Apply pull-up resistor */
> +            actual_value = pull;
> +        }
> +
> +        qemu_set_irq(s->output[i], actual_value);
> +
> +        /* Input value */
> +        ival = input_en && actual_value;
> +
> +        /* Interrupts */
> +        high_ip = high_ip || ival;
> +        s->high_ip = deposit32(s->high_ip, i, 1, high_ip);
> +
> +        low_ip = low_ip || !ival;
> +        s->low_ip = deposit32(s->low_ip,  i, 1, low_ip);
> +
> +        rise_ip = rise_ip || (ival && !prev_ival);
> +        s->rise_ip = deposit32(s->rise_ip, i, 1, rise_ip);
> +
> +        fall_ip = fall_ip || (!ival && prev_ival);
> +        s->fall_ip = deposit32(s->fall_ip, i, 1, fall_ip);
> +
> +        /* Update value */
> +        s->value = deposit32(s->value, i, 1, ival);
> +    }
> +    update_output_irq(s);
> +}
> +
> +static uint64_t sifive_gpio_read(void *opaque, hwaddr offset, unsigned int size)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
> +    uint64_t r = 0;
> +
> +    switch (offset) {
> +    case SIFIVE_GPIO_REG_VALUE:
> +        r = s->value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_INPUT_EN:
> +        r = s->input_en;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_OUTPUT_EN:
> +        r = s->output_en;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_PORT:
> +        r = s->port;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_PUE:
> +        r = s->pue;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_DS:
> +        r = s->ds;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_RISE_IE:
> +        r = s->rise_ie;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_RISE_IP:
> +        r = s->rise_ip;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_FALL_IE:
> +        r = s->fall_ie;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_FALL_IP:
> +        r = s->fall_ip;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_HIGH_IE:
> +        r = s->high_ie;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_HIGH_IP:
> +        r = s->high_ip;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_LOW_IE:
> +        r = s->low_ie;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_LOW_IP:
> +        r = s->low_ip;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_IOF_EN:
> +        r = s->iof_en;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_IOF_SEL:
> +        r = s->iof_sel;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_OUT_XOR:
> +        r = s->out_xor;
> +        break;
> +
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                "%s: bad read offset 0x%" HWADDR_PRIx "\n",
> +                      __func__, offset);
> +    }
> +
> +    trace_sifive_gpio_read(offset, r);
> +
> +    return r;
> +}
> +
> +static void sifive_gpio_write(void *opaque, hwaddr offset,
> +                       uint64_t value, unsigned int size)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
> +
> +    trace_sifive_gpio_write(offset, value);
> +
> +    switch (offset) {
> +

No newline here as well

> +    case SIFIVE_GPIO_REG_INPUT_EN:
> +        s->input_en = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_OUTPUT_EN:
> +        s->output_en = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_PORT:
> +        s->port = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_PUE:
> +        s->pue = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_DS:
> +        s->ds = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_RISE_IE:
> +        s->rise_ie = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_RISE_IP:
> +         /* Write 1 to clear */
> +        s->rise_ip &= ~value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_FALL_IE:
> +        s->fall_ie = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_FALL_IP:
> +         /* Write 1 to clear */
> +        s->fall_ip &= ~value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_HIGH_IE:
> +        s->high_ie = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_HIGH_IP:
> +         /* Write 1 to clear */
> +        s->high_ip &= ~value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_LOW_IE:
> +        s->low_ie = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_LOW_IP:
> +         /* Write 1 to clear */
> +        s->low_ip &= ~value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_IOF_EN:
> +        s->iof_en = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_IOF_SEL:
> +        s->iof_sel = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_OUT_XOR:
> +        s->out_xor = value;
> +        break;

You don't have to hand write these all out most other devices use
arrays to simplfy this. There is also the register API, which is
pretty cool (hw/intc/xlnx-zynqmp-ipi.c).

Now that you have already done it this is fine.

> +
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: bad write offset 0x%" HWADDR_PRIx "\n",
> +                      __func__, offset);
> +    }
> +
> +    update_state(s);
> +}
> +
> +static const MemoryRegionOps gpio_ops = {
> +    .read =  sifive_gpio_read,
> +    .write = sifive_gpio_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .impl.min_access_size = 4,
> +    .impl.max_access_size = 4,
> +};
> +
> +static void sifive_gpio_set(void *opaque, int line, int value)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
> +
> +    trace_sifive_gpio_set(line, value);
> +
> +    assert(line >= 0 && line < SIFIVE_GPIO_PINS);

g_assert() instead of assert()

> +
> +    s->in_mask = deposit32(s->in_mask, line, 1, value >= 0);
> +    if (value >= 0) {
> +        s->in = deposit32(s->in, line, 1, value != 0);
> +    }
> +
> +    update_state(s);
> +}
> +
> +static void sifive_gpio_reset(DeviceState *dev)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(dev);
> +
> +    s->value = 0;
> +    s->input_en = 0;
> +    s->output_en = 0;
> +    s->port = 0;
> +    s->pue = 0;
> +    s->ds = 0;
> +    s->rise_ie = 0;
> +    s->rise_ip = 0;
> +    s->fall_ie = 0;
> +    s->fall_ip = 0;
> +    s->high_ie = 0;
> +    s->high_ip = 0;
> +    s->low_ie = 0;
> +    s->low_ip = 0;
> +    s->iof_en = 0;
> +    s->iof_sel = 0;
> +    s->out_xor = 0;
> +    s->in = 0;
> +    s->in_mask = 0;

Maybe a memset is better here?

> +
> +}
> +
> +static const VMStateDescription vmstate_sifive_gpio = {
> +    .name = TYPE_SIFIVE_GPIO,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(value,     SIFIVEGPIOState),
> +        VMSTATE_UINT32(input_en,  SIFIVEGPIOState),
> +        VMSTATE_UINT32(output_en, SIFIVEGPIOState),
> +        VMSTATE_UINT32(port,      SIFIVEGPIOState),
> +        VMSTATE_UINT32(pue,       SIFIVEGPIOState),
> +        VMSTATE_UINT32(rise_ie,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(rise_ip,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(fall_ie,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(fall_ip,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(high_ie,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(high_ip,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(low_ie,    SIFIVEGPIOState),
> +        VMSTATE_UINT32(low_ip,    SIFIVEGPIOState),
> +        VMSTATE_UINT32(iof_en,    SIFIVEGPIOState),
> +        VMSTATE_UINT32(iof_sel,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(out_xor,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(in, SIFIVEGPIOState),
> +        VMSTATE_UINT32(in_mask, SIFIVEGPIOState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void sifive_gpio_init(Object *obj)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(obj);
> +
> +    memory_region_init_io(&s->mmio, obj, &gpio_ops, s,
> +            TYPE_SIFIVE_GPIO, SIFIVE_GPIO_SIZE);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
> +
> +

Remove new line.

> +    for (int i = 0; i < SIFIVE_GPIO_PINS; i++) {
> +        sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[i]);
> +    }
> +
> +    qdev_init_gpio_in(DEVICE(s), sifive_gpio_set, SIFIVE_GPIO_PINS);
> +    qdev_init_gpio_out(DEVICE(s), s->output, SIFIVE_GPIO_PINS);
> +}
> +
> +static void sifive_gpio_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->vmsd = &vmstate_sifive_gpio;
> +    dc->reset = sifive_gpio_reset;
> +    dc->desc = "sifive GPIO";
> +}
> +
> +static const TypeInfo sifive_gpio_info = {
> +    .name = TYPE_SIFIVE_GPIO,
> +    .parent = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(SIFIVEGPIOState),
> +    .instance_init = sifive_gpio_init,
> +    .class_init = sifive_gpio_class_init
> +};
> +
> +static void sifive_gpio_register_types(void)
> +{
> +    type_register_static(&sifive_gpio_info);
> +}
> +
> +type_init(sifive_gpio_register_types)
> diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
> new file mode 100644
> index 0000000000..6d59233e23
> --- /dev/null
> +++ b/hw/riscv/trace-events
> @@ -0,0 +1,7 @@
> +# See docs/devel/tracing.txt for syntax documentation.
> +
> +# hw/gpio/sifive_gpio.c
> +sifive_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64
> +sifive_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
> +sifive_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
> +sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
> diff --git a/include/hw/riscv/sifive_e.h b/include/hw/riscv/sifive_e.h
> index 7b6d8aed96..6852bc36c6 100644
> --- a/include/hw/riscv/sifive_e.h
> +++ b/include/hw/riscv/sifive_e.h
> @@ -19,6 +19,8 @@
>  #ifndef HW_SIFIVE_E_H
>  #define HW_SIFIVE_E_H
>
> +#include "hw/riscv/sifive_gpio.h"
> +
>  #define TYPE_RISCV_E_SOC "riscv.sifive.e.soc"
>  #define RISCV_E_SOC(obj) \
>      OBJECT_CHECK(SiFiveESoCState, (obj), TYPE_RISCV_E_SOC)
> @@ -30,6 +32,7 @@ typedef struct SiFiveESoCState {
>      /*< public >*/
>      RISCVHartArrayState cpus;
>      DeviceState *plic;
> +    SIFIVEGPIOState gpio;
>  } SiFiveESoCState;
>
>  typedef struct SiFiveEState {
> @@ -63,8 +66,9 @@ enum {
>  };
>
>  enum {
> -    SIFIVE_E_UART0_IRQ = 3,
> -    SIFIVE_E_UART1_IRQ = 4
> +    SIFIVE_E_UART0_IRQ  = 3,

Why is this line changing?

Besides the few comments it looks good to me.

Alistair

> +    SIFIVE_E_UART1_IRQ  = 4,
> +    SIFIVE_E_GPIO0_IRQ0 = 8
>  };
>
>  #define SIFIVE_E_PLIC_HART_CONFIG "M"
> diff --git a/include/hw/riscv/sifive_gpio.h b/include/hw/riscv/sifive_gpio.h
> new file mode 100644
> index 0000000000..fce03d6c41
> --- /dev/null
> +++ b/include/hw/riscv/sifive_gpio.h
> @@ -0,0 +1,72 @@
> +/*
> + * sifive System-on-Chip general purpose input/output register definition
> + *
> + * Copyright 2019 AdaCore
> + *
> + * Base on nrf51_gpio.c:
> + *
> + * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
> + *
> + * This code is licensed under the GPL version 2 or later.  See
> + * the COPYING file in the top-level directory.
> + */
> +#ifndef SIFIVE_GPIO_H
> +#define SIFIVE_GPIO_H
> +
> +#include "hw/sysbus.h"
> +#define TYPE_SIFIVE_GPIO "sifive_soc.gpio"
> +#define SIFIVE_GPIO(obj) OBJECT_CHECK(SIFIVEGPIOState, (obj), TYPE_SIFIVE_GPIO)
> +
> +#define SIFIVE_GPIO_PINS 32
> +
> +#define SIFIVE_GPIO_SIZE 0x100
> +
> +#define SIFIVE_GPIO_REG_VALUE      0x000
> +#define SIFIVE_GPIO_REG_INPUT_EN   0x004
> +#define SIFIVE_GPIO_REG_OUTPUT_EN  0x008
> +#define SIFIVE_GPIO_REG_PORT       0x00C
> +#define SIFIVE_GPIO_REG_PUE        0x010
> +#define SIFIVE_GPIO_REG_DS         0x014
> +#define SIFIVE_GPIO_REG_RISE_IE    0x018
> +#define SIFIVE_GPIO_REG_RISE_IP    0x01C
> +#define SIFIVE_GPIO_REG_FALL_IE    0x020
> +#define SIFIVE_GPIO_REG_FALL_IP    0x024
> +#define SIFIVE_GPIO_REG_HIGH_IE    0x028
> +#define SIFIVE_GPIO_REG_HIGH_IP    0x02C
> +#define SIFIVE_GPIO_REG_LOW_IE     0x030
> +#define SIFIVE_GPIO_REG_LOW_IP     0x034
> +#define SIFIVE_GPIO_REG_IOF_EN     0x038
> +#define SIFIVE_GPIO_REG_IOF_SEL    0x03C
> +#define SIFIVE_GPIO_REG_OUT_XOR    0x040
> +
> +typedef struct SIFIVEGPIOState {
> +    SysBusDevice parent_obj;
> +
> +    MemoryRegion mmio;
> +
> +    qemu_irq irq[SIFIVE_GPIO_PINS];
> +    qemu_irq output[SIFIVE_GPIO_PINS];
> +
> +    uint32_t value;             /* Actual value of the pin */
> +    uint32_t input_en;
> +    uint32_t output_en;
> +    uint32_t port;              /* Pin value requested by the user */
> +    uint32_t pue;
> +    uint32_t ds;
> +    uint32_t rise_ie;
> +    uint32_t rise_ip;
> +    uint32_t fall_ie;
> +    uint32_t fall_ip;
> +    uint32_t high_ie;
> +    uint32_t high_ip;
> +    uint32_t low_ie;
> +    uint32_t low_ip;
> +    uint32_t iof_en;
> +    uint32_t iof_sel;
> +    uint32_t out_xor;
> +    uint32_t in;
> +    uint32_t in_mask;
> +
> +} SIFIVEGPIOState;
> +
> +#endif
> --
> 2.17.1
>
>
Fabien Chouteau Feb. 13, 2019, 4:49 p.m. UTC | #2
On 13/02/2019 01:06, Alistair Francis wrote:> Thanks for the patch!
You are welcome!

Let me know if there is something to improve.

Regads,
Peter Maydell Feb. 13, 2019, 5:14 p.m. UTC | #3
On Wed, 13 Feb 2019 at 00:13, Alistair Francis <alistair23@gmail.com> wrote:
> I know the other RISC-V files don't do it, but this should go in the
> hw/gpio directory instead of hw/riscv.

It might be nice to move those existing riscv devices into
their proper places at some point: should be a fairly easy
cleanup patch if somebody wants to take it on. (Advice
on what should live where available on request.)

thanks
-- PMM
Thomas Huth Feb. 13, 2019, 6:54 p.m. UTC | #4
On 2019-02-13 18:14, Peter Maydell wrote:
> On Wed, 13 Feb 2019 at 00:13, Alistair Francis <alistair23@gmail.com> wrote:
>> I know the other RISC-V files don't do it, but this should go in the
>> hw/gpio directory instead of hw/riscv.
> 
> It might be nice to move those existing riscv devices into
> their proper places at some point: should be a fairly easy
> cleanup patch if somebody wants to take it on. (Advice
> on what should live where available on request.)

If devices only work on riscv, they should IMHO stay in hw/riscv/. I
think hw/gpio/ and friends should primarily be used if a device is
shared between architectures. If they stay in hw/riscv/ it is way easier
to match the devices with wildcards in the MAINTAINERS file. Just my
0.02 € only, of course.

 Thomas
Palmer Dabbelt Feb. 13, 2019, 8:14 p.m. UTC | #5
On Wed, 13 Feb 2019 10:54:08 PST (-0800), thuth@redhat.com wrote:
> On 2019-02-13 18:14, Peter Maydell wrote:
>> On Wed, 13 Feb 2019 at 00:13, Alistair Francis <alistair23@gmail.com> wrote:
>>> I know the other RISC-V files don't do it, but this should go in the
>>> hw/gpio directory instead of hw/riscv.
>>
>> It might be nice to move those existing riscv devices into
>> their proper places at some point: should be a fairly easy
>> cleanup patch if somebody wants to take it on. (Advice
>> on what should live where available on request.)
>
> If devices only work on riscv, they should IMHO stay in hw/riscv/. I
> think hw/gpio/ and friends should primarily be used if a device is
> shared between architectures. If they stay in hw/riscv/ it is way easier
> to match the devices with wildcards in the MAINTAINERS file. Just my
> 0.02 € only, of course.

FWIW, I generally consider things like SiFive's GPIO controller to have nothing 
to do with RISC-V and therefor don't belong in hw/riscv.  That said, I don't 
really care that much so I'm happy with pretty much any source code 
organization that works for everyone else.  Let me know if we should move them 
around.
Peter Maydell Feb. 13, 2019, 9:35 p.m. UTC | #6
On Wed, 13 Feb 2019 at 18:54, Thomas Huth <thuth@redhat.com> wrote:
>
> On 2019-02-13 18:14, Peter Maydell wrote:
> > On Wed, 13 Feb 2019 at 00:13, Alistair Francis <alistair23@gmail.com> wrote:
> >> I know the other RISC-V files don't do it, but this should go in the
> >> hw/gpio directory instead of hw/riscv.
> >
> > It might be nice to move those existing riscv devices into
> > their proper places at some point: should be a fairly easy
> > cleanup patch if somebody wants to take it on. (Advice
> > on what should live where available on request.)
>
> If devices only work on riscv, they should IMHO stay in hw/riscv/. I
> think hw/gpio/ and friends should primarily be used if a device is
> shared between architectures. If they stay in hw/riscv/ it is way easier
> to match the devices with wildcards in the MAINTAINERS file. Just my
> 0.02 € only, of course.

This is not the way we generally arrange the source tree,
at least not the parts I'm familiar with. The rule of
thumb I use is that devices of type X go in hw/X, and
hw/$ARCH is only for board models and SoC container objects.
I think the model for this is the Linux kernel, which
splits drivers for devices into directories by device
family, rather than putting them all in arch/whatever.
A split that puts all the architecture-specific device
models in hw/$ARCH has the significant disadvantage that
it would put a huge number of files in hw/arm ...

(More generally, I don't think the exact way we split
the source files between directories matters much, but
I do think it helps if we try to be consistent about it.)

thanks
-- PMM
Alistair Francis Feb. 13, 2019, 10:35 p.m. UTC | #7
On Wed, Feb 13, 2019 at 1:36 PM Peter Maydell <peter.maydell@linaro.org> wrote:
>
> On Wed, 13 Feb 2019 at 18:54, Thomas Huth <thuth@redhat.com> wrote:
> >
> > On 2019-02-13 18:14, Peter Maydell wrote:
> > > On Wed, 13 Feb 2019 at 00:13, Alistair Francis <alistair23@gmail.com> wrote:
> > >> I know the other RISC-V files don't do it, but this should go in the
> > >> hw/gpio directory instead of hw/riscv.
> > >
> > > It might be nice to move those existing riscv devices into
> > > their proper places at some point: should be a fairly easy
> > > cleanup patch if somebody wants to take it on. (Advice
> > > on what should live where available on request.)
> >
> > If devices only work on riscv, they should IMHO stay in hw/riscv/. I
> > think hw/gpio/ and friends should primarily be used if a device is
> > shared between architectures. If they stay in hw/riscv/ it is way easier
> > to match the devices with wildcards in the MAINTAINERS file. Just my
> > 0.02 € only, of course.
>
> This is not the way we generally arrange the source tree,
> at least not the parts I'm familiar with. The rule of
> thumb I use is that devices of type X go in hw/X, and
> hw/$ARCH is only for board models and SoC container objects.
> I think the model for this is the Linux kernel, which
> splits drivers for devices into directories by device
> family, rather than putting them all in arch/whatever.
> A split that puts all the architecture-specific device
> models in hw/$ARCH has the significant disadvantage that
> it would put a huge number of files in hw/arm ...

That is how I understood it as well.

I think some of the current files in hw/riscv/ could be moved, but
they are somewhat border line and we don't have many files yet so I
wasn't bothering yet.

This GPIO device doesn't have to be RISC-V specific so it makes sense
to put it under hw/gpio. The maintainers file will need to be updated
though.

Alistair

>
> (More generally, I don't think the exact way we split
> the source files between directories matters much, but
> I do think it helps if we try to be consistent about it.)
>
> thanks
> -- PMM
Palmer Dabbelt March 26, 2019, 8:58 a.m. UTC | #8
On Tue, 12 Feb 2019 09:38:39 PST (-0800), chouteau@adacore.com wrote:
> QEMU model of the GPIO device on the SiFive E300 series SOCs.
>
> The pins are not used by a board definition yet, however this
> implementation can already be used to trigger GPIO interrupts from the
> software by configuring a pin as both output and input.

This has been at the top of my inbox for a while now.  There was some 
discussion on it, but it doesn't look like there was anything conclusive so I'm 
OK taking it in to hw/riscv for now and then dealing with it later.

Do you have anything that actually glues this to a machine so I can test it?

> Signed-off-by: Fabien Chouteau <chouteau@adacore.com>
> ---
>  Makefile.objs                  |   1 +
>  hw/riscv/Makefile.objs         |   1 +
>  hw/riscv/sifive_e.c            |  28 ++-
>  hw/riscv/sifive_gpio.c         | 388 +++++++++++++++++++++++++++++++++
>  hw/riscv/trace-events          |   7 +
>  include/hw/riscv/sifive_e.h    |   8 +-
>  include/hw/riscv/sifive_gpio.h |  72 ++++++
>  7 files changed, 501 insertions(+), 4 deletions(-)
>  create mode 100644 hw/riscv/sifive_gpio.c
>  create mode 100644 hw/riscv/trace-events
>  create mode 100644 include/hw/riscv/sifive_gpio.h
>
> diff --git a/Makefile.objs b/Makefile.objs
> index 67a054b08a..d40eb089ae 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -184,6 +184,7 @@ trace-events-subdirs += hw/virtio
>  trace-events-subdirs += hw/watchdog
>  trace-events-subdirs += hw/xen
>  trace-events-subdirs += hw/gpio
> +trace-events-subdirs += hw/riscv
>  trace-events-subdirs += io
>  trace-events-subdirs += linux-user
>  trace-events-subdirs += migration
> diff --git a/hw/riscv/Makefile.objs b/hw/riscv/Makefile.objs
> index 1dde01d39d..ced7935371 100644
> --- a/hw/riscv/Makefile.objs
> +++ b/hw/riscv/Makefile.objs
> @@ -7,5 +7,6 @@ obj-y += sifive_plic.o
>  obj-y += sifive_test.o
>  obj-y += sifive_u.o
>  obj-y += sifive_uart.o
> +obj-y += sifive_gpio.o
>  obj-y += spike.o
>  obj-y += virt.o
> diff --git a/hw/riscv/sifive_e.c b/hw/riscv/sifive_e.c
> index 5d9d65ff29..49c1dd986c 100644
> --- a/hw/riscv/sifive_e.c
> +++ b/hw/riscv/sifive_e.c
> @@ -146,11 +146,15 @@ static void riscv_sifive_e_soc_init(Object *obj)
>                              &error_abort);
>      object_property_set_int(OBJECT(&s->cpus), smp_cpus, "num-harts",
>                              &error_abort);
> +    sysbus_init_child_obj(obj, "riscv.sifive.e.gpio0",
> +                          &s->gpio, sizeof(s->gpio),
> +                          TYPE_SIFIVE_GPIO);
>  }
>
>  static void riscv_sifive_e_soc_realize(DeviceState *dev, Error **errp)
>  {
>      const struct MemmapEntry *memmap = sifive_e_memmap;
> +    Error *err = NULL;
>
>      SiFiveESoCState *s = RISCV_E_SOC(dev);
>      MemoryRegion *sys_mem = get_system_memory();
> @@ -184,8 +188,28 @@ static void riscv_sifive_e_soc_realize(DeviceState *dev, Error **errp)
>      sifive_mmio_emulate(sys_mem, "riscv.sifive.e.aon",
>          memmap[SIFIVE_E_AON].base, memmap[SIFIVE_E_AON].size);
>      sifive_prci_create(memmap[SIFIVE_E_PRCI].base);
> -    sifive_mmio_emulate(sys_mem, "riscv.sifive.e.gpio0",
> -        memmap[SIFIVE_E_GPIO0].base, memmap[SIFIVE_E_GPIO0].size);
> +
> +    /* GPIO */
> +
> +    object_property_set_bool(OBJECT(&s->gpio), true, "realized", &err);
> +    if (err) {
> +        error_propagate(errp, err);
> +        return;
> +    }
> +
> +    /* Map GPIO registers */
> +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio), 0, memmap[SIFIVE_E_GPIO0].base);
> +
> +    /* Pass all GPIOs to the SOC layer so they are available to the board */
> +    qdev_pass_gpios(DEVICE(&s->gpio), dev, NULL);
> +
> +    /* Connect GPIO interrupts to the PLIC */
> +    for (int i = 0; i < 32; i++) {
> +        sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio), i,
> +                           qdev_get_gpio_in(DEVICE(s->plic),
> +                                            SIFIVE_E_GPIO0_IRQ0 + i));
> +    }
> +
>      sifive_uart_create(sys_mem, memmap[SIFIVE_E_UART0].base,
>          serial_hd(0), qdev_get_gpio_in(DEVICE(s->plic), SIFIVE_E_UART0_IRQ));
>      sifive_mmio_emulate(sys_mem, "riscv.sifive.e.qspi0",
> diff --git a/hw/riscv/sifive_gpio.c b/hw/riscv/sifive_gpio.c
> new file mode 100644
> index 0000000000..06bd8112d7
> --- /dev/null
> +++ b/hw/riscv/sifive_gpio.c
> @@ -0,0 +1,388 @@
> +/*
> + * sifive System-on-Chip general purpose input/output register definition
> + *
> + * Copyright 2019 AdaCore
> + *
> + * Base on nrf51_gpio.c:
> + *
> + * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
> + *
> + * This code is licensed under the GPL version 2 or later.  See
> + * the COPYING file in the top-level directory.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "hw/riscv/sifive_gpio.h"
> +#include "trace.h"
> +
> +static void update_output_irq(SIFIVEGPIOState *s)
> +{
> +
> +    uint32_t pending;
> +    uint32_t pin;
> +
> +    pending = s->high_ip & s->high_ie;
> +    pending |= s->low_ip & s->low_ie;
> +    pending |= s->rise_ip & s->rise_ie;
> +    pending |= s->fall_ip & s->fall_ie;
> +
> +    for (int i = 0; i < SIFIVE_GPIO_PINS; i++) {
> +        pin = 1 << i;
> +        qemu_set_irq(s->irq[i], (pending & pin) != 0);
> +        trace_sifive_gpio_update_output_irq(i, (pending & pin) != 0);
> +    }
> +}
> +
> +static void update_state(SIFIVEGPIOState *s)
> +{
> +    size_t i;
> +    bool prev_ival, in, in_mask, port, out_xor, pull, output_en, input_en,
> +        rise_ip, fall_ip, low_ip, high_ip, oval, actual_value, ival;
> +
> +    for (i = 0; i < SIFIVE_GPIO_PINS; i++) {
> +
> +        prev_ival = extract32(s->value, i, 1);
> +        in        = extract32(s->in, i, 1);
> +        in_mask   = extract32(s->in_mask, i, 1);
> +        port      = extract32(s->port, i, 1);
> +        out_xor   = extract32(s->out_xor, i, 1);
> +        pull      = extract32(s->pue, i, 1);
> +        output_en = extract32(s->output_en, i, 1);
> +        input_en  = extract32(s->input_en, i, 1);
> +        rise_ip   = extract32(s->rise_ip, i, 1);
> +        fall_ip   = extract32(s->fall_ip, i, 1);
> +        low_ip    = extract32(s->low_ip, i, 1);
> +        high_ip   = extract32(s->high_ip, i, 1);
> +
> +        /* Output value (IOF not supported) */
> +        oval = output_en && (port ^ out_xor);
> +
> +        /* Pin both driven externally and internally */
> +        if (output_en && in_mask) {
> +            qemu_log_mask(LOG_GUEST_ERROR, "GPIO pin %zu short circuited\n", i);
> +        }
> +
> +        if (in_mask) {
> +            /* The pin is driven by external device */
> +            actual_value = in;
> +        } else if (output_en) {
> +            /* The pin is driven by internal circuit */
> +            actual_value = oval;
> +        } else {
> +            /* Floating? Apply pull-up resistor */
> +            actual_value = pull;
> +        }
> +
> +        qemu_set_irq(s->output[i], actual_value);
> +
> +        /* Input value */
> +        ival = input_en && actual_value;
> +
> +        /* Interrupts */
> +        high_ip = high_ip || ival;
> +        s->high_ip = deposit32(s->high_ip, i, 1, high_ip);
> +
> +        low_ip = low_ip || !ival;
> +        s->low_ip = deposit32(s->low_ip,  i, 1, low_ip);
> +
> +        rise_ip = rise_ip || (ival && !prev_ival);
> +        s->rise_ip = deposit32(s->rise_ip, i, 1, rise_ip);
> +
> +        fall_ip = fall_ip || (!ival && prev_ival);
> +        s->fall_ip = deposit32(s->fall_ip, i, 1, fall_ip);
> +
> +        /* Update value */
> +        s->value = deposit32(s->value, i, 1, ival);
> +    }
> +    update_output_irq(s);
> +}
> +
> +static uint64_t sifive_gpio_read(void *opaque, hwaddr offset, unsigned int size)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
> +    uint64_t r = 0;
> +
> +    switch (offset) {
> +    case SIFIVE_GPIO_REG_VALUE:
> +        r = s->value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_INPUT_EN:
> +        r = s->input_en;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_OUTPUT_EN:
> +        r = s->output_en;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_PORT:
> +        r = s->port;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_PUE:
> +        r = s->pue;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_DS:
> +        r = s->ds;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_RISE_IE:
> +        r = s->rise_ie;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_RISE_IP:
> +        r = s->rise_ip;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_FALL_IE:
> +        r = s->fall_ie;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_FALL_IP:
> +        r = s->fall_ip;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_HIGH_IE:
> +        r = s->high_ie;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_HIGH_IP:
> +        r = s->high_ip;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_LOW_IE:
> +        r = s->low_ie;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_LOW_IP:
> +        r = s->low_ip;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_IOF_EN:
> +        r = s->iof_en;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_IOF_SEL:
> +        r = s->iof_sel;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_OUT_XOR:
> +        r = s->out_xor;
> +        break;
> +
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                "%s: bad read offset 0x%" HWADDR_PRIx "\n",
> +                      __func__, offset);
> +    }
> +
> +    trace_sifive_gpio_read(offset, r);
> +
> +    return r;
> +}
> +
> +static void sifive_gpio_write(void *opaque, hwaddr offset,
> +                       uint64_t value, unsigned int size)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
> +
> +    trace_sifive_gpio_write(offset, value);
> +
> +    switch (offset) {
> +
> +    case SIFIVE_GPIO_REG_INPUT_EN:
> +        s->input_en = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_OUTPUT_EN:
> +        s->output_en = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_PORT:
> +        s->port = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_PUE:
> +        s->pue = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_DS:
> +        s->ds = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_RISE_IE:
> +        s->rise_ie = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_RISE_IP:
> +         /* Write 1 to clear */
> +        s->rise_ip &= ~value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_FALL_IE:
> +        s->fall_ie = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_FALL_IP:
> +         /* Write 1 to clear */
> +        s->fall_ip &= ~value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_HIGH_IE:
> +        s->high_ie = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_HIGH_IP:
> +         /* Write 1 to clear */
> +        s->high_ip &= ~value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_LOW_IE:
> +        s->low_ie = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_LOW_IP:
> +         /* Write 1 to clear */
> +        s->low_ip &= ~value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_IOF_EN:
> +        s->iof_en = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_IOF_SEL:
> +        s->iof_sel = value;
> +        break;
> +
> +    case SIFIVE_GPIO_REG_OUT_XOR:
> +        s->out_xor = value;
> +        break;
> +
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: bad write offset 0x%" HWADDR_PRIx "\n",
> +                      __func__, offset);
> +    }
> +
> +    update_state(s);
> +}
> +
> +static const MemoryRegionOps gpio_ops = {
> +    .read =  sifive_gpio_read,
> +    .write = sifive_gpio_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .impl.min_access_size = 4,
> +    .impl.max_access_size = 4,
> +};
> +
> +static void sifive_gpio_set(void *opaque, int line, int value)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
> +
> +    trace_sifive_gpio_set(line, value);
> +
> +    assert(line >= 0 && line < SIFIVE_GPIO_PINS);
> +
> +    s->in_mask = deposit32(s->in_mask, line, 1, value >= 0);
> +    if (value >= 0) {
> +        s->in = deposit32(s->in, line, 1, value != 0);
> +    }
> +
> +    update_state(s);
> +}
> +
> +static void sifive_gpio_reset(DeviceState *dev)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(dev);
> +
> +    s->value = 0;
> +    s->input_en = 0;
> +    s->output_en = 0;
> +    s->port = 0;
> +    s->pue = 0;
> +    s->ds = 0;
> +    s->rise_ie = 0;
> +    s->rise_ip = 0;
> +    s->fall_ie = 0;
> +    s->fall_ip = 0;
> +    s->high_ie = 0;
> +    s->high_ip = 0;
> +    s->low_ie = 0;
> +    s->low_ip = 0;
> +    s->iof_en = 0;
> +    s->iof_sel = 0;
> +    s->out_xor = 0;
> +    s->in = 0;
> +    s->in_mask = 0;
> +
> +}
> +
> +static const VMStateDescription vmstate_sifive_gpio = {
> +    .name = TYPE_SIFIVE_GPIO,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(value,     SIFIVEGPIOState),
> +        VMSTATE_UINT32(input_en,  SIFIVEGPIOState),
> +        VMSTATE_UINT32(output_en, SIFIVEGPIOState),
> +        VMSTATE_UINT32(port,      SIFIVEGPIOState),
> +        VMSTATE_UINT32(pue,       SIFIVEGPIOState),
> +        VMSTATE_UINT32(rise_ie,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(rise_ip,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(fall_ie,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(fall_ip,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(high_ie,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(high_ip,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(low_ie,    SIFIVEGPIOState),
> +        VMSTATE_UINT32(low_ip,    SIFIVEGPIOState),
> +        VMSTATE_UINT32(iof_en,    SIFIVEGPIOState),
> +        VMSTATE_UINT32(iof_sel,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(out_xor,   SIFIVEGPIOState),
> +        VMSTATE_UINT32(in, SIFIVEGPIOState),
> +        VMSTATE_UINT32(in_mask, SIFIVEGPIOState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void sifive_gpio_init(Object *obj)
> +{
> +    SIFIVEGPIOState *s = SIFIVE_GPIO(obj);
> +
> +    memory_region_init_io(&s->mmio, obj, &gpio_ops, s,
> +            TYPE_SIFIVE_GPIO, SIFIVE_GPIO_SIZE);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
> +
> +
> +    for (int i = 0; i < SIFIVE_GPIO_PINS; i++) {
> +        sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[i]);
> +    }
> +
> +    qdev_init_gpio_in(DEVICE(s), sifive_gpio_set, SIFIVE_GPIO_PINS);
> +    qdev_init_gpio_out(DEVICE(s), s->output, SIFIVE_GPIO_PINS);
> +}
> +
> +static void sifive_gpio_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->vmsd = &vmstate_sifive_gpio;
> +    dc->reset = sifive_gpio_reset;
> +    dc->desc = "sifive GPIO";
> +}
> +
> +static const TypeInfo sifive_gpio_info = {
> +    .name = TYPE_SIFIVE_GPIO,
> +    .parent = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(SIFIVEGPIOState),
> +    .instance_init = sifive_gpio_init,
> +    .class_init = sifive_gpio_class_init
> +};
> +
> +static void sifive_gpio_register_types(void)
> +{
> +    type_register_static(&sifive_gpio_info);
> +}
> +
> +type_init(sifive_gpio_register_types)
> diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
> new file mode 100644
> index 0000000000..6d59233e23
> --- /dev/null
> +++ b/hw/riscv/trace-events
> @@ -0,0 +1,7 @@
> +# See docs/devel/tracing.txt for syntax documentation.
> +
> +# hw/gpio/sifive_gpio.c
> +sifive_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64
> +sifive_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
> +sifive_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
> +sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
> diff --git a/include/hw/riscv/sifive_e.h b/include/hw/riscv/sifive_e.h
> index 7b6d8aed96..6852bc36c6 100644
> --- a/include/hw/riscv/sifive_e.h
> +++ b/include/hw/riscv/sifive_e.h
> @@ -19,6 +19,8 @@
>  #ifndef HW_SIFIVE_E_H
>  #define HW_SIFIVE_E_H
>
> +#include "hw/riscv/sifive_gpio.h"
> +
>  #define TYPE_RISCV_E_SOC "riscv.sifive.e.soc"
>  #define RISCV_E_SOC(obj) \
>      OBJECT_CHECK(SiFiveESoCState, (obj), TYPE_RISCV_E_SOC)
> @@ -30,6 +32,7 @@ typedef struct SiFiveESoCState {
>      /*< public >*/
>      RISCVHartArrayState cpus;
>      DeviceState *plic;
> +    SIFIVEGPIOState gpio;
>  } SiFiveESoCState;
>
>  typedef struct SiFiveEState {
> @@ -63,8 +66,9 @@ enum {
>  };
>
>  enum {
> -    SIFIVE_E_UART0_IRQ = 3,
> -    SIFIVE_E_UART1_IRQ = 4
> +    SIFIVE_E_UART0_IRQ  = 3,
> +    SIFIVE_E_UART1_IRQ  = 4,
> +    SIFIVE_E_GPIO0_IRQ0 = 8
>  };
>
>  #define SIFIVE_E_PLIC_HART_CONFIG "M"
> diff --git a/include/hw/riscv/sifive_gpio.h b/include/hw/riscv/sifive_gpio.h
> new file mode 100644
> index 0000000000..fce03d6c41
> --- /dev/null
> +++ b/include/hw/riscv/sifive_gpio.h
> @@ -0,0 +1,72 @@
> +/*
> + * sifive System-on-Chip general purpose input/output register definition
> + *
> + * Copyright 2019 AdaCore
> + *
> + * Base on nrf51_gpio.c:
> + *
> + * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
> + *
> + * This code is licensed under the GPL version 2 or later.  See
> + * the COPYING file in the top-level directory.
> + */
> +#ifndef SIFIVE_GPIO_H
> +#define SIFIVE_GPIO_H
> +
> +#include "hw/sysbus.h"
> +#define TYPE_SIFIVE_GPIO "sifive_soc.gpio"
> +#define SIFIVE_GPIO(obj) OBJECT_CHECK(SIFIVEGPIOState, (obj), TYPE_SIFIVE_GPIO)
> +
> +#define SIFIVE_GPIO_PINS 32
> +
> +#define SIFIVE_GPIO_SIZE 0x100
> +
> +#define SIFIVE_GPIO_REG_VALUE      0x000
> +#define SIFIVE_GPIO_REG_INPUT_EN   0x004
> +#define SIFIVE_GPIO_REG_OUTPUT_EN  0x008
> +#define SIFIVE_GPIO_REG_PORT       0x00C
> +#define SIFIVE_GPIO_REG_PUE        0x010
> +#define SIFIVE_GPIO_REG_DS         0x014
> +#define SIFIVE_GPIO_REG_RISE_IE    0x018
> +#define SIFIVE_GPIO_REG_RISE_IP    0x01C
> +#define SIFIVE_GPIO_REG_FALL_IE    0x020
> +#define SIFIVE_GPIO_REG_FALL_IP    0x024
> +#define SIFIVE_GPIO_REG_HIGH_IE    0x028
> +#define SIFIVE_GPIO_REG_HIGH_IP    0x02C
> +#define SIFIVE_GPIO_REG_LOW_IE     0x030
> +#define SIFIVE_GPIO_REG_LOW_IP     0x034
> +#define SIFIVE_GPIO_REG_IOF_EN     0x038
> +#define SIFIVE_GPIO_REG_IOF_SEL    0x03C
> +#define SIFIVE_GPIO_REG_OUT_XOR    0x040
> +
> +typedef struct SIFIVEGPIOState {
> +    SysBusDevice parent_obj;
> +
> +    MemoryRegion mmio;
> +
> +    qemu_irq irq[SIFIVE_GPIO_PINS];
> +    qemu_irq output[SIFIVE_GPIO_PINS];
> +
> +    uint32_t value;             /* Actual value of the pin */
> +    uint32_t input_en;
> +    uint32_t output_en;
> +    uint32_t port;              /* Pin value requested by the user */
> +    uint32_t pue;
> +    uint32_t ds;
> +    uint32_t rise_ie;
> +    uint32_t rise_ip;
> +    uint32_t fall_ie;
> +    uint32_t fall_ip;
> +    uint32_t high_ie;
> +    uint32_t high_ip;
> +    uint32_t low_ie;
> +    uint32_t low_ip;
> +    uint32_t iof_en;
> +    uint32_t iof_sel;
> +    uint32_t out_xor;
> +    uint32_t in;
> +    uint32_t in_mask;
> +
> +} SIFIVEGPIOState;
> +
> +#endif
Fabien Chouteau March 26, 2019, 5:49 p.m. UTC | #9
Hi Palmer,

On 26/03/2019 09:58, Palmer Dabbelt wrote:
> Do you have anything that actually glues this to a machine so I can test it?
> 

In this patch I do instantiate the device in sifive_e machine.

Regards,
Palmer Dabbelt March 27, 2019, 2:51 a.m. UTC | #10
On Tue, 26 Mar 2019 10:49:11 PDT (-0700), chouteau@adacore.com wrote:
> Hi Palmer,
>
> On 26/03/2019 09:58, Palmer Dabbelt wrote:
>> Do you have anything that actually glues this to a machine so I can test it?
>>
>
> In this patch I do instantiate the device in sifive_e machine.

OK, that's what I thought but I couldn't figure out how to actually talk to the 
device.  Hopefully it's just an issue on my end.
diff mbox series

Patch

diff --git a/Makefile.objs b/Makefile.objs
index 67a054b08a..d40eb089ae 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -184,6 +184,7 @@  trace-events-subdirs += hw/virtio
 trace-events-subdirs += hw/watchdog
 trace-events-subdirs += hw/xen
 trace-events-subdirs += hw/gpio
+trace-events-subdirs += hw/riscv
 trace-events-subdirs += io
 trace-events-subdirs += linux-user
 trace-events-subdirs += migration
diff --git a/hw/riscv/Makefile.objs b/hw/riscv/Makefile.objs
index 1dde01d39d..ced7935371 100644
--- a/hw/riscv/Makefile.objs
+++ b/hw/riscv/Makefile.objs
@@ -7,5 +7,6 @@  obj-y += sifive_plic.o
 obj-y += sifive_test.o
 obj-y += sifive_u.o
 obj-y += sifive_uart.o
+obj-y += sifive_gpio.o
 obj-y += spike.o
 obj-y += virt.o
diff --git a/hw/riscv/sifive_e.c b/hw/riscv/sifive_e.c
index 5d9d65ff29..49c1dd986c 100644
--- a/hw/riscv/sifive_e.c
+++ b/hw/riscv/sifive_e.c
@@ -146,11 +146,15 @@  static void riscv_sifive_e_soc_init(Object *obj)
                             &error_abort);
     object_property_set_int(OBJECT(&s->cpus), smp_cpus, "num-harts",
                             &error_abort);
+    sysbus_init_child_obj(obj, "riscv.sifive.e.gpio0",
+                          &s->gpio, sizeof(s->gpio),
+                          TYPE_SIFIVE_GPIO);
 }
 
 static void riscv_sifive_e_soc_realize(DeviceState *dev, Error **errp)
 {
     const struct MemmapEntry *memmap = sifive_e_memmap;
+    Error *err = NULL;
 
     SiFiveESoCState *s = RISCV_E_SOC(dev);
     MemoryRegion *sys_mem = get_system_memory();
@@ -184,8 +188,28 @@  static void riscv_sifive_e_soc_realize(DeviceState *dev, Error **errp)
     sifive_mmio_emulate(sys_mem, "riscv.sifive.e.aon",
         memmap[SIFIVE_E_AON].base, memmap[SIFIVE_E_AON].size);
     sifive_prci_create(memmap[SIFIVE_E_PRCI].base);
-    sifive_mmio_emulate(sys_mem, "riscv.sifive.e.gpio0",
-        memmap[SIFIVE_E_GPIO0].base, memmap[SIFIVE_E_GPIO0].size);
+
+    /* GPIO */
+
+    object_property_set_bool(OBJECT(&s->gpio), true, "realized", &err);
+    if (err) {
+        error_propagate(errp, err);
+        return;
+    }
+
+    /* Map GPIO registers */
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio), 0, memmap[SIFIVE_E_GPIO0].base);
+
+    /* Pass all GPIOs to the SOC layer so they are available to the board */
+    qdev_pass_gpios(DEVICE(&s->gpio), dev, NULL);
+
+    /* Connect GPIO interrupts to the PLIC */
+    for (int i = 0; i < 32; i++) {
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio), i,
+                           qdev_get_gpio_in(DEVICE(s->plic),
+                                            SIFIVE_E_GPIO0_IRQ0 + i));
+    }
+
     sifive_uart_create(sys_mem, memmap[SIFIVE_E_UART0].base,
         serial_hd(0), qdev_get_gpio_in(DEVICE(s->plic), SIFIVE_E_UART0_IRQ));
     sifive_mmio_emulate(sys_mem, "riscv.sifive.e.qspi0",
diff --git a/hw/riscv/sifive_gpio.c b/hw/riscv/sifive_gpio.c
new file mode 100644
index 0000000000..06bd8112d7
--- /dev/null
+++ b/hw/riscv/sifive_gpio.c
@@ -0,0 +1,388 @@ 
+/*
+ * sifive System-on-Chip general purpose input/output register definition
+ *
+ * Copyright 2019 AdaCore
+ *
+ * Base on nrf51_gpio.c:
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/riscv/sifive_gpio.h"
+#include "trace.h"
+
+static void update_output_irq(SIFIVEGPIOState *s)
+{
+
+    uint32_t pending;
+    uint32_t pin;
+
+    pending = s->high_ip & s->high_ie;
+    pending |= s->low_ip & s->low_ie;
+    pending |= s->rise_ip & s->rise_ie;
+    pending |= s->fall_ip & s->fall_ie;
+
+    for (int i = 0; i < SIFIVE_GPIO_PINS; i++) {
+        pin = 1 << i;
+        qemu_set_irq(s->irq[i], (pending & pin) != 0);
+        trace_sifive_gpio_update_output_irq(i, (pending & pin) != 0);
+    }
+}
+
+static void update_state(SIFIVEGPIOState *s)
+{
+    size_t i;
+    bool prev_ival, in, in_mask, port, out_xor, pull, output_en, input_en,
+        rise_ip, fall_ip, low_ip, high_ip, oval, actual_value, ival;
+
+    for (i = 0; i < SIFIVE_GPIO_PINS; i++) {
+
+        prev_ival = extract32(s->value, i, 1);
+        in        = extract32(s->in, i, 1);
+        in_mask   = extract32(s->in_mask, i, 1);
+        port      = extract32(s->port, i, 1);
+        out_xor   = extract32(s->out_xor, i, 1);
+        pull      = extract32(s->pue, i, 1);
+        output_en = extract32(s->output_en, i, 1);
+        input_en  = extract32(s->input_en, i, 1);
+        rise_ip   = extract32(s->rise_ip, i, 1);
+        fall_ip   = extract32(s->fall_ip, i, 1);
+        low_ip    = extract32(s->low_ip, i, 1);
+        high_ip   = extract32(s->high_ip, i, 1);
+
+        /* Output value (IOF not supported) */
+        oval = output_en && (port ^ out_xor);
+
+        /* Pin both driven externally and internally */
+        if (output_en && in_mask) {
+            qemu_log_mask(LOG_GUEST_ERROR, "GPIO pin %zu short circuited\n", i);
+        }
+
+        if (in_mask) {
+            /* The pin is driven by external device */
+            actual_value = in;
+        } else if (output_en) {
+            /* The pin is driven by internal circuit */
+            actual_value = oval;
+        } else {
+            /* Floating? Apply pull-up resistor */
+            actual_value = pull;
+        }
+
+        qemu_set_irq(s->output[i], actual_value);
+
+        /* Input value */
+        ival = input_en && actual_value;
+
+        /* Interrupts */
+        high_ip = high_ip || ival;
+        s->high_ip = deposit32(s->high_ip, i, 1, high_ip);
+
+        low_ip = low_ip || !ival;
+        s->low_ip = deposit32(s->low_ip,  i, 1, low_ip);
+
+        rise_ip = rise_ip || (ival && !prev_ival);
+        s->rise_ip = deposit32(s->rise_ip, i, 1, rise_ip);
+
+        fall_ip = fall_ip || (!ival && prev_ival);
+        s->fall_ip = deposit32(s->fall_ip, i, 1, fall_ip);
+
+        /* Update value */
+        s->value = deposit32(s->value, i, 1, ival);
+    }
+    update_output_irq(s);
+}
+
+static uint64_t sifive_gpio_read(void *opaque, hwaddr offset, unsigned int size)
+{
+    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
+    uint64_t r = 0;
+
+    switch (offset) {
+    case SIFIVE_GPIO_REG_VALUE:
+        r = s->value;
+        break;
+
+    case SIFIVE_GPIO_REG_INPUT_EN:
+        r = s->input_en;
+        break;
+
+    case SIFIVE_GPIO_REG_OUTPUT_EN:
+        r = s->output_en;
+        break;
+
+    case SIFIVE_GPIO_REG_PORT:
+        r = s->port;
+        break;
+
+    case SIFIVE_GPIO_REG_PUE:
+        r = s->pue;
+        break;
+
+    case SIFIVE_GPIO_REG_DS:
+        r = s->ds;
+        break;
+
+    case SIFIVE_GPIO_REG_RISE_IE:
+        r = s->rise_ie;
+        break;
+
+    case SIFIVE_GPIO_REG_RISE_IP:
+        r = s->rise_ip;
+        break;
+
+    case SIFIVE_GPIO_REG_FALL_IE:
+        r = s->fall_ie;
+        break;
+
+    case SIFIVE_GPIO_REG_FALL_IP:
+        r = s->fall_ip;
+        break;
+
+    case SIFIVE_GPIO_REG_HIGH_IE:
+        r = s->high_ie;
+        break;
+
+    case SIFIVE_GPIO_REG_HIGH_IP:
+        r = s->high_ip;
+        break;
+
+    case SIFIVE_GPIO_REG_LOW_IE:
+        r = s->low_ie;
+        break;
+
+    case SIFIVE_GPIO_REG_LOW_IP:
+        r = s->low_ip;
+        break;
+
+    case SIFIVE_GPIO_REG_IOF_EN:
+        r = s->iof_en;
+        break;
+
+    case SIFIVE_GPIO_REG_IOF_SEL:
+        r = s->iof_sel;
+        break;
+
+    case SIFIVE_GPIO_REG_OUT_XOR:
+        r = s->out_xor;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+    }
+
+    trace_sifive_gpio_read(offset, r);
+
+    return r;
+}
+
+static void sifive_gpio_write(void *opaque, hwaddr offset,
+                       uint64_t value, unsigned int size)
+{
+    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
+
+    trace_sifive_gpio_write(offset, value);
+
+    switch (offset) {
+
+    case SIFIVE_GPIO_REG_INPUT_EN:
+        s->input_en = value;
+        break;
+
+    case SIFIVE_GPIO_REG_OUTPUT_EN:
+        s->output_en = value;
+        break;
+
+    case SIFIVE_GPIO_REG_PORT:
+        s->port = value;
+        break;
+
+    case SIFIVE_GPIO_REG_PUE:
+        s->pue = value;
+        break;
+
+    case SIFIVE_GPIO_REG_DS:
+        s->ds = value;
+        break;
+
+    case SIFIVE_GPIO_REG_RISE_IE:
+        s->rise_ie = value;
+        break;
+
+    case SIFIVE_GPIO_REG_RISE_IP:
+         /* Write 1 to clear */
+        s->rise_ip &= ~value;
+        break;
+
+    case SIFIVE_GPIO_REG_FALL_IE:
+        s->fall_ie = value;
+        break;
+
+    case SIFIVE_GPIO_REG_FALL_IP:
+         /* Write 1 to clear */
+        s->fall_ip &= ~value;
+        break;
+
+    case SIFIVE_GPIO_REG_HIGH_IE:
+        s->high_ie = value;
+        break;
+
+    case SIFIVE_GPIO_REG_HIGH_IP:
+         /* Write 1 to clear */
+        s->high_ip &= ~value;
+        break;
+
+    case SIFIVE_GPIO_REG_LOW_IE:
+        s->low_ie = value;
+        break;
+
+    case SIFIVE_GPIO_REG_LOW_IP:
+         /* Write 1 to clear */
+        s->low_ip &= ~value;
+        break;
+
+    case SIFIVE_GPIO_REG_IOF_EN:
+        s->iof_en = value;
+        break;
+
+    case SIFIVE_GPIO_REG_IOF_SEL:
+        s->iof_sel = value;
+        break;
+
+    case SIFIVE_GPIO_REG_OUT_XOR:
+        s->out_xor = value;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+    }
+
+    update_state(s);
+}
+
+static const MemoryRegionOps gpio_ops = {
+    .read =  sifive_gpio_read,
+    .write = sifive_gpio_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+};
+
+static void sifive_gpio_set(void *opaque, int line, int value)
+{
+    SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
+
+    trace_sifive_gpio_set(line, value);
+
+    assert(line >= 0 && line < SIFIVE_GPIO_PINS);
+
+    s->in_mask = deposit32(s->in_mask, line, 1, value >= 0);
+    if (value >= 0) {
+        s->in = deposit32(s->in, line, 1, value != 0);
+    }
+
+    update_state(s);
+}
+
+static void sifive_gpio_reset(DeviceState *dev)
+{
+    SIFIVEGPIOState *s = SIFIVE_GPIO(dev);
+
+    s->value = 0;
+    s->input_en = 0;
+    s->output_en = 0;
+    s->port = 0;
+    s->pue = 0;
+    s->ds = 0;
+    s->rise_ie = 0;
+    s->rise_ip = 0;
+    s->fall_ie = 0;
+    s->fall_ip = 0;
+    s->high_ie = 0;
+    s->high_ip = 0;
+    s->low_ie = 0;
+    s->low_ip = 0;
+    s->iof_en = 0;
+    s->iof_sel = 0;
+    s->out_xor = 0;
+    s->in = 0;
+    s->in_mask = 0;
+
+}
+
+static const VMStateDescription vmstate_sifive_gpio = {
+    .name = TYPE_SIFIVE_GPIO,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(value,     SIFIVEGPIOState),
+        VMSTATE_UINT32(input_en,  SIFIVEGPIOState),
+        VMSTATE_UINT32(output_en, SIFIVEGPIOState),
+        VMSTATE_UINT32(port,      SIFIVEGPIOState),
+        VMSTATE_UINT32(pue,       SIFIVEGPIOState),
+        VMSTATE_UINT32(rise_ie,   SIFIVEGPIOState),
+        VMSTATE_UINT32(rise_ip,   SIFIVEGPIOState),
+        VMSTATE_UINT32(fall_ie,   SIFIVEGPIOState),
+        VMSTATE_UINT32(fall_ip,   SIFIVEGPIOState),
+        VMSTATE_UINT32(high_ie,   SIFIVEGPIOState),
+        VMSTATE_UINT32(high_ip,   SIFIVEGPIOState),
+        VMSTATE_UINT32(low_ie,    SIFIVEGPIOState),
+        VMSTATE_UINT32(low_ip,    SIFIVEGPIOState),
+        VMSTATE_UINT32(iof_en,    SIFIVEGPIOState),
+        VMSTATE_UINT32(iof_sel,   SIFIVEGPIOState),
+        VMSTATE_UINT32(out_xor,   SIFIVEGPIOState),
+        VMSTATE_UINT32(in, SIFIVEGPIOState),
+        VMSTATE_UINT32(in_mask, SIFIVEGPIOState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void sifive_gpio_init(Object *obj)
+{
+    SIFIVEGPIOState *s = SIFIVE_GPIO(obj);
+
+    memory_region_init_io(&s->mmio, obj, &gpio_ops, s,
+            TYPE_SIFIVE_GPIO, SIFIVE_GPIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+
+    for (int i = 0; i < SIFIVE_GPIO_PINS; i++) {
+        sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[i]);
+    }
+
+    qdev_init_gpio_in(DEVICE(s), sifive_gpio_set, SIFIVE_GPIO_PINS);
+    qdev_init_gpio_out(DEVICE(s), s->output, SIFIVE_GPIO_PINS);
+}
+
+static void sifive_gpio_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd = &vmstate_sifive_gpio;
+    dc->reset = sifive_gpio_reset;
+    dc->desc = "sifive GPIO";
+}
+
+static const TypeInfo sifive_gpio_info = {
+    .name = TYPE_SIFIVE_GPIO,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(SIFIVEGPIOState),
+    .instance_init = sifive_gpio_init,
+    .class_init = sifive_gpio_class_init
+};
+
+static void sifive_gpio_register_types(void)
+{
+    type_register_static(&sifive_gpio_info);
+}
+
+type_init(sifive_gpio_register_types)
diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
new file mode 100644
index 0000000000..6d59233e23
--- /dev/null
+++ b/hw/riscv/trace-events
@@ -0,0 +1,7 @@ 
+# See docs/devel/tracing.txt for syntax documentation.
+
+# hw/gpio/sifive_gpio.c
+sifive_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64
+sifive_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
+sifive_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
diff --git a/include/hw/riscv/sifive_e.h b/include/hw/riscv/sifive_e.h
index 7b6d8aed96..6852bc36c6 100644
--- a/include/hw/riscv/sifive_e.h
+++ b/include/hw/riscv/sifive_e.h
@@ -19,6 +19,8 @@ 
 #ifndef HW_SIFIVE_E_H
 #define HW_SIFIVE_E_H
 
+#include "hw/riscv/sifive_gpio.h"
+
 #define TYPE_RISCV_E_SOC "riscv.sifive.e.soc"
 #define RISCV_E_SOC(obj) \
     OBJECT_CHECK(SiFiveESoCState, (obj), TYPE_RISCV_E_SOC)
@@ -30,6 +32,7 @@  typedef struct SiFiveESoCState {
     /*< public >*/
     RISCVHartArrayState cpus;
     DeviceState *plic;
+    SIFIVEGPIOState gpio;
 } SiFiveESoCState;
 
 typedef struct SiFiveEState {
@@ -63,8 +66,9 @@  enum {
 };
 
 enum {
-    SIFIVE_E_UART0_IRQ = 3,
-    SIFIVE_E_UART1_IRQ = 4
+    SIFIVE_E_UART0_IRQ  = 3,
+    SIFIVE_E_UART1_IRQ  = 4,
+    SIFIVE_E_GPIO0_IRQ0 = 8
 };
 
 #define SIFIVE_E_PLIC_HART_CONFIG "M"
diff --git a/include/hw/riscv/sifive_gpio.h b/include/hw/riscv/sifive_gpio.h
new file mode 100644
index 0000000000..fce03d6c41
--- /dev/null
+++ b/include/hw/riscv/sifive_gpio.h
@@ -0,0 +1,72 @@ 
+/*
+ * sifive System-on-Chip general purpose input/output register definition
+ *
+ * Copyright 2019 AdaCore
+ *
+ * Base on nrf51_gpio.c:
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+#ifndef SIFIVE_GPIO_H
+#define SIFIVE_GPIO_H
+
+#include "hw/sysbus.h"
+#define TYPE_SIFIVE_GPIO "sifive_soc.gpio"
+#define SIFIVE_GPIO(obj) OBJECT_CHECK(SIFIVEGPIOState, (obj), TYPE_SIFIVE_GPIO)
+
+#define SIFIVE_GPIO_PINS 32
+
+#define SIFIVE_GPIO_SIZE 0x100
+
+#define SIFIVE_GPIO_REG_VALUE      0x000
+#define SIFIVE_GPIO_REG_INPUT_EN   0x004
+#define SIFIVE_GPIO_REG_OUTPUT_EN  0x008
+#define SIFIVE_GPIO_REG_PORT       0x00C
+#define SIFIVE_GPIO_REG_PUE        0x010
+#define SIFIVE_GPIO_REG_DS         0x014
+#define SIFIVE_GPIO_REG_RISE_IE    0x018
+#define SIFIVE_GPIO_REG_RISE_IP    0x01C
+#define SIFIVE_GPIO_REG_FALL_IE    0x020
+#define SIFIVE_GPIO_REG_FALL_IP    0x024
+#define SIFIVE_GPIO_REG_HIGH_IE    0x028
+#define SIFIVE_GPIO_REG_HIGH_IP    0x02C
+#define SIFIVE_GPIO_REG_LOW_IE     0x030
+#define SIFIVE_GPIO_REG_LOW_IP     0x034
+#define SIFIVE_GPIO_REG_IOF_EN     0x038
+#define SIFIVE_GPIO_REG_IOF_SEL    0x03C
+#define SIFIVE_GPIO_REG_OUT_XOR    0x040
+
+typedef struct SIFIVEGPIOState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+
+    qemu_irq irq[SIFIVE_GPIO_PINS];
+    qemu_irq output[SIFIVE_GPIO_PINS];
+
+    uint32_t value;             /* Actual value of the pin */
+    uint32_t input_en;
+    uint32_t output_en;
+    uint32_t port;              /* Pin value requested by the user */
+    uint32_t pue;
+    uint32_t ds;
+    uint32_t rise_ie;
+    uint32_t rise_ip;
+    uint32_t fall_ie;
+    uint32_t fall_ip;
+    uint32_t high_ie;
+    uint32_t high_ip;
+    uint32_t low_ie;
+    uint32_t low_ip;
+    uint32_t iof_en;
+    uint32_t iof_sel;
+    uint32_t out_xor;
+    uint32_t in;
+    uint32_t in_mask;
+
+} SIFIVEGPIOState;
+
+#endif