qemu/hw/pl190.c
<<
>>
Prefs
   1/*
   2 * Arm PrimeCell PL190 Vector Interrupt Controller
   3 *
   4 * Copyright (c) 2006 CodeSourcery.
   5 * Written by Paul Brook
   6 *
   7 * This code is licenced under the GPL.
   8 */
   9
  10#include "sysbus.h"
  11
  12/* The number of virtual priority levels.  16 user vectors plus the
  13   unvectored IRQ.  Chained interrupts would require an additional level
  14   if implemented.  */
  15
  16#define PL190_NUM_PRIO 17
  17
  18typedef struct {
  19    SysBusDevice busdev;
  20    uint32_t level;
  21    uint32_t soft_level;
  22    uint32_t irq_enable;
  23    uint32_t fiq_select;
  24    uint8_t vect_control[16];
  25    uint32_t vect_addr[PL190_NUM_PRIO];
  26    /* Mask containing interrupts with higher priority than this one.  */
  27    uint32_t prio_mask[PL190_NUM_PRIO + 1];
  28    int protected;
  29    /* Current priority level.  */
  30    int priority;
  31    int prev_prio[PL190_NUM_PRIO];
  32    qemu_irq irq;
  33    qemu_irq fiq;
  34} pl190_state;
  35
  36static const unsigned char pl190_id[] =
  37{ 0x90, 0x11, 0x04, 0x00, 0x0D, 0xf0, 0x05, 0xb1 };
  38
  39static inline uint32_t pl190_irq_level(pl190_state *s)
  40{
  41    return (s->level | s->soft_level) & s->irq_enable & ~s->fiq_select;
  42}
  43
  44/* Update interrupts.  */
  45static void pl190_update(pl190_state *s)
  46{
  47    uint32_t level = pl190_irq_level(s);
  48    int set;
  49
  50    set = (level & s->prio_mask[s->priority]) != 0;
  51    qemu_set_irq(s->irq, set);
  52    set = ((s->level | s->soft_level) & s->fiq_select) != 0;
  53    qemu_set_irq(s->fiq, set);
  54}
  55
  56static void pl190_set_irq(void *opaque, int irq, int level)
  57{
  58    pl190_state *s = (pl190_state *)opaque;
  59
  60    if (level)
  61        s->level |= 1u << irq;
  62    else
  63        s->level &= ~(1u << irq);
  64    pl190_update(s);
  65}
  66
  67static void pl190_update_vectors(pl190_state *s)
  68{
  69    uint32_t mask;
  70    int i;
  71    int n;
  72
  73    mask = 0;
  74    for (i = 0; i < 16; i++)
  75      {
  76        s->prio_mask[i] = mask;
  77        if (s->vect_control[i] & 0x20)
  78          {
  79            n = s->vect_control[i] & 0x1f;
  80            mask |= 1 << n;
  81          }
  82      }
  83    s->prio_mask[16] = mask;
  84    pl190_update(s);
  85}
  86
  87static uint32_t pl190_read(void *opaque, target_phys_addr_t offset)
  88{
  89    pl190_state *s = (pl190_state *)opaque;
  90    int i;
  91
  92    if (offset >= 0xfe0 && offset < 0x1000) {
  93        return pl190_id[(offset - 0xfe0) >> 2];
  94    }
  95    if (offset >= 0x100 && offset < 0x140) {
  96        return s->vect_addr[(offset - 0x100) >> 2];
  97    }
  98    if (offset >= 0x200 && offset < 0x240) {
  99        return s->vect_control[(offset - 0x200) >> 2];
 100    }
 101    switch (offset >> 2) {
 102    case 0: /* IRQSTATUS */
 103        return pl190_irq_level(s);
 104    case 1: /* FIQSATUS */
 105        return (s->level | s->soft_level) & s->fiq_select;
 106    case 2: /* RAWINTR */
 107        return s->level | s->soft_level;
 108    case 3: /* INTSELECT */
 109        return s->fiq_select;
 110    case 4: /* INTENABLE */
 111        return s->irq_enable;
 112    case 6: /* SOFTINT */
 113        return s->soft_level;
 114    case 8: /* PROTECTION */
 115        return s->protected;
 116    case 12: /* VECTADDR */
 117        /* Read vector address at the start of an ISR.  Increases the
 118           current priority level to that of the current interrupt.  */
 119        for (i = 0; i < s->priority; i++)
 120          {
 121            if ((s->level | s->soft_level) & s->prio_mask[i])
 122              break;
 123          }
 124        /* Reading this value with no pending interrupts is undefined.
 125           We return the default address.  */
 126        if (i == PL190_NUM_PRIO)
 127          return s->vect_addr[16];
 128        if (i < s->priority)
 129          {
 130            s->prev_prio[i] = s->priority;
 131            s->priority = i;
 132            pl190_update(s);
 133          }
 134        return s->vect_addr[s->priority];
 135    case 13: /* DEFVECTADDR */
 136        return s->vect_addr[16];
 137    default:
 138        hw_error("pl190_read: Bad offset %x\n", (int)offset);
 139        return 0;
 140    }
 141}
 142
 143static void pl190_write(void *opaque, target_phys_addr_t offset, uint32_t val)
 144{
 145    pl190_state *s = (pl190_state *)opaque;
 146
 147    if (offset >= 0x100 && offset < 0x140) {
 148        s->vect_addr[(offset - 0x100) >> 2] = val;
 149        pl190_update_vectors(s);
 150        return;
 151    }
 152    if (offset >= 0x200 && offset < 0x240) {
 153        s->vect_control[(offset - 0x200) >> 2] = val;
 154        pl190_update_vectors(s);
 155        return;
 156    }
 157    switch (offset >> 2) {
 158    case 0: /* SELECT */
 159        /* This is a readonly register, but linux tries to write to it
 160           anyway.  Ignore the write.  */
 161        break;
 162    case 3: /* INTSELECT */
 163        s->fiq_select = val;
 164        break;
 165    case 4: /* INTENABLE */
 166        s->irq_enable |= val;
 167        break;
 168    case 5: /* INTENCLEAR */
 169        s->irq_enable &= ~val;
 170        break;
 171    case 6: /* SOFTINT */
 172        s->soft_level |= val;
 173        break;
 174    case 7: /* SOFTINTCLEAR */
 175        s->soft_level &= ~val;
 176        break;
 177    case 8: /* PROTECTION */
 178        /* TODO: Protection (supervisor only access) is not implemented.  */
 179        s->protected = val & 1;
 180        break;
 181    case 12: /* VECTADDR */
 182        /* Restore the previous priority level.  The value written is
 183           ignored.  */
 184        if (s->priority < PL190_NUM_PRIO)
 185            s->priority = s->prev_prio[s->priority];
 186        break;
 187    case 13: /* DEFVECTADDR */
 188        s->vect_addr[16] = val;
 189        break;
 190    case 0xc0: /* ITCR */
 191        if (val) {
 192            hw_error("pl190: Test mode not implemented\n");
 193        }
 194        break;
 195    default:
 196        hw_error("pl190_write: Bad offset %x\n", (int)offset);
 197        return;
 198    }
 199    pl190_update(s);
 200}
 201
 202static CPUReadMemoryFunc * const pl190_readfn[] = {
 203   pl190_read,
 204   pl190_read,
 205   pl190_read
 206};
 207
 208static CPUWriteMemoryFunc * const pl190_writefn[] = {
 209   pl190_write,
 210   pl190_write,
 211   pl190_write
 212};
 213
 214static void pl190_reset(DeviceState *d)
 215{
 216  pl190_state *s = DO_UPCAST(pl190_state, busdev.qdev, d);
 217  int i;
 218
 219  for (i = 0; i < 16; i++)
 220    {
 221      s->vect_addr[i] = 0;
 222      s->vect_control[i] = 0;
 223    }
 224  s->vect_addr[16] = 0;
 225  s->prio_mask[17] = 0xffffffff;
 226  s->priority = PL190_NUM_PRIO;
 227  pl190_update_vectors(s);
 228}
 229
 230static int pl190_init(SysBusDevice *dev)
 231{
 232    pl190_state *s = FROM_SYSBUS(pl190_state, dev);
 233    int iomemtype;
 234
 235    iomemtype = cpu_register_io_memory(pl190_readfn,
 236                                       pl190_writefn, s,
 237                                       DEVICE_NATIVE_ENDIAN);
 238    sysbus_init_mmio(dev, 0x1000, iomemtype);
 239    qdev_init_gpio_in(&dev->qdev, pl190_set_irq, 32);
 240    sysbus_init_irq(dev, &s->irq);
 241    sysbus_init_irq(dev, &s->fiq);
 242    return 0;
 243}
 244
 245static const VMStateDescription vmstate_pl190 = {
 246    .name = "pl190",
 247    .version_id = 1,
 248    .minimum_version_id = 1,
 249    .fields = (VMStateField[]) {
 250        VMSTATE_UINT32(level, pl190_state),
 251        VMSTATE_UINT32(soft_level, pl190_state),
 252        VMSTATE_UINT32(irq_enable, pl190_state),
 253        VMSTATE_UINT32(fiq_select, pl190_state),
 254        VMSTATE_UINT8_ARRAY(vect_control, pl190_state, 16),
 255        VMSTATE_UINT32_ARRAY(vect_addr, pl190_state, PL190_NUM_PRIO),
 256        VMSTATE_UINT32_ARRAY(prio_mask, pl190_state, PL190_NUM_PRIO+1),
 257        VMSTATE_INT32(protected, pl190_state),
 258        VMSTATE_INT32(priority, pl190_state),
 259        VMSTATE_INT32_ARRAY(prev_prio, pl190_state, PL190_NUM_PRIO),
 260        VMSTATE_END_OF_LIST()
 261    }
 262};
 263
 264static SysBusDeviceInfo pl190_info = {
 265    .init = pl190_init,
 266    .qdev.name = "pl190",
 267    .qdev.size = sizeof(pl190_state),
 268    .qdev.vmsd = &vmstate_pl190,
 269    .qdev.reset = pl190_reset,
 270    .qdev.no_user = 1,
 271};
 272
 273static void pl190_register_devices(void)
 274{
 275    sysbus_register_withprop(&pl190_info);
 276}
 277
 278device_init(pl190_register_devices)
 279