aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/char/agp/Kconfig10
-rw-r--r--drivers/char/agp/Makefile1
-rw-r--r--drivers/char/agp/parisc-agp.c416
-rw-r--r--drivers/parisc/sba_iommu.c35
-rw-r--r--include/asm-parisc/agp.h25
-rw-r--r--include/asm-parisc/ropes.h12
6 files changed, 487 insertions, 12 deletions
diff --git a/drivers/char/agp/Kconfig b/drivers/char/agp/Kconfig
index 22f8cf218cc6..c603bf291580 100644
--- a/drivers/char/agp/Kconfig
+++ b/drivers/char/agp/Kconfig
@@ -1,6 +1,6 @@
1config AGP 1config AGP
2 tristate "/dev/agpgart (AGP Support)" 2 tristate "/dev/agpgart (AGP Support)"
3 depends on ALPHA || IA64 || PPC || X86 3 depends on ALPHA || IA64 || PARISC || PPC || X86
4 depends on PCI 4 depends on PCI
5 ---help--- 5 ---help---
6 AGP (Accelerated Graphics Port) is a bus system mainly used to 6 AGP (Accelerated Graphics Port) is a bus system mainly used to
@@ -122,6 +122,14 @@ config AGP_HP_ZX1
122 This option gives you AGP GART support for the HP ZX1 chipset 122 This option gives you AGP GART support for the HP ZX1 chipset
123 for IA64 processors. 123 for IA64 processors.
124 124
125config AGP_PARISC
126 tristate "HP Quicksilver AGP support"
127 depends on AGP && PARISC && 64BIT
128 help
129 This option gives you AGP GART support for the HP Quicksilver
130 AGP bus adapter on HP PA-RISC machines (Ok, just on the C8000
131 workstation...)
132
125config AGP_ALPHA_CORE 133config AGP_ALPHA_CORE
126 tristate "Alpha AGP support" 134 tristate "Alpha AGP support"
127 depends on AGP && (ALPHA_GENERIC || ALPHA_TITAN || ALPHA_MARVEL) 135 depends on AGP && (ALPHA_GENERIC || ALPHA_TITAN || ALPHA_MARVEL)
diff --git a/drivers/char/agp/Makefile b/drivers/char/agp/Makefile
index d33a22f2fa0b..3e581603d0a8 100644
--- a/drivers/char/agp/Makefile
+++ b/drivers/char/agp/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_AGP_AMD64) += amd64-agp.o
8obj-$(CONFIG_AGP_ALPHA_CORE) += alpha-agp.o 8obj-$(CONFIG_AGP_ALPHA_CORE) += alpha-agp.o
9obj-$(CONFIG_AGP_EFFICEON) += efficeon-agp.o 9obj-$(CONFIG_AGP_EFFICEON) += efficeon-agp.o
10obj-$(CONFIG_AGP_HP_ZX1) += hp-agp.o 10obj-$(CONFIG_AGP_HP_ZX1) += hp-agp.o
11obj-$(CONFIG_AGP_PARISC) += parisc-agp.o
11obj-$(CONFIG_AGP_I460) += i460-agp.o 12obj-$(CONFIG_AGP_I460) += i460-agp.o
12obj-$(CONFIG_AGP_INTEL) += intel-agp.o 13obj-$(CONFIG_AGP_INTEL) += intel-agp.o
13obj-$(CONFIG_AGP_NVIDIA) += nvidia-agp.o 14obj-$(CONFIG_AGP_NVIDIA) += nvidia-agp.o
diff --git a/drivers/char/agp/parisc-agp.c b/drivers/char/agp/parisc-agp.c
new file mode 100644
index 000000000000..17c50b0f83f0
--- /dev/null
+++ b/drivers/char/agp/parisc-agp.c
@@ -0,0 +1,416 @@
1/*
2 * HP Quicksilver AGP GART routines
3 *
4 * Copyright (c) 2006, Kyle McMartin <kyle@parisc-linux.org>
5 *
6 * Based on drivers/char/agpgart/hp-agp.c which is
7 * (c) Copyright 2002, 2003 Hewlett-Packard Development Company, L.P.
8 * Bjorn Helgaas <bjorn.helgaas@hp.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 *
14 */
15
16#include <linux/module.h>
17#include <linux/pci.h>
18#include <linux/init.h>
19#include <linux/klist.h>
20#include <linux/agp_backend.h>
21
22#include <asm-parisc/parisc-device.h>
23#include <asm-parisc/ropes.h>
24
25#include "agp.h"
26
27#define DRVNAME "quicksilver"
28#define DRVPFX DRVNAME ": "
29
30#ifndef log2
31#define log2(x) ffz(~(x))
32#endif
33
34#define AGP8X_MODE_BIT 3
35#define AGP8X_MODE (1 << AGP8X_MODE_BIT)
36
37static struct _parisc_agp_info {
38 void __iomem *ioc_regs;
39 void __iomem *lba_regs;
40
41 int lba_cap_offset;
42
43 u64 *gatt;
44 u64 gatt_entries;
45
46 u64 gart_base;
47 u64 gart_size;
48
49 int io_page_size;
50 int io_pages_per_kpage;
51} parisc_agp_info;
52
53static struct gatt_mask parisc_agp_masks[] =
54{
55 {
56 .mask = SBA_PDIR_VALID_BIT,
57 .type = 0
58 }
59};
60
61static struct aper_size_info_fixed parisc_agp_sizes[] =
62{
63 {0, 0, 0}, /* filled in by parisc_agp_fetch_size() */
64};
65
66static int
67parisc_agp_fetch_size(void)
68{
69 int size;
70
71 size = parisc_agp_info.gart_size / MB(1);
72 parisc_agp_sizes[0].size = size;
73 agp_bridge->current_size = (void *) &parisc_agp_sizes[0];
74
75 return size;
76}
77
78static int
79parisc_agp_configure(void)
80{
81 struct _parisc_agp_info *info = &parisc_agp_info;
82
83 agp_bridge->gart_bus_addr = info->gart_base;
84 agp_bridge->capndx = info->lba_cap_offset;
85 agp_bridge->mode = readl(info->lba_regs+info->lba_cap_offset+PCI_AGP_STATUS);
86
87 return 0;
88}
89
90static void
91parisc_agp_tlbflush(struct agp_memory *mem)
92{
93 struct _parisc_agp_info *info = &parisc_agp_info;
94
95 writeq(info->gart_base | log2(info->gart_size), info->ioc_regs+IOC_PCOM);
96 readq(info->ioc_regs+IOC_PCOM); /* flush */
97}
98
99static int
100parisc_agp_create_gatt_table(struct agp_bridge_data *bridge)
101{
102 struct _parisc_agp_info *info = &parisc_agp_info;
103 int i;
104
105 for (i = 0; i < info->gatt_entries; i++) {
106 info->gatt[i] = (unsigned long)agp_bridge->scratch_page;
107 }
108
109 return 0;
110}
111
112static int
113parisc_agp_free_gatt_table(struct agp_bridge_data *bridge)
114{
115 struct _parisc_agp_info *info = &parisc_agp_info;
116
117 info->gatt[0] = SBA_AGPGART_COOKIE;
118
119 return 0;
120}
121
122static int
123parisc_agp_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
124{
125 struct _parisc_agp_info *info = &parisc_agp_info;
126 int i, k;
127 off_t j, io_pg_start;
128 int io_pg_count;
129
130 if (type != 0 || mem->type != 0) {
131 return -EINVAL;
132 }
133
134 io_pg_start = info->io_pages_per_kpage * pg_start;
135 io_pg_count = info->io_pages_per_kpage * mem->page_count;
136 if ((io_pg_start + io_pg_count) > info->gatt_entries) {
137 return -EINVAL;
138 }
139
140 j = io_pg_start;
141 while (j < (io_pg_start + io_pg_count)) {
142 if (info->gatt[j])
143 return -EBUSY;
144 j++;
145 }
146
147 if (mem->is_flushed == FALSE) {
148 global_cache_flush();
149 mem->is_flushed = TRUE;
150 }
151
152 for (i = 0, j = io_pg_start; i < mem->page_count; i++) {
153 unsigned long paddr;
154
155 paddr = mem->memory[i];
156 for (k = 0;
157 k < info->io_pages_per_kpage;
158 k++, j++, paddr += info->io_page_size) {
159 info->gatt[j] =
160 agp_bridge->driver->mask_memory(agp_bridge,
161 paddr, type);
162 }
163 }
164
165 agp_bridge->driver->tlb_flush(mem);
166
167 return 0;
168}
169
170static int
171parisc_agp_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
172{
173 struct _parisc_agp_info *info = &parisc_agp_info;
174 int i, io_pg_start, io_pg_count;
175
176 if (type != 0 || mem->type != 0) {
177 return -EINVAL;
178 }
179
180 io_pg_start = info->io_pages_per_kpage * pg_start;
181 io_pg_count = info->io_pages_per_kpage * mem->page_count;
182 for (i = io_pg_start; i < io_pg_count + io_pg_start; i++) {
183 info->gatt[i] = agp_bridge->scratch_page;
184 }
185
186 agp_bridge->driver->tlb_flush(mem);
187 return 0;
188}
189
190static unsigned long
191parisc_agp_mask_memory(struct agp_bridge_data *bridge,
192 unsigned long addr, int type)
193{
194 return SBA_PDIR_VALID_BIT | addr;
195}
196
197static void
198parisc_agp_enable(struct agp_bridge_data *bridge, u32 mode)
199{
200 struct _parisc_agp_info *info = &parisc_agp_info;
201 u32 command;
202
203 command = readl(info->lba_regs + info->lba_cap_offset + PCI_AGP_STATUS);
204
205 command = agp_collect_device_status(bridge, mode, command);
206 command |= 0x00000100;
207
208 writel(command, info->lba_regs + info->lba_cap_offset + PCI_AGP_COMMAND);
209
210 agp_device_command(command, (mode & AGP8X_MODE) != 0);
211}
212
213struct agp_bridge_driver parisc_agp_driver = {
214 .owner = THIS_MODULE,
215 .size_type = FIXED_APER_SIZE,
216 .configure = parisc_agp_configure,
217 .fetch_size = parisc_agp_fetch_size,
218 .tlb_flush = parisc_agp_tlbflush,
219 .mask_memory = parisc_agp_mask_memory,
220 .masks = parisc_agp_masks,
221 .agp_enable = parisc_agp_enable,
222 .cache_flush = global_cache_flush,
223 .create_gatt_table = parisc_agp_create_gatt_table,
224 .free_gatt_table = parisc_agp_free_gatt_table,
225 .insert_memory = parisc_agp_insert_memory,
226 .remove_memory = parisc_agp_remove_memory,
227 .alloc_by_type = agp_generic_alloc_by_type,
228 .free_by_type = agp_generic_free_by_type,
229 .agp_alloc_page = agp_generic_alloc_page,
230 .agp_destroy_page = agp_generic_destroy_page,
231 .cant_use_aperture = 1,
232};
233
234static int __init
235agp_ioc_init(void __iomem *ioc_regs)
236{
237 struct _parisc_agp_info *info = &parisc_agp_info;
238 u64 *iova_base, *io_pdir, io_tlb_ps;
239 int io_tlb_shift;
240
241 printk(KERN_INFO DRVPFX "IO PDIR shared with sba_iommu\n");
242
243 info->ioc_regs = ioc_regs;
244
245 io_tlb_ps = readq(info->ioc_regs+IOC_TCNFG);
246 switch (io_tlb_ps) {
247 case 0: io_tlb_shift = 12; break;
248 case 1: io_tlb_shift = 13; break;
249 case 2: io_tlb_shift = 14; break;
250 case 3: io_tlb_shift = 16; break;
251 default:
252 printk(KERN_ERR DRVPFX "Invalid IOTLB page size "
253 "configuration 0x%llx\n", io_tlb_ps);
254 info->gatt = NULL;
255 info->gatt_entries = 0;
256 return -ENODEV;
257 }
258 info->io_page_size = 1 << io_tlb_shift;
259 info->io_pages_per_kpage = PAGE_SIZE / info->io_page_size;
260
261 iova_base = readq(info->ioc_regs+IOC_IBASE) & ~0x1;
262 info->gart_base = iova_base + PLUTO_IOVA_SIZE - PLUTO_GART_SIZE;
263
264 info->gart_size = PLUTO_GART_SIZE;
265 info->gatt_entries = info->gart_size / info->io_page_size;
266
267 io_pdir = phys_to_virt(readq(info->ioc_regs+IOC_PDIR_BASE));
268 info->gatt = &io_pdir[(PLUTO_IOVA_SIZE/2) >> PAGE_SHIFT];
269
270 if (info->gatt[0] != SBA_AGPGART_COOKIE) {
271 info->gatt = NULL;
272 info->gatt_entries = 0;
273 printk(KERN_ERR DRVPFX "No reserved IO PDIR entry found; "
274 "GART disabled\n");
275 return -ENODEV;
276 }
277
278 return 0;
279}
280
281static int
282lba_find_capability(int cap)
283{
284 struct _parisc_agp_info *info = &parisc_agp_info;
285 u16 status;
286 u8 pos, id;
287 int ttl = 48;
288
289 status = readw(info->lba_regs + PCI_STATUS);
290 if (!(status & PCI_STATUS_CAP_LIST))
291 return 0;
292 pos = readb(info->lba_regs + PCI_CAPABILITY_LIST);
293 while (ttl-- && pos >= 0x40) {
294 pos &= ~3;
295 id = readb(info->lba_regs + pos + PCI_CAP_LIST_ID);
296 if (id == 0xff)
297 break;
298 if (id == cap)
299 return pos;
300 pos = readb(info->lba_regs + pos + PCI_CAP_LIST_NEXT);
301 }
302 return 0;
303}
304
305static int __init
306agp_lba_init(void __iomem *lba_hpa)
307{
308 struct _parisc_agp_info *info = &parisc_agp_info;
309 int cap;
310
311 info->lba_regs = lba_hpa;
312 info->lba_cap_offset = lba_find_capability(PCI_CAP_ID_AGP);
313
314 cap = readl(lba_hpa + info->lba_cap_offset) & 0xff;
315 if (cap != PCI_CAP_ID_AGP) {
316 printk(KERN_ERR DRVPFX "Invalid capability ID 0x%02x at 0x%x\n",
317 cap, info->lba_cap_offset);
318 return -ENODEV;
319 }
320
321 return 0;
322}
323
324static int __init
325parisc_agp_setup(void __iomem *ioc_hpa, void __iomem *lba_hpa)
326{
327 struct pci_dev *fake_bridge_dev = NULL;
328 struct agp_bridge_data *bridge;
329 int error = 0;
330
331 fake_bridge_dev = kmalloc(sizeof (struct pci_dev), GFP_KERNEL);
332 if (!fake_bridge_dev) {
333 error = -ENOMEM;
334 goto fail;
335 }
336
337 error = agp_ioc_init(ioc_hpa);
338 if (error)
339 goto fail;
340
341 error = agp_lba_init(lba_hpa);
342 if (error)
343 goto fail;
344
345 bridge = agp_alloc_bridge();
346 if (!bridge) {
347 error = -ENOMEM;
348 goto fail;
349 }
350 bridge->driver = &parisc_agp_driver;
351
352 fake_bridge_dev->vendor = PCI_VENDOR_ID_HP;
353 fake_bridge_dev->device = PCI_DEVICE_ID_HP_PCIX_LBA;
354 bridge->dev = fake_bridge_dev;
355
356 error = agp_add_bridge(bridge);
357
358fail:
359 return error;
360}
361
362static struct device *next_device(struct klist_iter *i) {
363 struct klist_node * n = klist_next(i);
364 return n ? container_of(n, struct device, knode_parent) : NULL;
365}
366
367static int
368parisc_agp_init(void)
369{
370 extern struct sba_device *sba_list;
371
372 int err = -1;
373 struct parisc_device *sba = NULL, *lba = NULL;
374 struct lba_device *lbadev = NULL;
375 struct device *dev = NULL;
376 struct klist_iter i;
377
378 if (!sba_list)
379 goto out;
380
381 /* Find our parent Pluto */
382 sba = sba_list->dev;
383 if (!IS_PLUTO(sba)) {
384 printk(KERN_INFO DRVPFX "No Pluto found, so no AGPGART for you.\n");
385 goto out;
386 }
387
388 /* Now search our Pluto for our precious AGP device... */
389 klist_iter_init(&sba->dev.klist_children, &i);
390 while ((dev = next_device(&i))) {
391 struct parisc_device *padev = to_parisc_device(dev);
392 if (IS_QUICKSILVER(padev))
393 lba = padev;
394 }
395 klist_iter_exit(&i);
396
397 if (!lba) {
398 printk(KERN_INFO DRVPFX "No AGP devices found.\n");
399 goto out;
400 }
401
402 lbadev = parisc_get_drvdata(lba);
403
404 /* w00t, let's go find our cookies... */
405 parisc_agp_setup(sba_list->ioc[0].ioc_hpa, lbadev->hba.base_addr);
406
407 return 0;
408
409out:
410 return err;
411}
412
413module_init(parisc_agp_init);
414
415MODULE_AUTHOR("Kyle McMartin <kyle@parisc-linux.org>");
416MODULE_LICENSE("GPL");
diff --git a/drivers/parisc/sba_iommu.c b/drivers/parisc/sba_iommu.c
index eed99bdcfb6b..294c1117098d 100644
--- a/drivers/parisc/sba_iommu.c
+++ b/drivers/parisc/sba_iommu.c
@@ -89,7 +89,8 @@
89 89
90#define DEFAULT_DMA_HINT_REG 0 90#define DEFAULT_DMA_HINT_REG 0
91 91
92static struct sba_device *sba_list; 92struct sba_device *sba_list;
93EXPORT_SYMBOL_GPL(sba_list);
93 94
94static unsigned long ioc_needs_fdc = 0; 95static unsigned long ioc_needs_fdc = 0;
95 96
@@ -102,8 +103,14 @@ static unsigned long piranha_bad_128k = 0;
102/* Looks nice and keeps the compiler happy */ 103/* Looks nice and keeps the compiler happy */
103#define SBA_DEV(d) ((struct sba_device *) (d)) 104#define SBA_DEV(d) ((struct sba_device *) (d))
104 105
106#ifdef CONFIG_AGP_PARISC
107#define SBA_AGP_SUPPORT
108#endif /*CONFIG_AGP_PARISC*/
109
105#ifdef SBA_AGP_SUPPORT 110#ifdef SBA_AGP_SUPPORT
106static int reserve_sba_gart = 1; 111static int sba_reserve_agpgart = 1;
112module_param(sba_reserve_agpgart, int, 1);
113MODULE_PARM_DESC(sba_reserve_agpgart, "Reserve half of IO pdir as AGPGART");
107#endif 114#endif
108 115
109#define ROUNDUP(x,y) ((x + ((y)-1)) & ~((y)-1)) 116#define ROUNDUP(x,y) ((x + ((y)-1)) & ~((y)-1))
@@ -1300,6 +1307,10 @@ sba_ioc_init_pluto(struct parisc_device *sba, struct ioc *ioc, int ioc_num)
1300 WRITE_REG(ioc->ibase | 31, ioc->ioc_hpa + IOC_PCOM); 1307 WRITE_REG(ioc->ibase | 31, ioc->ioc_hpa + IOC_PCOM);
1301 1308
1302#ifdef SBA_AGP_SUPPORT 1309#ifdef SBA_AGP_SUPPORT
1310{
1311 struct klist_iter i;
1312 struct device *dev = NULL;
1313
1303 /* 1314 /*
1304 ** If an AGP device is present, only use half of the IOV space 1315 ** If an AGP device is present, only use half of the IOV space
1305 ** for PCI DMA. Unfortunately we can't know ahead of time 1316 ** for PCI DMA. Unfortunately we can't know ahead of time
@@ -1308,20 +1319,22 @@ sba_ioc_init_pluto(struct parisc_device *sba, struct ioc *ioc, int ioc_num)
1308 ** We program the next pdir index after we stop w/ a key for 1319 ** We program the next pdir index after we stop w/ a key for
1309 ** the GART code to handshake on. 1320 ** the GART code to handshake on.
1310 */ 1321 */
1311 device=NULL; 1322 klist_iter_init(&sba->dev.klist_children, &i);
1312 for (lba = sba->child; lba; lba = lba->sibling) { 1323 while (dev = next_device(&i)) {
1324 struct parisc_device *lba = to_parisc_device(dev);
1313 if (IS_QUICKSILVER(lba)) 1325 if (IS_QUICKSILVER(lba))
1314 break; 1326 agp_found = 1;
1315 } 1327 }
1328 klist_iter_exit(&sba->dev.klist_children, &i);
1316 1329
1317 if (lba) { 1330 if (agp_found && sba_reserve_agpgart) {
1318 DBG_INIT("%s: Reserving half of IOVA space for AGP GART support\n", __FUNCTION__); 1331 printk(KERN_INFO "%s: reserving %dMb of IOVA space for agpgart\n",
1332 __FUNCTION__, (iova_space_size/2) >> 20);
1319 ioc->pdir_size /= 2; 1333 ioc->pdir_size /= 2;
1320 ((u64 *)ioc->pdir_base)[PDIR_INDEX(iova_space_size/2)] = SBA_IOMMU_COOKIE; 1334 ioc->pdir_base[PDIR_INDEX(iova_space_size/2)] = SBA_AGPGART_COOKIE;
1321 } else {
1322 DBG_INIT("%s: No GART needed - no AGP controller found\n", __FUNCTION__);
1323 } 1335 }
1324#endif /* 0 */ 1336}
1337#endif /*SBA_AGP_SUPPORT*/
1325 1338
1326} 1339}
1327 1340
diff --git a/include/asm-parisc/agp.h b/include/asm-parisc/agp.h
new file mode 100644
index 000000000000..9f61d4eb6c01
--- /dev/null
+++ b/include/asm-parisc/agp.h
@@ -0,0 +1,25 @@
1#ifndef _ASM_PARISC_AGP_H
2#define _ASM_PARISC_AGP_H
3
4/*
5 * PARISC specific AGP definitions.
6 * Copyright (c) 2006 Kyle McMartin <kyle@parisc-linux.org>
7 *
8 */
9
10#define map_page_into_agp(page) /* nothing */
11#define unmap_page_from_agp(page) /* nothing */
12#define flush_agp_mappings() /* nothing */
13#define flush_agp_cache() mb()
14
15/* Convert a physical address to an address suitable for the GART. */
16#define phys_to_gart(x) (x)
17#define gart_to_phys(x) (x)
18
19/* GATT allocation. Returns/accepts GATT kernel virtual address. */
20#define alloc_gatt_pages(order) \
21 ((char *)__get_free_pages(GFP_KERNEL, (order)))
22#define free_gatt_pages(table, order) \
23 free_pages((unsigned long)(table), (order))
24
25#endif /* _ASM_PARISC_AGP_H */
diff --git a/include/asm-parisc/ropes.h b/include/asm-parisc/ropes.h
index 2e3de0ae04d1..5542dd00472b 100644
--- a/include/asm-parisc/ropes.h
+++ b/include/asm-parisc/ropes.h
@@ -1,6 +1,8 @@
1#ifndef _ASM_PARISC_ROPES_H_ 1#ifndef _ASM_PARISC_ROPES_H_
2#define _ASM_PARISC_ROPES_H_ 2#define _ASM_PARISC_ROPES_H_
3 3
4#include <asm-parisc/parisc-device.h>
5
4#ifdef CONFIG_64BIT 6#ifdef CONFIG_64BIT
5/* "low end" PA8800 machines use ZX1 chipset: PAT PDC and only run 64-bit */ 7/* "low end" PA8800 machines use ZX1 chipset: PAT PDC and only run 64-bit */
6#define ZX1_SUPPORT 8#define ZX1_SUPPORT
@@ -231,6 +233,16 @@ static inline int IS_QUICKSILVER(struct parisc_device *d) {
231 return (d->id.hversion == QUICKSILVER_HVERS); 233 return (d->id.hversion == QUICKSILVER_HVERS);
232} 234}
233 235
236static inline int agp_mode_mercury(void __iomem *hpa) {
237 u64 bus_mode;
238
239 bus_mode = readl(hpa + 0x0620);
240 if (bus_mode & 1)
241 return 1;
242
243 return 0;
244}
245
234/* 246/*
235** I/O SAPIC init function 247** I/O SAPIC init function
236** Caller knows where an I/O SAPIC is. LBA has an integrated I/O SAPIC. 248** Caller knows where an I/O SAPIC is. LBA has an integrated I/O SAPIC.