at v2.6.26 402 lines 9.6 kB view raw
1/* 2 * Fake PCI Hot Plug Controller Driver 3 * 4 * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com> 5 * Copyright (C) 2003 IBM Corp. 6 * Copyright (C) 2003 Rolf Eike Beer <eike-kernel@sf-tec.de> 7 * 8 * Based on ideas and code from: 9 * Vladimir Kondratiev <vladimir.kondratiev@intel.com> 10 * Rolf Eike Beer <eike-kernel@sf-tec.de> 11 * 12 * All rights reserved. 13 * 14 * This program is free software; you can redistribute it and/or modify 15 * it under the terms of the GNU General Public License as published by 16 * the Free Software Foundation, version 2 of the License. 17 * 18 * Send feedback to <greg@kroah.com> 19 */ 20 21/* 22 * 23 * This driver will "emulate" removing PCI devices from the system. If 24 * the "power" file is written to with "0" then the specified PCI device 25 * will be completely removed from the kernel. 26 * 27 * WARNING, this does NOT turn off the power to the PCI device. This is 28 * a "logical" removal, not a physical or electrical removal. 29 * 30 * Use this module at your own risk, you have been warned! 31 * 32 * Enabling PCI devices is left as an exercise for the reader... 33 * 34 */ 35#include <linux/kernel.h> 36#include <linux/module.h> 37#include <linux/pci.h> 38#include <linux/pci_hotplug.h> 39#include <linux/init.h> 40#include <linux/string.h> 41#include <linux/slab.h> 42#include <linux/workqueue.h> 43#include "../pci.h" 44 45#if !defined(MODULE) 46 #define MY_NAME "fakephp" 47#else 48 #define MY_NAME THIS_MODULE->name 49#endif 50 51#define dbg(format, arg...) \ 52 do { \ 53 if (debug) \ 54 printk(KERN_DEBUG "%s: " format, \ 55 MY_NAME , ## arg); \ 56 } while (0) 57#define err(format, arg...) printk(KERN_ERR "%s: " format, MY_NAME , ## arg) 58#define info(format, arg...) printk(KERN_INFO "%s: " format, MY_NAME , ## arg) 59 60#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>" 61#define DRIVER_DESC "Fake PCI Hot Plug Controller Driver" 62 63struct dummy_slot { 64 struct list_head node; 65 struct hotplug_slot *slot; 66 struct pci_dev *dev; 67 struct work_struct remove_work; 68 unsigned long removed; 69}; 70 71static int debug; 72static LIST_HEAD(slot_list); 73static struct workqueue_struct *dummyphp_wq; 74 75static void pci_rescan_worker(struct work_struct *work); 76static DECLARE_WORK(pci_rescan_work, pci_rescan_worker); 77 78static int enable_slot (struct hotplug_slot *slot); 79static int disable_slot (struct hotplug_slot *slot); 80 81static struct hotplug_slot_ops dummy_hotplug_slot_ops = { 82 .owner = THIS_MODULE, 83 .enable_slot = enable_slot, 84 .disable_slot = disable_slot, 85}; 86 87static void dummy_release(struct hotplug_slot *slot) 88{ 89 struct dummy_slot *dslot = slot->private; 90 91 list_del(&dslot->node); 92 kfree(dslot->slot->info); 93 kfree(dslot->slot); 94 pci_dev_put(dslot->dev); 95 kfree(dslot); 96} 97 98static int add_slot(struct pci_dev *dev) 99{ 100 struct dummy_slot *dslot; 101 struct hotplug_slot *slot; 102 int retval = -ENOMEM; 103 104 slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); 105 if (!slot) 106 goto error; 107 108 slot->info = kzalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); 109 if (!slot->info) 110 goto error_slot; 111 112 slot->info->power_status = 1; 113 slot->info->max_bus_speed = PCI_SPEED_UNKNOWN; 114 slot->info->cur_bus_speed = PCI_SPEED_UNKNOWN; 115 116 slot->name = &dev->dev.bus_id[0]; 117 dbg("slot->name = %s\n", slot->name); 118 119 dslot = kzalloc(sizeof(struct dummy_slot), GFP_KERNEL); 120 if (!dslot) 121 goto error_info; 122 123 slot->ops = &dummy_hotplug_slot_ops; 124 slot->release = &dummy_release; 125 slot->private = dslot; 126 127 retval = pci_hp_register(slot); 128 if (retval) { 129 err("pci_hp_register failed with error %d\n", retval); 130 goto error_dslot; 131 } 132 133 dslot->slot = slot; 134 dslot->dev = pci_dev_get(dev); 135 list_add (&dslot->node, &slot_list); 136 return retval; 137 138error_dslot: 139 kfree(dslot); 140error_info: 141 kfree(slot->info); 142error_slot: 143 kfree(slot); 144error: 145 return retval; 146} 147 148static int __init pci_scan_buses(void) 149{ 150 struct pci_dev *dev = NULL; 151 int retval = 0; 152 153 while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { 154 retval = add_slot(dev); 155 if (retval) { 156 pci_dev_put(dev); 157 break; 158 } 159 } 160 161 return retval; 162} 163 164static void remove_slot(struct dummy_slot *dslot) 165{ 166 int retval; 167 168 dbg("removing slot %s\n", dslot->slot->name); 169 retval = pci_hp_deregister(dslot->slot); 170 if (retval) 171 err("Problem unregistering a slot %s\n", dslot->slot->name); 172} 173 174/* called from the single-threaded workqueue handler to remove a slot */ 175static void remove_slot_worker(struct work_struct *work) 176{ 177 struct dummy_slot *dslot = 178 container_of(work, struct dummy_slot, remove_work); 179 remove_slot(dslot); 180} 181 182/** 183 * pci_rescan_slot - Rescan slot 184 * @temp: Device template. Should be set: bus and devfn. 185 * 186 * Tries hard not to re-enable already existing devices; 187 * also handles scanning of subfunctions. 188 */ 189static void pci_rescan_slot(struct pci_dev *temp) 190{ 191 struct pci_bus *bus = temp->bus; 192 struct pci_dev *dev; 193 int func; 194 int retval; 195 u8 hdr_type; 196 197 if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) { 198 temp->hdr_type = hdr_type & 0x7f; 199 if ((dev = pci_get_slot(bus, temp->devfn)) != NULL) 200 pci_dev_put(dev); 201 else { 202 dev = pci_scan_single_device(bus, temp->devfn); 203 if (dev) { 204 dbg("New device on %s function %x:%x\n", 205 bus->name, temp->devfn >> 3, 206 temp->devfn & 7); 207 retval = pci_bus_add_device(dev); 208 if (retval) 209 dev_err(&dev->dev, "error adding " 210 "device, continuing.\n"); 211 else 212 add_slot(dev); 213 } 214 } 215 /* multifunction device? */ 216 if (!(hdr_type & 0x80)) 217 return; 218 219 /* continue scanning for other functions */ 220 for (func = 1, temp->devfn++; func < 8; func++, temp->devfn++) { 221 if (pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) 222 continue; 223 temp->hdr_type = hdr_type & 0x7f; 224 225 if ((dev = pci_get_slot(bus, temp->devfn)) != NULL) 226 pci_dev_put(dev); 227 else { 228 dev = pci_scan_single_device(bus, temp->devfn); 229 if (dev) { 230 dbg("New device on %s function %x:%x\n", 231 bus->name, temp->devfn >> 3, 232 temp->devfn & 7); 233 retval = pci_bus_add_device(dev); 234 if (retval) 235 dev_err(&dev->dev, "error adding " 236 "device, continuing.\n"); 237 else 238 add_slot(dev); 239 } 240 } 241 } 242 } 243} 244 245 246/** 247 * pci_rescan_bus - Rescan PCI bus 248 * @bus: the PCI bus to rescan 249 * 250 * Call pci_rescan_slot for each possible function of the bus. 251 */ 252static void pci_rescan_bus(const struct pci_bus *bus) 253{ 254 unsigned int devfn; 255 struct pci_dev *dev; 256 dev = alloc_pci_dev(); 257 if (!dev) 258 return; 259 260 dev->bus = (struct pci_bus*)bus; 261 dev->sysdata = bus->sysdata; 262 for (devfn = 0; devfn < 0x100; devfn += 8) { 263 dev->devfn = devfn; 264 pci_rescan_slot(dev); 265 } 266 kfree(dev); 267} 268 269/* recursively scan all buses */ 270static void pci_rescan_buses(const struct list_head *list) 271{ 272 const struct list_head *l; 273 list_for_each(l,list) { 274 const struct pci_bus *b = pci_bus_b(l); 275 pci_rescan_bus(b); 276 pci_rescan_buses(&b->children); 277 } 278} 279 280/* initiate rescan of all pci buses */ 281static inline void pci_rescan(void) { 282 pci_rescan_buses(&pci_root_buses); 283} 284 285/* called from the single-threaded workqueue handler to rescan all pci buses */ 286static void pci_rescan_worker(struct work_struct *work) 287{ 288 pci_rescan(); 289} 290 291static int enable_slot(struct hotplug_slot *hotplug_slot) 292{ 293 /* mis-use enable_slot for rescanning of the pci bus */ 294 cancel_work_sync(&pci_rescan_work); 295 queue_work(dummyphp_wq, &pci_rescan_work); 296 return 0; 297} 298 299/* find the hotplug_slot for the pci_dev */ 300static struct hotplug_slot *get_slot_from_dev(struct pci_dev *dev) 301{ 302 struct dummy_slot *dslot; 303 304 list_for_each_entry(dslot, &slot_list, node) { 305 if (dslot->dev == dev) 306 return dslot->slot; 307 } 308 return NULL; 309} 310 311 312static int disable_slot(struct hotplug_slot *slot) 313{ 314 struct dummy_slot *dslot; 315 struct hotplug_slot *hslot; 316 struct pci_dev *dev; 317 int func; 318 319 if (!slot) 320 return -ENODEV; 321 dslot = slot->private; 322 323 dbg("%s - physical_slot = %s\n", __func__, slot->name); 324 325 /* don't disable bridged devices just yet, we can't handle them easily... */ 326 if (dslot->dev->subordinate) { 327 err("Can't remove PCI devices with other PCI devices behind it yet.\n"); 328 return -ENODEV; 329 } 330 if (test_and_set_bit(0, &dslot->removed)) { 331 dbg("Slot already scheduled for removal\n"); 332 return -ENODEV; 333 } 334 /* search for subfunctions and disable them first */ 335 if (!(dslot->dev->devfn & 7)) { 336 for (func = 1; func < 8; func++) { 337 dev = pci_get_slot(dslot->dev->bus, 338 dslot->dev->devfn + func); 339 if (dev) { 340 hslot = get_slot_from_dev(dev); 341 if (hslot) 342 disable_slot(hslot); 343 else { 344 err("Hotplug slot not found for subfunction of PCI device\n"); 345 return -ENODEV; 346 } 347 pci_dev_put(dev); 348 } else 349 dbg("No device in slot found\n"); 350 } 351 } 352 353 /* remove the device from the pci core */ 354 pci_remove_bus_device(dslot->dev); 355 356 /* queue work item to blow away this sysfs entry and other parts. */ 357 INIT_WORK(&dslot->remove_work, remove_slot_worker); 358 queue_work(dummyphp_wq, &dslot->remove_work); 359 360 return 0; 361} 362 363static void cleanup_slots (void) 364{ 365 struct list_head *tmp; 366 struct list_head *next; 367 struct dummy_slot *dslot; 368 369 destroy_workqueue(dummyphp_wq); 370 list_for_each_safe (tmp, next, &slot_list) { 371 dslot = list_entry (tmp, struct dummy_slot, node); 372 remove_slot(dslot); 373 } 374 375} 376 377static int __init dummyphp_init(void) 378{ 379 info(DRIVER_DESC "\n"); 380 381 dummyphp_wq = create_singlethread_workqueue(MY_NAME); 382 if (!dummyphp_wq) 383 return -ENOMEM; 384 385 return pci_scan_buses(); 386} 387 388 389static void __exit dummyphp_exit(void) 390{ 391 cleanup_slots(); 392} 393 394module_init(dummyphp_init); 395module_exit(dummyphp_exit); 396 397MODULE_AUTHOR(DRIVER_AUTHOR); 398MODULE_DESCRIPTION(DRIVER_DESC); 399MODULE_LICENSE("GPL"); 400module_param(debug, bool, S_IRUGO | S_IWUSR); 401MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); 402