x86: APIC cleanup
[akaros.git] / kern / arch / x86 / ioapic.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 #include <arch/io.h>
24 #include <acpi.h>
25 #include <trap.h>
26
27 /* Rbus chains, one for each device bus: each rbus matches a device to an rdt */
28 struct Rbus {
29         struct Rbus *next;
30         int devno;
31         struct Rdt *rdt;
32 };
33
34 /* Each rdt describes an ioapic input pin (intin, from the bus/device) */
35 struct Rdt {
36         struct apic *apic;
37         int intin;
38         uint32_t lo;                            /* matches the lo in the intin, incl Im */
39         uint32_t hi;                            /* matches the hi in the intin, incl routing */
40
41         int ref;                                        /* could map to multiple busses */
42         int enabled;                            /* times enabled */
43 };
44
45 enum {                                                  /* IOAPIC registers */
46         Ioregsel = 0x00,                        /* indirect register address */
47         Iowin = 0x10,   /* indirect register data */
48         Ioipa = 0x08,   /* IRQ Pin Assertion */
49         Ioeoi = 0x10,   /* EOI */
50
51         Ioapicid = 0x00,        /* Identification */
52         Ioapicver = 0x01,       /* Version */
53         Ioapicarb = 0x02,       /* Arbitration */
54         Ioabcfg = 0x03, /* Boot Coniguration */
55         Ioredtbl = 0x10,        /* Redirection Table */
56 };
57
58 static struct Rdt rdtarray[Nrdt];
59 static int nrdtarray;
60 static struct Rbus *rdtbus[Nbus];
61 /* reverse mapping of IDT vector to the RDT/IOAPIC entry triggering vector */
62 static struct Rdt *rdtvecno[IdtMAX + 1];
63
64 static spinlock_t idtnolock;
65 static int idtno = IdtIOAPIC;
66
67 struct apic xioapic[Napic];
68
69 static bool ioapic_exists(void)
70 {
71         /* not foolproof, if we called this before parsing */
72         return xioapic[0].useable ? TRUE : FALSE;
73 }
74
75 static void rtblget(struct apic *apic, int sel, uint32_t * hi, uint32_t * lo)
76 {
77         sel = Ioredtbl + 2 * sel;
78
79         write_mmreg32(apic->addr + Ioregsel, sel + 1);
80         *hi = read_mmreg32(apic->addr + Iowin);
81         write_mmreg32(apic->addr + Ioregsel, sel);
82         *lo = read_mmreg32(apic->addr + Iowin);
83 }
84
85 static void rtblput(struct apic *apic, int sel, uint32_t hi, uint32_t lo)
86 {
87         sel = Ioredtbl + 2 * sel;
88
89         write_mmreg32(apic->addr + Ioregsel, sel + 1);
90         write_mmreg32(apic->addr + Iowin, hi);
91         write_mmreg32(apic->addr + Ioregsel, sel);
92         write_mmreg32(apic->addr + Iowin, lo);
93 }
94
95 struct Rdt *rdtlookup(struct apic *apic, int intin)
96 {
97         int i;
98         struct Rdt *r;
99
100         for (i = 0; i < nrdtarray; i++) {
101                 r = rdtarray + i;
102                 if (apic == r->apic && intin == r->intin)
103                         return r;
104         }
105         return NULL;
106 }
107
108 struct Rdt *rbus_get_rdt(int busno, int devno)
109 {
110         struct Rbus *rbus;
111         for (rbus = rdtbus[busno]; rbus != NULL; rbus = rbus->next) {
112                 if (rbus->devno == devno)
113                         return rbus->rdt;
114         }
115         return 0;
116 }
117
118 /* builds RDT and Rbus entries, given the wiring of bus:dev to ioapicno:intin.
119  * - busno is the source bus
120  * - devno is the device number in the style of a PCI Interrupt Assignment
121  * Entry.  Which is the irq << 2 (check MP spec D.3).
122  * - ioapic is the ioapic the device is connected to
123  * - intin is the INTIN pin on the ioapic
124  * - lo is the lower part of the IOAPIC apic-message, which has the polarity and
125  * trigger mode flags. */
126 void ioapicintrinit(int busno, int ioapicno, int intin, int devno, int lo)
127 {
128         struct Rbus *rbus;
129         struct Rdt *rdt;
130         struct apic *ioapic;
131
132         if (busno >= Nbus || ioapicno >= Napic || nrdtarray >= Nrdt) {
133                 printk("Bad bus %d ioapic %d or nrdtarray %d too big\n", busno,
134                        ioapicno, nrdtarray);
135                 return;
136         }
137         ioapic = &xioapic[ioapicno];
138         if (!ioapic->useable || intin >= ioapic->nrdt) {
139                 printk("IOAPIC unusable (%d) or not enough nrdt (%d) for %d\n",
140                        ioapic->useable, ioapic->nrdt, intin);
141                 return;
142         }
143
144         rdt = rdtlookup(ioapic, intin);
145         if (rdt == NULL) {
146                 rdt = &rdtarray[nrdtarray++];
147                 rdt->apic = ioapic;
148                 rdt->intin = intin;
149                 rdt->lo = lo;
150                 rdt->hi = 0;
151         } else {
152                 if (lo != rdt->lo) {
153                         printk("multiple irq botch bus %d %d/%d/%d lo %d vs %d\n",
154                                    busno, ioapicno, intin, devno, lo, rdt->lo);
155                         return;
156                 }
157         }
158         rdt->ref++;
159         rbus = kzmalloc(sizeof *rbus, 0);
160         rbus->rdt = rdt;
161         rbus->devno = devno;
162         rbus->next = rdtbus[busno];
163         rdtbus[busno] = rbus;
164 }
165
166 static int map_polarity[4] = {
167         -1, IPhigh, -1, IPlow
168 };
169
170 static int map_edge_level[4] = {
171         -1, TMedge, -1, TMlevel
172 };
173
174 static int acpi_irq2ioapic(int irq)
175 {
176         int ioapic_idx = 0;
177         struct apic *ioapic;
178         /* with acpi, the ioapics map a global interrupt space.  each covers a
179          * window of the space from [ibase, ibase + nrdt). */
180         for (ioapic = xioapic; ioapic < &xioapic[Napic]; ioapic++, ioapic_idx++) {
181                 /* addr check is just for sanity */
182                 if (!ioapic->useable || !ioapic->addr)
183                         continue;
184                 if ((ioapic->ibase <= irq) && (irq < ioapic->ibase + ioapic->nrdt))
185                         return ioapic_idx;
186         }
187         return -1;
188 }
189
190 /* Build an RDT route, like we would have had from the MP tables had they been
191  * parsed, via ACPI.
192  *
193  * This only really deals with the ISA IRQs and maybe PCI ones that happen to
194  * have an override.  FWIW, on qemu the PCI NIC shows up as an ACPI intovr.
195  *
196  * From Brendan http://f.osdev.org/viewtopic.php?f=1&t=25951:
197  *
198  *              Before parsing the MADT you should begin by assuming that redirection
199  *              entries 0 to 15 are used for ISA IRQs 0 to 15. The MADT's "Interrupt
200  *              Source Override Structures" will tell you when this initial/default
201  *              assumption is wrong. For example, the MADT might tell you that ISA IRQ 9
202  *              is connected to IO APIC 44 and is level triggered; and (in this case)
203  *              it'd be silly to assume that ISA IRQ 9 is also connected to IO APIC
204  *              input 9 just because IO APIC input 9 is not listed.
205  *
206  *              For PCI IRQs, the MADT tells you nothing and you can't assume anything
207  *              at all. Sadly, you have to interpret the ACPI AML to determine how PCI
208  *              IRQs are connected to IO APIC inputs (or find some other work-around;
209  *              like implementing a motherboard driver for each different motherboard,
210  *              or some complex auto-detection scheme, or just configure PCI devices to
211  *              use MSI instead). */
212 static int acpi_make_rdt(int tbdf, int irq, int busno, int devno)
213 {
214         struct Apicst *st;
215         uint32_t lo;
216         int pol, edge_level, ioapic_nr, gsi_irq;
217
218         for (st = apics->st; st != NULL; st = st->next) {
219                 if (st->type == ASintovr) {
220                         if (st->intovr.irq == irq)
221                                 break;
222                 }
223         }
224         if (st) {
225                 pol = map_polarity[st->intovr.flags & AFpmask];
226                 if (pol < 0) {
227                         printk("ACPI override had bad polarity\n");
228                         return -1;
229                 }
230                 edge_level = map_edge_level[(st->intovr.flags & AFlevel) >> 2];
231                 if (edge_level < 0) {
232                         printk("ACPI override had bad edge/level\n");
233                         return -1;
234                 }
235                 lo = pol | edge_level;
236                 gsi_irq = st->intovr.intr;
237         } else {
238                 if (BUSTYPE(tbdf) == BusISA) {
239                         lo = IPhigh | TMedge;
240                         gsi_irq = irq;
241                 } else {
242                         /* Need to query ACPI at some point to handle this */
243                         printk("Non-ISA IRQ %d not found in MADT", irq);
244                         if (BUSTYPE(tbdf) != BusPCI) {
245                                 printk(", aborting...\n");
246                                 return -1;
247                         }
248                         /* Going to just guess some values for PCI */
249                         printk(", guessing...\n");
250                         lo = IPlow | TMlevel;
251                         gsi_irq = irq;
252                 }
253         }
254         ioapic_nr = acpi_irq2ioapic(gsi_irq);
255         if (ioapic_nr < 0) {
256                 printk("Could not find an IOAPIC for global irq %d!\n", gsi_irq);
257                 return -1;
258         }
259         ioapicintrinit(busno, ioapic_nr, gsi_irq - xioapic[ioapic_nr].ibase,
260                        devno, lo);
261         return 0;
262 }
263
264 void ioapicinit(int id, int ibase, uintptr_t pa)
265 {
266         struct apic *apic;
267         static int base;
268
269         assert((IOAPIC_PBASE <= pa) && (pa + PGSIZE <= IOAPIC_PBASE + APIC_SIZE));
270         /*
271          * Mark the IOAPIC useable if it has a good ID
272          * and the registers can be mapped.
273          */
274         if (id >= Napic)
275                 return;
276
277         apic = &xioapic[id];
278         apic->addr = IOAPIC_BASE + (pa - IOAPIC_PBASE);
279         if (apic->useable)
280                 return;
281         apic->useable = 1;
282         apic->paddr = pa;
283
284         /*
285          * Initialise the I/O APIC.
286          * The MultiProcessor Specification says it is the
287          * responsibility of the O/S to set the APIC ID.
288          */
289         spin_lock(&apic->lock);
290         write_mmreg32(apic->addr + Ioregsel, Ioapicver);
291         apic->nrdt = ((read_mmreg32(apic->addr + Iowin) >> 16) & 0xff) + 1;
292         /* the ibase is the global system interrupt base, told to us by ACPI.  if
293          * it's -1, we're called from mpparse, and just guess/make up our own
294          * assignments. */
295         if (ibase != -1)
296                 apic->ibase = ibase;
297         else {
298                 apic->ibase = base;
299                 base += apic->nrdt;
300         }
301         write_mmreg32(apic->addr + Ioregsel, Ioapicid);
302         write_mmreg32(apic->addr + Iowin, id << 24);
303         spin_unlock(&apic->lock);
304         printk("IOAPIC initialized at %p\n", apic->addr);
305 }
306
307 char *ioapicdump(char *start, char *end)
308 {
309         int i, n;
310         struct Rbus *rbus;
311         struct Rdt *rdt;
312         struct apic *apic;
313         uint32_t hi, lo;
314
315         if (!2)
316                 return start;
317         for (i = 0; i < Napic; i++) {
318                 apic = &xioapic[i];
319                 if (!apic->useable || apic->addr == 0)
320                         continue;
321                 start = seprintf(start, end, "ioapic %d addr %p nrdt %d ibase %d\n",
322                                                  i, apic->addr, apic->nrdt, apic->ibase);
323                 for (n = 0; n < apic->nrdt; n++) {
324                         spin_lock(&apic->lock);
325                         rtblget(apic, n, &hi, &lo);
326                         spin_unlock(&apic->lock);
327                         start = seprintf(start, end, " rdt %2.2d %p %p\n", n, hi, lo);
328                 }
329         }
330         for (i = 0; i < Nbus; i++) {
331                 if ((rbus = rdtbus[i]) == NULL)
332                         continue;
333                 start = seprintf(start, end, "iointr bus %d:\n", i);
334                 for (; rbus != NULL; rbus = rbus->next) {
335                         rdt = rbus->rdt;
336                         start = seprintf(start, end,
337                                                          " apic %ld devno %p(%d %d) intin %d hi %p lo %p\n",
338                                                          rdt->apic - xioapic, rbus->devno, rbus->devno >> 2,
339                                                          rbus->devno & 0x03, rdt->intin, rdt->hi, rdt->lo);
340                 }
341         }
342         return start;
343 }
344
345 /* Zeros and masks every redirect entry in every IOAPIC */
346 void ioapiconline(void)
347 {
348         int i;
349         struct apic *apic;
350
351         for (apic = xioapic; apic < &xioapic[Napic]; apic++) {
352                 if (!apic->useable || !apic->addr)
353                         continue;
354                 for (i = 0; i < apic->nrdt; i++) {
355                         spin_lock(&apic->lock);
356                         rtblput(apic, i, 0, Im);
357                         spin_unlock(&apic->lock);
358                 }
359         }
360 }
361
362 int nextvec(void)
363 {
364         unsigned int vecno;
365
366         /* TODO: half-way decent integer service (vmem) */
367         spin_lock(&idtnolock);
368         vecno = idtno;
369         idtno = (idtno + 1) % IdtMAX;
370         if (idtno < IdtIOAPIC)
371                 idtno += IdtIOAPIC;
372         spin_unlock(&idtnolock);
373
374         return vecno;
375 }
376
377 /* TODO: MSI work */
378 #if 0
379 static int msimask(struct Vkey *v, int mask)
380 {
381         Pcidev *p;
382
383         p = pcimatchtbdf(v->tbdf);
384         if (p == NULL)
385                 return -1;
386         return pcimsimask(p, mask);
387 }
388
389 static int intrenablemsi(struct vctl *v, Pcidev * p)
390 {
391         unsigned int vno, lo, hi;
392         uint64_t msivec;
393
394         vno = nextvec();
395
396         lo = IPlow | TMedge | vno;
397         ioapicintrdd(&hi, &lo);
398
399         if (lo & Lm)
400                 lo |= MTlp;
401
402         msivec = (uint64_t) hi << 32 | lo;
403         if (pcimsienable(p, msivec) == -1)
404                 return -1;
405         v->isr = apicisr;
406         v->eoi = apiceoi;
407         v->vno = vno;
408         v->type = "msi";
409         v->mask = msimask;
410
411         printk("msiirq: %T: enabling %.16llp %s irq %d vno %d\n", p->tbdf, msivec,
412                    v->name, v->irq, vno);
413         return vno;
414 }
415
416 int disablemsi(Vctl *, Pcidev * p)
417 {
418         if (p == NULL)
419                 return -1;
420         return pcimsimask(p, 1);
421 }
422 #endif
423
424 static struct Rdt *ioapic_vector2rdt(int apic_vector)
425 {
426         struct Rdt *rdt;
427         if (apic_vector < IdtIOAPIC || apic_vector > MaxIdtIOAPIC) {
428                 printk("ioapic vector %d out of range", apic_vector);
429                 return 0;
430         }
431         /* Fortunately rdtvecno[vecno] is static once assigned. o/w, we'll need some
432          * global sync for the callers, both for lookup and keeping rdt valid. */
433         rdt = rdtvecno[apic_vector];
434         if (!rdt) {
435                 printk("vector %d has no RDT! (did you enable it?)", apic_vector);
436                 return 0;
437         }
438         return rdt;
439 }
440
441 /* Routes the IRQ to the os_coreid.  Will take effect immediately.  Route
442  * masking from rdt->lo will take effect. */
443 static int ioapic_route_irq(int apic_vector, int os_coreid)
444 {
445         int hw_coreid;
446         struct Rdt *rdt = ioapic_vector2rdt(apic_vector);
447         if (!rdt)
448                 return -1;
449         if (os_coreid >= MAX_NUM_CPUS) {
450                 printk("os_coreid %d out of range!\n", os_coreid);
451                 return -1;
452         }
453         /* using the old akaros-style lapic id lookup */
454         hw_coreid = get_hw_coreid(os_coreid);
455         if (hw_coreid == -1) {
456                 printk("os_coreid %d not a valid hw core!", os_coreid);
457                 return -1;
458         }
459         spin_lock(&rdt->apic->lock);
460         /* this bit gets set in apicinit, only if we found it via MP or ACPI */
461         if (!xlapic[hw_coreid].useable) {
462                 printk("Can't route to uninitialized LAPIC %d!\n", hw_coreid);
463                 spin_unlock(&rdt->apic->lock);
464                 return -1;
465         }
466         rdt->hi = hw_coreid << 24;
467         rdt->lo |= Pm | MTf;
468         rtblput(rdt->apic, rdt->intin, rdt->hi, rdt->lo);
469         spin_unlock(&rdt->apic->lock);
470         return 0;
471 }
472
473 static void ioapic_mask_irq(int apic_vector)
474 {
475         struct Rdt *rdt = ioapic_vector2rdt(apic_vector);
476         if (!rdt)
477                 return;
478         spin_lock(&rdt->apic->lock);
479         /* don't allow shared vectors to be masked.  whatever. */
480         if (rdt->enabled > 1) {
481                 spin_unlock(&rdt->apic->lock);
482                 return;
483         }
484         rdt->lo |= Im;
485         rtblput(rdt->apic, rdt->intin, rdt->hi, rdt->lo);
486         spin_unlock(&rdt->apic->lock);
487 }
488
489 static void ioapic_unmask_irq(int apic_vector)
490 {
491         struct Rdt *rdt = ioapic_vector2rdt(apic_vector);
492         if (!rdt)
493                 return;
494         spin_lock(&rdt->apic->lock);
495         rdt->lo &= ~Im;
496         rtblput(rdt->apic, rdt->intin, rdt->hi, rdt->lo);
497         spin_unlock(&rdt->apic->lock);
498 }
499
500 /* Attempts to init a bus interrupt, initializes irq_h, and returns the IDT
501  * vector to use (-1 on error).  If routable, the IRQ will route to core 0.  The
502  * IRQ will be masked, if possible.  Call irq_h->unmask() when you're ready.
503  *
504  * This will determine the type of bus the device is on (LAPIC, IOAPIC, PIC,
505  * etc), and set the appropriate fields in isr_h.  If applicable, it'll also
506  * allocate an IDT vector, such as for an IOAPIC, and route the IOAPIC entries
507  * appropriately.
508  *
509  * Callers init irq_h->dev_irq and ->tbdf.  tbdf encodes the bus type and the
510  * classic PCI bus:dev:func.
511  *
512  * In plan9, this was ioapicintrenable(), which also unmasked.  We don't have a
513  * deinit/disable method that would tear down the route yet.  All the plan9 one
514  * did was dec enabled and mask the entry. */
515 int bus_irq_setup(struct irq_handler *irq_h)
516 {
517         struct Rbus *rbus;
518         struct Rdt *rdt;
519         int busno = 0, devno, vecno;
520         struct pci_device pcidev;
521
522         if (!ioapic_exists() && (BUSTYPE(irq_h->tbdf) != BusLAPIC)) {
523                 irq_h->check_spurious = pic_check_spurious;
524                 irq_h->eoi = pic_send_eoi;
525                 irq_h->mask = pic_mask_irq;
526                 irq_h->unmask = pic_unmask_irq;
527                 irq_h->route_irq = 0;
528                 irq_h->type = "pic";
529                 /* PIC devices have vector = irq + 32 */
530                 return irq_h->dev_irq + IdtPIC;
531         }
532         switch (BUSTYPE(irq_h->tbdf)) {
533                 case BusLAPIC:
534                         /* nxm used to set the initial 'isr' method (i think equiv to our
535                          * check_spurious) to apiceoi for non-spurious lapic vectors.  in
536                          * effect, i think they were sending the EOI early, and their eoi
537                          * method was 0.  we're not doing that (unless we have to). */
538                         irq_h->check_spurious = lapic_check_spurious;
539                         irq_h->eoi = lapic_send_eoi;
540                         irq_h->mask = lapic_mask_irq;
541                         irq_h->unmask = lapic_unmask_irq;
542                         irq_h->route_irq = 0;
543                         irq_h->type = "lapic";
544                         /* For the LAPIC, irq == vector */
545                         return irq_h->dev_irq;
546                 case BusIPI:
547                         /* similar to LAPIC, but we don't actually have LVT entries */
548                         irq_h->check_spurious = lapic_check_spurious;
549                         irq_h->eoi = lapic_send_eoi;
550                         irq_h->mask = 0;
551                         irq_h->unmask = 0;
552                         irq_h->route_irq = 0;
553                         irq_h->type = "IPI";
554                         return irq_h->dev_irq;
555                 case BusISA:
556                         if (mpisabusno == -1)
557                                 panic("No ISA bus allocated");
558                         busno = mpisabusno;
559                         /* need to track the irq in devno in PCI interrupt assignment entry
560                          * format (see mp.c or MP spec D.3). */
561                         devno = irq_h->dev_irq << 2;
562                         break;
563                 case BusPCI:
564                         /* TODO: we'll assume it's there.  (fix when adding MSI) */
565 #if 0
566                         Pcidev *pcidev;
567
568                         busno = BUSBNO(irq_h->tbdf);
569                         if ((pcidev = pcimatchtbdf(irq_h->tbdf)) == NULL)
570                                 panic("no PCI dev for tbdf %p", irq_h->tbdf);
571                         if ((vecno = intrenablemsi(irq_h, pcidev)) != -1)
572                                 return vecno;
573                         disablemsi(irq_h, pcidev);
574 #endif
575                         explode_tbdf(irq_h->tbdf);
576                         devno = pcidev_read8(&pcidev, PciINTP);
577
578                         if (devno == 0)
579                                 panic("no INTP for tbdf %p", irq_h->tbdf);
580                         /* remember, devno is the device shifted with irq pin in bits 0-1 */
581                         devno = BUSDNO(irq_h->tbdf) << 2 | (devno - 1);
582                         break;
583                 default:
584                         panic("Unknown bus type, TBDF %p", irq_h->tbdf);
585         }
586         /* busno and devno are set, regardless of the bustype, enough to find rdt.
587          * these may differ from the values in tbdf. */
588         rdt = rbus_get_rdt(busno, devno);
589         if (!rdt) {
590                 /* second chance.  if we didn't find the item the first time, then (if
591                  * it exists at all), it wasn't in the MP tables (or we had no tables).
592                  * So maybe we can figure it out via ACPI. */
593                 acpi_make_rdt(irq_h->tbdf, irq_h->dev_irq, busno, devno);
594                 rdt = rbus_get_rdt(busno, devno);
595         }
596         if (!rdt) {
597                 printk("Unable to build IOAPIC route for irq %d\n", irq_h->dev_irq);
598                 return -1;
599         }
600         /*
601          * what to do about devices that intrenable/intrdisable frequently?
602          * 1) there is no ioapicdisable yet;
603          * 2) it would be good to reuse freed vectors.
604          * Oh bugger.
605          * brho: plus the diff btw mask/unmask and enable/disable is unclear
606          */
607         /*
608          * This is a low-frequency event so just lock
609          * the whole IOAPIC to initialise the RDT entry
610          * rather than putting a Lock in each entry.
611          */
612         spin_lock(&rdt->apic->lock);
613         /* if a destination has already been picked, we store it in the lo.  this
614          * stays around regardless of enabled/disabled, since we don't reap vectors
615          * yet.  nor do we really mess with enabled... */
616         if ((rdt->lo & 0xff) == 0) {
617                 vecno = nextvec();
618                 rdt->lo |= vecno;
619                 rdtvecno[vecno] = rdt;
620         } else {
621                 printd("%p: mutiple irq bus %d dev %d\n", irq_h->tbdf, busno, devno);
622         }
623         rdt->enabled++;
624         rdt->hi = 0;                    /* route to 0 by default */
625         rdt->lo |= Pm | MTf;
626         rtblput(rdt->apic, rdt->intin, rdt->hi, rdt->lo);
627         vecno = rdt->lo & 0xff;
628         spin_unlock(&rdt->apic->lock);
629
630         irq_h->check_spurious = lapic_check_spurious;
631         irq_h->eoi = lapic_send_eoi;
632         irq_h->mask = ioapic_mask_irq;
633         irq_h->unmask = ioapic_unmask_irq;
634         irq_h->route_irq = ioapic_route_irq;
635         irq_h->type = "ioapic";
636
637         return vecno;
638 }