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