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