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