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