Altix: SN ACPI hotplug support.
[linux-2.6.git] / arch / ia64 / sn / kernel / io_acpi_init.c
1 /*
2  * This file is subject to the terms and conditions of the GNU General Public
3  * License.  See the file "COPYING" in the main directory of this archive
4  * for more details.
5  *
6  * Copyright (C) 2006 Silicon Graphics, Inc. All rights reserved.
7  */
8
9 #include <asm/sn/types.h>
10 #include <asm/sn/addrs.h>
11 #include <asm/sn/pcidev.h>
12 #include <asm/sn/pcibus_provider_defs.h>
13 #include <asm/sn/sn_sal.h>
14 #include "xtalk/hubdev.h"
15 #include <linux/acpi.h>
16
17
18 /*
19  * The code in this file will only be executed when running with
20  * a PROM that has ACPI IO support. (i.e., SN_ACPI_BASE_SUPPORT() == 1)
21  */
22
23
24 /*
25  * This value must match the UUID the PROM uses
26  * (io/acpi/defblk.c) when building a vendor descriptor.
27  */
28 struct acpi_vendor_uuid sn_uuid = {
29         .subtype = 0,
30         .data   = { 0x2c, 0xc6, 0xa6, 0xfe, 0x9c, 0x44, 0xda, 0x11,
31                     0xa2, 0x7c, 0x08, 0x00, 0x69, 0x13, 0xea, 0x51 },
32 };
33
34 /*
35  * Perform the early IO init in PROM.
36  */
37 static s64
38 sal_ioif_init(u64 *result)
39 {
40         struct ia64_sal_retval isrv = {0,0,0,0};
41
42         SAL_CALL_NOLOCK(isrv,
43                         SN_SAL_IOIF_INIT, 0, 0, 0, 0, 0, 0, 0);
44         *result = isrv.v0;
45         return isrv.status;
46 }
47
48 /*
49  * sn_hubdev_add - The 'add' function of the acpi_sn_hubdev_driver.
50  *                 Called for every "SGIHUB" or "SGITIO" device defined
51  *                 in the ACPI namespace.
52  */
53 static int __init
54 sn_hubdev_add(struct acpi_device *device)
55 {
56         struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
57         u64 addr;
58         struct hubdev_info *hubdev;
59         struct hubdev_info *hubdev_ptr;
60         int i;
61         u64 nasid;
62         struct acpi_resource *resource;
63         int ret = 0;
64         acpi_status status;
65         struct acpi_resource_vendor_typed *vendor;
66         extern void sn_common_hubdev_init(struct hubdev_info *);
67
68         status = acpi_get_vendor_resource(device->handle, METHOD_NAME__CRS,
69                                           &sn_uuid, &buffer);
70         if (ACPI_FAILURE(status)) {
71                 printk(KERN_ERR
72                        "sn_hubdev_add: acpi_get_vendor_resource() failed: %d\n",
73                         status);
74                 return 1;
75         }
76
77         resource = buffer.pointer;
78         vendor = &resource->data.vendor_typed;
79         if ((vendor->byte_length - sizeof(struct acpi_vendor_uuid)) !=
80             sizeof(struct hubdev_info *)) {
81                 printk(KERN_ERR
82                        "sn_hubdev_add: Invalid vendor data length: %d\n",
83                         vendor->byte_length);
84                 ret = 1;
85                 goto exit;
86         }
87
88         memcpy(&addr, vendor->byte_data, sizeof(struct hubdev_info *));
89         hubdev_ptr = __va((struct hubdev_info *) addr);
90
91         nasid = hubdev_ptr->hdi_nasid;
92         i = nasid_to_cnodeid(nasid);
93         hubdev = (struct hubdev_info *)(NODEPDA(i)->pdinfo);
94         *hubdev = *hubdev_ptr;
95         sn_common_hubdev_init(hubdev);
96
97 exit:
98         kfree(buffer.pointer);
99         return ret;
100 }
101
102 /*
103  * sn_get_bussoft_ptr() - The pcibus_bussoft pointer is found in
104  *                        the ACPI Vendor resource for this bus.
105  */
106 static struct pcibus_bussoft *
107 sn_get_bussoft_ptr(struct pci_bus *bus)
108 {
109         u64 addr;
110         struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
111         acpi_handle handle;
112         struct pcibus_bussoft *prom_bussoft_ptr;
113         struct acpi_resource *resource;
114         acpi_status status;
115         struct acpi_resource_vendor_typed *vendor;
116
117
118         handle = PCI_CONTROLLER(bus)->acpi_handle;
119         status = acpi_get_vendor_resource(handle, METHOD_NAME__CRS,
120                                           &sn_uuid, &buffer);
121         if (ACPI_FAILURE(status)) {
122                 printk(KERN_ERR "get_acpi_pcibus_ptr: "
123                        "get_acpi_bussoft_info() failed: %d\n",
124                        status);
125                 return NULL;
126         }
127         resource = buffer.pointer;
128         vendor = &resource->data.vendor_typed;
129
130         if ((vendor->byte_length - sizeof(struct acpi_vendor_uuid)) !=
131              sizeof(struct pcibus_bussoft *)) {
132                 printk(KERN_ERR
133                        "get_acpi_bussoft_ptr: Invalid vendor data "
134                        "length %d\n", vendor->byte_length);
135                 kfree(buffer.pointer);
136                 return NULL;
137         }
138         memcpy(&addr, vendor->byte_data, sizeof(struct pcibus_bussoft *));
139         prom_bussoft_ptr = __va((struct pcibus_bussoft *) addr);
140         kfree(buffer.pointer);
141
142         return prom_bussoft_ptr;
143 }
144
145 /*
146  * sn_acpi_bus_fixup
147  */
148 void
149 sn_acpi_bus_fixup(struct pci_bus *bus)
150 {
151         struct pci_dev *pci_dev = NULL;
152         struct pcibus_bussoft *prom_bussoft_ptr;
153         extern void sn_common_bus_fixup(struct pci_bus *,
154                                         struct pcibus_bussoft *);
155
156         if (!bus->parent) {     /* If root bus */
157                 prom_bussoft_ptr = sn_get_bussoft_ptr(bus);
158                 if (prom_bussoft_ptr == NULL) {
159                         printk(KERN_ERR
160                                "sn_pci_fixup_bus: 0x%04x:0x%02x Unable to "
161                                "obtain prom_bussoft_ptr\n",
162                                pci_domain_nr(bus), bus->number);
163                         return;
164                 }
165                 sn_common_bus_fixup(bus, prom_bussoft_ptr);
166         }
167         list_for_each_entry(pci_dev, &bus->devices, bus_list) {
168                 sn_pci_fixup_slot(pci_dev);
169         }
170 }
171
172 static struct acpi_driver acpi_sn_hubdev_driver = {
173         .name = "SGI HUBDEV Driver",
174         .ids = "SGIHUB,SGITIO",
175         .ops = {
176                 .add = sn_hubdev_add,
177                 },
178 };
179
180
181 /*
182  * sn_io_acpi_init - PROM has ACPI support for IO, defining at a minimum the
183  *                   nodes and root buses in the DSDT. As a result, bus scanning
184  *                   will be initiated by the Linux ACPI code.
185  */
186
187 void __init
188 sn_io_acpi_init(void)
189 {
190         u64 result;
191         s64 status;
192
193         acpi_bus_register_driver(&acpi_sn_hubdev_driver);
194         status = sal_ioif_init(&result);
195         if (status || result)
196                 panic("sal_ioif_init failed: [%lx] %s\n",
197                       status, ia64_sal_strerror(status));
198 }