at v2.6.27 376 lines 9.0 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 char name[8]; 70}; 71 72static int debug; 73static LIST_HEAD(slot_list); 74static struct workqueue_struct *dummyphp_wq; 75 76static void pci_rescan_worker(struct work_struct *work); 77static DECLARE_WORK(pci_rescan_work, pci_rescan_worker); 78 79static int enable_slot (struct hotplug_slot *slot); 80static int disable_slot (struct hotplug_slot *slot); 81 82static struct hotplug_slot_ops dummy_hotplug_slot_ops = { 83 .owner = THIS_MODULE, 84 .enable_slot = enable_slot, 85 .disable_slot = disable_slot, 86}; 87 88static void dummy_release(struct hotplug_slot *slot) 89{ 90 struct dummy_slot *dslot = slot->private; 91 92 list_del(&dslot->node); 93 kfree(dslot->slot->info); 94 kfree(dslot->slot); 95 pci_dev_put(dslot->dev); 96 kfree(dslot); 97} 98 99static int add_slot(struct pci_dev *dev) 100{ 101 struct dummy_slot *dslot; 102 struct hotplug_slot *slot; 103 int retval = -ENOMEM; 104 static int count = 1; 105 106 slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); 107 if (!slot) 108 goto error; 109 110 slot->info = kzalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); 111 if (!slot->info) 112 goto error_slot; 113 114 slot->info->power_status = 1; 115 slot->info->max_bus_speed = PCI_SPEED_UNKNOWN; 116 slot->info->cur_bus_speed = PCI_SPEED_UNKNOWN; 117 118 dslot = kzalloc(sizeof(struct dummy_slot), GFP_KERNEL); 119 if (!dslot) 120 goto error_info; 121 122 slot->name = dslot->name; 123 snprintf(slot->name, sizeof(dslot->name), "fake%d", count++); 124 dbg("slot->name = %s\n", slot->name); 125 slot->ops = &dummy_hotplug_slot_ops; 126 slot->release = &dummy_release; 127 slot->private = dslot; 128 129 retval = pci_hp_register(slot, dev->bus, PCI_SLOT(dev->devfn)); 130 if (retval) { 131 err("pci_hp_register failed with error %d\n", retval); 132 goto error_dslot; 133 } 134 135 dslot->slot = slot; 136 dslot->dev = pci_dev_get(dev); 137 list_add (&dslot->node, &slot_list); 138 return retval; 139 140error_dslot: 141 kfree(dslot); 142error_info: 143 kfree(slot->info); 144error_slot: 145 kfree(slot); 146error: 147 return retval; 148} 149 150static int __init pci_scan_buses(void) 151{ 152 struct pci_dev *dev = NULL; 153 int lastslot = 0; 154 155 while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { 156 if (PCI_FUNC(dev->devfn) > 0 && 157 lastslot == PCI_SLOT(dev->devfn)) 158 continue; 159 lastslot = PCI_SLOT(dev->devfn); 160 add_slot(dev); 161 } 162 163 return 0; 164} 165 166static void remove_slot(struct dummy_slot *dslot) 167{ 168 int retval; 169 170 dbg("removing slot %s\n", dslot->slot->name); 171 retval = pci_hp_deregister(dslot->slot); 172 if (retval) 173 err("Problem unregistering a slot %s\n", dslot->slot->name); 174} 175 176/* called from the single-threaded workqueue handler to remove a slot */ 177static void remove_slot_worker(struct work_struct *work) 178{ 179 struct dummy_slot *dslot = 180 container_of(work, struct dummy_slot, remove_work); 181 remove_slot(dslot); 182} 183 184/** 185 * pci_rescan_slot - Rescan slot 186 * @temp: Device template. Should be set: bus and devfn. 187 * 188 * Tries hard not to re-enable already existing devices; 189 * also handles scanning of subfunctions. 190 */ 191static void pci_rescan_slot(struct pci_dev *temp) 192{ 193 struct pci_bus *bus = temp->bus; 194 struct pci_dev *dev; 195 int func; 196 int retval; 197 u8 hdr_type; 198 199 if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) { 200 temp->hdr_type = hdr_type & 0x7f; 201 if ((dev = pci_get_slot(bus, temp->devfn)) != NULL) 202 pci_dev_put(dev); 203 else { 204 dev = pci_scan_single_device(bus, temp->devfn); 205 if (dev) { 206 dbg("New device on %s function %x:%x\n", 207 bus->name, temp->devfn >> 3, 208 temp->devfn & 7); 209 retval = pci_bus_add_device(dev); 210 if (retval) 211 dev_err(&dev->dev, "error adding " 212 "device, continuing.\n"); 213 else 214 add_slot(dev); 215 } 216 } 217 /* multifunction device? */ 218 if (!(hdr_type & 0x80)) 219 return; 220 221 /* continue scanning for other functions */ 222 for (func = 1, temp->devfn++; func < 8; func++, temp->devfn++) { 223 if (pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) 224 continue; 225 temp->hdr_type = hdr_type & 0x7f; 226 227 if ((dev = pci_get_slot(bus, temp->devfn)) != NULL) 228 pci_dev_put(dev); 229 else { 230 dev = pci_scan_single_device(bus, temp->devfn); 231 if (dev) { 232 dbg("New device on %s function %x:%x\n", 233 bus->name, temp->devfn >> 3, 234 temp->devfn & 7); 235 retval = pci_bus_add_device(dev); 236 if (retval) 237 dev_err(&dev->dev, "error adding " 238 "device, continuing.\n"); 239 else 240 add_slot(dev); 241 } 242 } 243 } 244 } 245} 246 247 248/** 249 * pci_rescan_bus - Rescan PCI bus 250 * @bus: the PCI bus to rescan 251 * 252 * Call pci_rescan_slot for each possible function of the bus. 253 */ 254static void pci_rescan_bus(const struct pci_bus *bus) 255{ 256 unsigned int devfn; 257 struct pci_dev *dev; 258 dev = alloc_pci_dev(); 259 if (!dev) 260 return; 261 262 dev->bus = (struct pci_bus*)bus; 263 dev->sysdata = bus->sysdata; 264 for (devfn = 0; devfn < 0x100; devfn += 8) { 265 dev->devfn = devfn; 266 pci_rescan_slot(dev); 267 } 268 kfree(dev); 269} 270 271/* recursively scan all buses */ 272static void pci_rescan_buses(const struct list_head *list) 273{ 274 const struct list_head *l; 275 list_for_each(l,list) { 276 const struct pci_bus *b = pci_bus_b(l); 277 pci_rescan_bus(b); 278 pci_rescan_buses(&b->children); 279 } 280} 281 282/* initiate rescan of all pci buses */ 283static inline void pci_rescan(void) { 284 pci_rescan_buses(&pci_root_buses); 285} 286 287/* called from the single-threaded workqueue handler to rescan all pci buses */ 288static void pci_rescan_worker(struct work_struct *work) 289{ 290 pci_rescan(); 291} 292 293static int enable_slot(struct hotplug_slot *hotplug_slot) 294{ 295 /* mis-use enable_slot for rescanning of the pci bus */ 296 cancel_work_sync(&pci_rescan_work); 297 queue_work(dummyphp_wq, &pci_rescan_work); 298 return 0; 299} 300 301static int disable_slot(struct hotplug_slot *slot) 302{ 303 struct dummy_slot *dslot; 304 struct pci_dev *dev; 305 int func; 306 307 if (!slot) 308 return -ENODEV; 309 dslot = slot->private; 310 311 dbg("%s - physical_slot = %s\n", __func__, slot->name); 312 313 for (func = 7; func >= 0; func--) { 314 dev = pci_get_slot(dslot->dev->bus, dslot->dev->devfn + func); 315 if (!dev) 316 continue; 317 318 if (test_and_set_bit(0, &dslot->removed)) { 319 dbg("Slot already scheduled for removal\n"); 320 return -ENODEV; 321 } 322 323 /* remove the device from the pci core */ 324 pci_remove_bus_device(dev); 325 326 /* queue work item to blow away this sysfs entry and other 327 * parts. 328 */ 329 INIT_WORK(&dslot->remove_work, remove_slot_worker); 330 queue_work(dummyphp_wq, &dslot->remove_work); 331 332 pci_dev_put(dev); 333 } 334 return 0; 335} 336 337static void cleanup_slots (void) 338{ 339 struct list_head *tmp; 340 struct list_head *next; 341 struct dummy_slot *dslot; 342 343 destroy_workqueue(dummyphp_wq); 344 list_for_each_safe (tmp, next, &slot_list) { 345 dslot = list_entry (tmp, struct dummy_slot, node); 346 remove_slot(dslot); 347 } 348 349} 350 351static int __init dummyphp_init(void) 352{ 353 info(DRIVER_DESC "\n"); 354 355 dummyphp_wq = create_singlethread_workqueue(MY_NAME); 356 if (!dummyphp_wq) 357 return -ENOMEM; 358 359 return pci_scan_buses(); 360} 361 362 363static void __exit dummyphp_exit(void) 364{ 365 cleanup_slots(); 366} 367 368module_init(dummyphp_init); 369module_exit(dummyphp_exit); 370 371MODULE_AUTHOR(DRIVER_AUTHOR); 372MODULE_DESCRIPTION(DRIVER_DESC); 373MODULE_LICENSE("GPL"); 374module_param(debug, bool, S_IRUGO | S_IWUSR); 375MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); 376