MSI cleanup and IRQ routing
[akaros.git] / kern / arch / x86 / msi.c
1 /* 
2  * This file is part of the UCB release of Plan 9. It is subject to the license
3  * terms in the LICENSE file found in the top-level directory of this
4  * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
5  * part of the UCB release of Plan 9, including this file, may be copied,
6  * modified, propagated, or distributed except according to the terms contained
7  * in the LICENSE file.
8  */
9
10 #include <vfs.h>
11 #include <kfs.h>
12 #include <slab.h>
13 #include <kmalloc.h>
14 #include <kref.h>
15 #include <string.h>
16 #include <stdio.h>
17 #include <assert.h>
18 #include <error.h>
19 #include <cpio.h>
20 #include <pmap.h>
21 #include <smp.h>
22 #include <ip.h>
23
24 enum {
25         Dpcicap         = 1<<0,
26         Dmsicap         = 1<<1,
27         Dvec            = 1<<2,
28         Debug           = 0,
29 };
30
31 enum {
32         /* MSI address format
33          *
34          * +31----------------------20+19----------12+11--------4+--3--+--2--+1---0+
35          * |       0xfee              | Dest APIC ID |  Reserved | RH  | DM  |  XX |
36          * +--------------------------+--------------+-----------+-----+-----+-----+
37          *
38          * RH: Redirection Hint
39          * DM: Destinatio Mode
40          * XX: Probably reserved, set to 0
41          */
42         Msiabase                = 0xfee00000u,
43         Msiadest                = 1<<12,                /* same as 63:56 of apic vector */
44         Msiaedest       = 1<<4,         /* same as 55:48 of apic vector */
45         Msialowpri      = 1<<3,         /* redirection hint */
46         Msialogical     = 1<<2,
47
48         /* MSI data format
49          * +63-------------------------------------------------------------------32+
50          * |                          Reserved                                     |
51          * +-------------------------------+-15-+-14-+--------+10----8+7----------0+
52          * |          Reserved             | TM | Lv | Reserv | Dmode |   Vector   |
53          * +-------------------------------+----+----+--------+-------+------------+
54          *
55          * Dmode: delivery mode (like APIC/LVT messages).  Usually 000 (Fixed).
56          * TM: Trigger mode (0 Edge, 1 Level)
57          * Lv: Level assert (0 Deassert, 1 Assert)
58          *
59          *
60          * for more info, check intel's SDMv3 (grep message signal) */
61         Msidlevel       = 1<<15,
62         Msidassert      = 1<<14,
63         Msidmode        = 1<<8,         /* 3 bits; delivery mode */
64         Msidvector      = 0xff<<0,
65 };
66
67 enum{
68         /* msi capabilities */
69         Vmask           = 1<<8, /* Vectors can be masked. Optional. */
70         Cap64           = 1<<7, /* 64-bit addresses. Optional. */
71         Mmesgmsk        = 7<<4, /* Mask for # of messages allowed. See 6.8.1.3 */
72         Mmcap           = 7<<1, /* # of messages the function can support. */
73         Msienable       = 1<<0, /* Enable. */
74         /* msix capabilities */
75         Msixenable      = 1<<15,
76         Msixmask        = 1<<15,
77         Msixsize        = 0x3ff,
78 };
79
80 /* Find the offset in config space of this function of the msi capability.
81  * It is defined in 6.8.1 and is variable-sized.  Returns 0 on failure. */
82 static int
83 msicap(struct pci_device *p)
84 {
85         return p->caps[PCI_CAP_ID_MSI];
86 }
87
88 /* Find the offset in config space of this function of the msi-x capability.
89  * It is defined in 6.8.1 and is variable-sized.
90  */
91 static int
92 msixcap(struct pci_device *p)
93 {
94         return p->caps[PCI_CAP_ID_MSIX];
95 }
96
97 static int
98 blacklist(struct pci_device *p)
99 {
100         switch(p->ven_id<<16 | p->dev_id){
101         case 0x11ab<<16 | 0x6485:
102         case 0x8086<<16 | 0x100f:
103                 return -1;
104         }
105         return 0;
106 }
107
108 /* see section 6.8.1 of the pci spec. */
109 /* Set up a single function on a single device.
110  * We need to take the vec, bust it up into bits,
111  * and put parts of it in the msi address and parts
112  * in the msi data.
113  */
114 int pci_msi_enable(struct pci_device *p, uint64_t vec)
115 {
116         char *s;
117         unsigned int c, f, dest, datao, lopri, dmode, logical;
118
119         /* Get the offset of the MSI capability
120          * in the function's config space.
121          */
122
123         c = msicap(p);
124         if(c == 0)
125                 return -1;
126
127         /* read it, clear out the Mmesgmsk bits. 
128          * This means that there will be no multiple
129          * messages enabled.
130          */
131         f = pcidev_read16(p, c + 2) & ~Mmesgmsk;
132
133         if (blacklist(p) != 0)
134                 return -1;
135
136         /* Data begins at 8 bytes in. */
137         datao = 8;
138
139         /* The destination is the traditional 8-bit APIC id is in 63:56 of the
140          * vector.  Later we may need to deal with extra destination bits
141          * (Msiaedest, in this code).  I haven't seen anything in the Intel SDM
142          * about using Msiaedest (the bits are reserved) */
143         dest = vec >> 56;
144
145         /* lopri is rarely set, and intel doesn't recommend using it.  with msi, the
146          * lopri field is actually a redirection hint, and also must be set when
147          * sending logical messages. */
148         lopri = (vec & 0x700) == MTlp;
149
150         logical = (vec & Lm) != 0;
151         if (logical)
152                 lopri = 1;
153
154         /* OK, Msiabase is fee00000, and we offset with the
155          * dest from above, lowpri, and logical.
156          */
157         p->msi_msg_addr_lo = Msiabase | Msiadest * dest | Msialowpri * lopri |
158                              Msialogical * logical;
159         printd("Write to %d %08lx \n",c + 4, p->msi_msg_addr_lo);
160         pcidev_write32(p, c + 4, p->msi_msg_addr_lo);
161
162         /* And even if it's 64-bit capable, we do nothing with
163          * the high order bits. If it is 64-bit we need to offset
164          * datao (data offset) by 4 (i.e. another 32 bits)
165          */
166         if(f & Cap64){
167                 datao += 4;
168                 pcidev_write32(p, c + 8, 0);
169         }
170         p->msi_msg_addr_hi = 0;
171
172         /* pick up the delivery mode from the vector */
173         dmode = (vec >> 8) & 7;
174
175         /* We can only specify the lower 16 bits of the MSI message, the rest gets
176          * forced to 0 by the device.  We're assuming edge triggered here. */
177         p->msi_msg_data = Msidmode * dmode | ((unsigned int)vec & 0xff);
178         printd("Write data %d %04x\n", c + datao, p->msi_msg_data);
179         pcidev_write16(p, c + datao, p->msi_msg_data);
180
181         /* If we have the option of masking the vectors,
182          * blow all the masks to 0. It's a 32-bit mask.
183          */
184         if(f & Vmask)
185                 pcidev_write32(p, c + datao + 4, 0);
186
187         /* Now write the control bits back, with the Mmesg mask (which is a power of
188          * 2) set to 0 (meaning one vector only).  Note we still haven't enabled
189          * MSI.  Will do that when we unmask. */
190         printd("write @ %d %04lx\n",c + 2, f);
191         pcidev_write16(p, c + 2, f);
192         return 0;
193 }
194
195 /* see section 6.8.1 of the pci spec. */
196 /* Set up a single function on a single device.
197  * We need to take the vec, bust it up into bits,
198  * and put parts of it in the msi address and parts
199  * in the msi data.
200  */
201 static int pci_msix_init(struct pci_device *p)
202 {
203         char *s;
204         unsigned int c, d, datao, lopri, dmode, logical;
205         uint16_t f;
206         int bars[2], found;
207
208         /* Get the offset of the MSI capability
209          * in the function's config space.
210          */
211         c = msixcap(p);
212         if(c == 0)
213                 return -1;
214
215         /* for this to work, we need at least one free BAR. */
216         found = pci_find_unused_bars(p, bars, 1);
217
218         /* we'll use one for now. */
219         if (found < 1)
220                 return -1;
221
222         f = pcidev_read16(p, c + 2);
223         printd("msix control %04x\n", f);
224         if (!(f & Msixenable)){
225                 printk("msix not enabled, f is 0x%x; done\n", f);
226                 return -1;
227         }
228
229         /* See if it's a broken device.
230          */
231         if(blacklist(p) != 0)
232                 return -1;
233
234         /* alloc 8 physically contiguous pages. */
235         p->msix = get_cont_pages(8, KMALLOC_WAIT);
236         if (! p->msix)
237                 return -1;
238         /* init them. */
239         memset(p->msix, 0, 8*PAGE_SIZE);
240         /* point the bar you found to them. */
241         p->msixbar = found;
242         p->msixsize = f & Msixsize;
243         /* what do we do for 64-bit bars? Who knows? */
244         /* there's an issue here. Does it need to be 8k aligned? Hmm. */
245         pcidev_write32(p, 0x10 + p->msixbar, paddr_low32(p->msix));
246         /* set the caps to point to the bar. */
247         /* Format is offset from the msibar | which bar it is. */
248         /* table is at offset 0. */
249         pcidev_write32(p, c + 4, found);
250         /* PBA is at offset 4096 */
251         pcidev_write32(p, c + 8, found | 4*PAGE_SIZE);
252         /* that seems to be it for the config space. */
253         return 0;
254 }
255
256 struct msixentry {
257         uint32_t addrlo, addrhi, data, vector;
258 };
259
260 int pci_msix_enable(struct pci_device *p, uint64_t vec)
261 {
262         int i;
263         struct msixentry *entry;
264         unsigned int c, d, datao, lopri, dmode, logical;
265         /* we don't call this much, so we don't mind
266          * retrying it.
267          */
268         if (! p->msixready) {
269                 if (pci_msix_init(p) < 0)
270                         return -1;
271                 p->msixready = 1;
272         }
273
274         /* find an unused slot. */
275         for(i = 0, entry = p->msix; i < p->msixsize; i++, entry++)
276                 if (! entry->vector)
277                         break;
278         if (i == p->msixsize)
279                 return -1;
280
281         /* The data we write is 16 bits, scarfed
282          * in the upper 16 bits of d.
283          */
284         /* The data we write is 16 bits, scarfed
285          * in the upper 16 bits of d.
286          */
287         d = vec>>48;
288
289         entry->data = d;
290
291         /* Hard to see it being anything but lopri but ... */
292         lopri = (vec & 0x700) == MTlp;
293
294         logical = (vec & Lm) != 0;
295
296         /* OK, Msiabase is fee00000, and we offset with the
297          * dest from above, lowpri, and logical.
298          */
299         printd("Write to %d %08lx \n",c + 4, Msiabase | Msiaedest * d
300                 | Msialowpri * lopri | Msialogical * logical);
301         entry->addrlo = Msiabase | Msiaedest * d
302                 | Msialowpri * lopri | Msialogical * logical;
303         
304         /* And even if it's 64-bit capable, we do nothing with
305          * the high order bits. If it is 64-bit we need to offset
306          * datao (data offset) by 4 (i.e. another 32 bits)
307          */
308         entry->addrhi = 0;
309
310         /* pick up the delivery mode from the vector */
311         dmode = (vec >> 8) & 7;
312
313         /* the data we write to that location is a combination
314          * of things. It's not yet clear if this is a plan 9 chosen
315          * thing or a PCI spec chosen thing.
316          */
317         printd("Write data @%p %04x\n", &entry->data, Msidassert |
318                Msidmode * dmode | ((unsigned int)vec & 0xff));
319         entry->data = Msidassert | Msidmode * dmode | ((unsigned int)vec & 0xff);
320         return 0;
321 }
322 /* Mask the msi function. Since 'masking' means disable it,
323  * but the parameter has a 1 for disabling it, well, it's a
324  * bit clear operation.
325  */
326 int
327 pcimsimask(struct pci_device *p, int mask)
328 {
329         unsigned int c, f;
330
331         c = msicap(p);
332         if(c == 0)
333                 return -1;
334         f = pcidev_read16(p, c + 2);
335         if(mask){
336                 pcidev_write16(p, c + 2, f & ~Msienable);
337         }else{
338                 pcidev_write16(p, c + 2, f | Msienable);
339         }
340         return 0;
341 }
342
343 /* TODO: should lock in all of these PCI/MSI functions */
344 void msi_mask_irq(struct irq_handler *irq_h, int apic_vector)
345 {
346         struct pci_device *p = (struct pci_device*)irq_h->dev_private;
347         unsigned int c, f;
348         c = msicap(p);
349         if (!c)
350                 return;
351
352         f = pcidev_read16(p, c + 2);
353         pcidev_write16(p, c + 2, f & ~Msienable);
354 }
355
356 void msi_unmask_irq(struct irq_handler *irq_h, int apic_vector)
357 {
358         struct pci_device *p = (struct pci_device*)irq_h->dev_private;
359         unsigned int c, f;
360         c = msicap(p);
361         assert(c);
362
363         f = pcidev_read16(p, c + 2);
364         pcidev_write16(p, c + 2, f | Msienable);
365 }
366
367 int msi_route_irq(struct irq_handler *irq_h, int apic_vector, int dest)
368 {
369         struct pci_device *p = (struct pci_device*)irq_h->dev_private;
370         unsigned int c, f;
371         c = msicap(p);
372         assert(c);
373
374         /* mask out the old destination, replace with new */
375         p->msi_msg_addr_lo &= ~(((1 << 8) - 1) << 12);
376         p->msi_msg_addr_lo |= dest << 12;
377         pcidev_write32(p, c + 4, p->msi_msg_addr_lo);
378         return 0;
379 }