📄 device.c
字号:
voidccw_device_do_unreg_rereg(void *data){ struct ccw_device *cdev; struct subchannel *sch; int need_rename; cdev = (struct ccw_device *)data; sch = to_subchannel(cdev->dev.parent); if (cdev->private->devno != sch->schib.pmcw.dev) { /* * The device number has changed. This is usually only when * a device has been detached under VM and then re-appeared * on another subchannel because of a different attachment * order than before. Ideally, we should should just switch * subchannels, but unfortunately, this is not possible with * the current implementation. * Instead, we search for the old subchannel for this device * number and deregister so there are no collisions with the * newly registered ccw_device. * FIXME: Find another solution so the block layer doesn't * get possibly sick... */ struct ccw_device *other_cdev; need_rename = 1; other_cdev = get_disc_ccwdev_by_devno(sch->schib.pmcw.dev, cdev); if (other_cdev) { struct subchannel *other_sch; other_sch = to_subchannel(other_cdev->dev.parent); if (get_device(&other_sch->dev)) { stsch(other_sch->irq, &other_sch->schib); if (other_sch->schib.pmcw.dnv) { other_sch->schib.pmcw.intparm = 0; cio_modify(other_sch); } device_unregister(&other_sch->dev); } } cdev->private->devno = sch->schib.pmcw.dev; } else need_rename = 0; device_remove_files(&cdev->dev); device_del(&cdev->dev); if (need_rename) snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", sch->schib.pmcw.dev); if (device_add(&cdev->dev)) { put_device(&cdev->dev); return; } if (device_add_files(&cdev->dev)) device_unregister(&cdev->dev);}static voidccw_device_release(struct device *dev){ struct ccw_device *cdev; cdev = to_ccwdev(dev); kfree(cdev->private); kfree(cdev);}/* * Register recognized device. */static voidio_subchannel_register(void *data){ struct ccw_device *cdev; struct subchannel *sch; int ret; cdev = (struct ccw_device *) data; sch = to_subchannel(cdev->dev.parent); if (!list_empty(&sch->dev.children)) { bus_rescan_devices(&ccw_bus_type); goto out; } /* make it known to the system */ ret = ccw_device_register(cdev); if (ret) { printk (KERN_WARNING "%s: could not register %s\n", __func__, cdev->dev.bus_id); put_device(&cdev->dev); sch->dev.driver_data = 0; kfree (cdev->private); kfree (cdev); goto out; } ret = subchannel_add_files(cdev->dev.parent); if (ret) printk(KERN_WARNING "%s: could not add attributes to %s\n", __func__, sch->dev.bus_id); put_device(&cdev->dev);out: cdev->private->flags.recog_done = 1; put_device(&sch->dev); wake_up(&cdev->private->wait_q);}voidccw_device_call_sch_unregister(void *data){ struct ccw_device *cdev = data; struct subchannel *sch; sch = to_subchannel(cdev->dev.parent); device_unregister(&sch->dev); /* Reset intparm to zeroes. */ sch->schib.pmcw.intparm = 0; cio_modify(sch); put_device(&cdev->dev); put_device(&sch->dev);}/* * subchannel recognition done. Called from the state machine. */voidio_subchannel_recog_done(struct ccw_device *cdev){ struct subchannel *sch; if (css_init_done == 0) { cdev->private->flags.recog_done = 1; return; } switch (cdev->private->state) { case DEV_STATE_NOT_OPER: cdev->private->flags.recog_done = 1; /* Remove device found not operational. */ if (!get_device(&cdev->dev)) break; sch = to_subchannel(cdev->dev.parent); INIT_WORK(&cdev->private->kick_work, ccw_device_call_sch_unregister, (void *) cdev); queue_work(slow_path_wq, &cdev->private->kick_work); break; case DEV_STATE_BOXED: /* Device did not respond in time. */ case DEV_STATE_OFFLINE: /* * We can't register the device in interrupt context so * we schedule a work item. */ if (!get_device(&cdev->dev)) break; INIT_WORK(&cdev->private->kick_work, io_subchannel_register, (void *) cdev); queue_work(ccw_device_work, &cdev->private->kick_work); break; } if (atomic_dec_and_test(&ccw_device_init_count)) wake_up(&ccw_device_init_wq);}static intio_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch){ int rc; sch->dev.driver_data = cdev; sch->driver = &io_subchannel_driver; cdev->ccwlock = &sch->lock; *cdev->private = (struct ccw_device_private) { .devno = sch->schib.pmcw.dev, .irq = sch->irq, .state = DEV_STATE_NOT_OPER, .cmb_list = LIST_HEAD_INIT(cdev->private->cmb_list), }; init_waitqueue_head(&cdev->private->wait_q); init_timer(&cdev->private->timer); /* Set an initial name for the device. */ snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", sch->schib.pmcw.dev); /* Increase counter of devices currently in recognition. */ atomic_inc(&ccw_device_init_count); /* Start async. device sensing. */ spin_lock_irq(&sch->lock); rc = ccw_device_recognition(cdev); spin_unlock_irq(&sch->lock); if (rc) { if (atomic_dec_and_test(&ccw_device_init_count)) wake_up(&ccw_device_init_wq); } return rc;}static intio_subchannel_probe (struct device *pdev){ struct subchannel *sch; struct ccw_device *cdev; int rc; sch = to_subchannel(pdev); if (sch->dev.driver_data) { /* * This subchannel already has an associated ccw_device. * Register it and exit. This happens for all early * device, e.g. the console. */ cdev = sch->dev.driver_data; device_initialize(&cdev->dev); ccw_device_register(cdev); subchannel_add_files(&sch->dev); /* * Check if the device is already online. If it is * the reference count needs to be corrected * (see ccw_device_online and css_init_done for the * ugly details). */ if (cdev->private->state != DEV_STATE_NOT_OPER && cdev->private->state != DEV_STATE_OFFLINE && cdev->private->state != DEV_STATE_BOXED) get_device(&cdev->dev); return 0; } cdev = kmalloc (sizeof(*cdev), GFP_KERNEL); if (!cdev) return -ENOMEM; memset(cdev, 0, sizeof(struct ccw_device)); cdev->private = kmalloc(sizeof(struct ccw_device_private), GFP_KERNEL | GFP_DMA); if (!cdev->private) { kfree(cdev); return -ENOMEM; } memset(cdev->private, 0, sizeof(struct ccw_device_private)); atomic_set(&cdev->private->onoff, 0); cdev->dev = (struct device) { .parent = pdev, .release = ccw_device_release, }; /* Do first half of device_register. */ device_initialize(&cdev->dev); if (!get_device(&sch->dev)) { if (cdev->dev.release) cdev->dev.release(&cdev->dev); return -ENODEV; } rc = io_subchannel_recog(cdev, to_subchannel(pdev)); if (rc) { sch->dev.driver_data = 0; if (cdev->dev.release) cdev->dev.release(&cdev->dev); } return rc;}static intio_subchannel_remove (struct device *dev){ struct ccw_device *cdev; if (!dev->driver_data) return 0; cdev = dev->driver_data; /* Set ccw device to not operational and drop reference. */ cdev->private->state = DEV_STATE_NOT_OPER; /* * Careful here. Our ccw device might be yet unregistered when * de-registering its subchannel (machine check during device * recognition). Better look if the subchannel has children. */ if (!list_empty(&dev->children)) device_unregister(&cdev->dev); dev->driver_data = NULL; return 0;}static intio_subchannel_notify(struct device *dev, int event){ struct ccw_device *cdev; cdev = dev->driver_data; if (!cdev) return 0; if (!cdev->drv) return 0; if (!cdev->online) return 0; return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0;}static voidio_subchannel_verify(struct device *dev){ struct ccw_device *cdev; cdev = dev->driver_data; if (cdev) dev_fsm_event(cdev, DEV_EVENT_VERIFY);}static voidio_subchannel_ioterm(struct device *dev){ struct ccw_device *cdev; cdev = dev->driver_data; if (!cdev) return; cdev->private->state = DEV_STATE_CLEAR_VERIFY; if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, ERR_PTR(-EIO));}static voidio_subchannel_shutdown(struct device *dev){ struct subchannel *sch; struct ccw_device *cdev; int ret; sch = to_subchannel(dev); cdev = dev->driver_data; if (cio_is_console(sch->irq)) return; if (!sch->schib.pmcw.ena) /* Nothing to do. */ return; ret = cio_disable_subchannel(sch); if (ret != -EBUSY) /* Subchannel is disabled, we're done. */ return; cdev->private->state = DEV_STATE_QUIESCE; if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, ERR_PTR(-EIO)); ret = ccw_device_cancel_halt_clear(cdev); if (ret == -EBUSY) { ccw_device_set_timeout(cdev, HZ/10); wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); } cio_disable_subchannel(sch);}#ifdef CONFIG_CCW_CONSOLEstatic struct ccw_device console_cdev;static struct ccw_device_private console_private;static int console_cdev_in_use;static intccw_device_console_enable (struct ccw_device *cdev, struct subchannel *sch){ int rc; /* Initialize the ccw_device structure. */ cdev->dev = (struct device) { .parent = &sch->dev, }; /* Initialize the subchannel structure */ sch->dev.parent = &css_bus_device; sch->dev.bus = &css_bus_type; rc = io_subchannel_recog(cdev, sch); if (rc) return rc; /* Now wait for the async. recognition to come to an end. */ spin_lock_irq(cdev->ccwlock); while (!dev_fsm_final_state(cdev)) wait_cons_dev(); rc = -EIO; if (cdev->private->state != DEV_STATE_OFFLINE) goto out_unlock; ccw_device_online(cdev); while (!dev_fsm_final_state(cdev)) wait_cons_dev(); if (cdev->private->state != DEV_STATE_ONLINE) goto out_unlock; rc = 0;out_unlock: spin_unlock_irq(cdev->ccwlock); return 0;}struct ccw_device *ccw_device_probe_console(void){ struct subchannel *sch; int ret; if (xchg(&console_cdev_in_use, 1) != 0) return NULL; sch = cio_probe_console(); if (IS_ERR(sch)) { console_cdev_in_use = 0; return (void *) sch; } memset(&console_cdev, 0, sizeof(struct ccw_device)); memset(&console_private, 0, sizeof(struct ccw_device_private)); console_cdev.private = &console_private; ret = ccw_device_console_enable(&console_cdev, sch); if (ret) { cio_release_console(); console_cdev_in_use = 0; return ERR_PTR(ret); } console_cdev.online = 1; return &console_cdev;}#endif/* * get ccw_device matching the busid, but only if owned by cdrv */struct ccw_device *get_ccwdev_by_busid(struct ccw_driver *cdrv, const char *bus_id){ struct device *d, *dev; struct device_driver *drv; drv = get_driver(&cdrv->driver); if (!drv) return 0; down_read(&drv->bus->subsys.rwsem); dev = NULL; list_for_each_entry(d, &drv->devices, driver_list) { dev = get_device(d); if (dev && !strncmp(bus_id, dev->bus_id, BUS_ID_SIZE)) break; else if (dev) { put_device(dev); dev = NULL; } } up_read(&drv->bus->subsys.rwsem); put_driver(drv); return dev ? to_ccwdev(dev) : 0;}/************************** device driver handling ************************//* This is the implementation of the ccw_driver class. The probe, remove * and release methods are initially very similar to the device_driver * implementations, with the difference that they have ccw_device * arguments. * * A ccw driver also contains the information that is needed for * device matching. */static intccw_device_probe (struct device *dev){ struct ccw_device *cdev = to_ccwdev(dev); struct ccw_driver *cdrv = to_ccwdrv(dev->driver); int ret; cdev->drv = cdrv; /* to let the driver call _set_online */ ret = cdrv->probe ? cdrv->probe(cdev) : -ENODEV; if (ret) { cdev->drv = 0; return ret; } return 0;}static intccw_device_remove (struct device *dev){ struct ccw_device *cdev = to_ccwdev(dev); struct ccw_driver *cdrv = cdev->drv; int ret; pr_debug("removing device %s\n", cdev->dev.bus_id); if (cdrv->remove) cdrv->remove(cdev); if (cdev->online) { cdev->online = 0; spin_lock_irq(cdev->ccwlock); ret = ccw_device_offline(cdev); spin_unlock_irq(cdev->ccwlock); if (ret == 0) wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); else //FIXME: we can't fail! pr_debug("ccw_device_offline returned %d, device %s\n", ret, cdev->dev.bus_id); } ccw_device_set_timeout(cdev, 0); cdev->drv = 0; return 0;}intccw_driver_register (struct ccw_driver *cdriver){ struct device_driver *drv = &cdriver->driver; drv->bus = &ccw_bus_type; drv->name = cdriver->name; drv->probe = ccw_device_probe; drv->remove = ccw_device_remove; return driver_register(drv);}voidccw_driver_unregister (struct ccw_driver *cdriver){ driver_unregister(&cdriver->driver);}MODULE_LICENSE("GPL");EXPORT_SYMBOL(ccw_device_set_online);EXPORT_SYMBOL(ccw_device_set_offline);EXPORT_SYMBOL(ccw_driver_register);EXPORT_SYMBOL(ccw_driver_unregister);EXPORT_SYMBOL(get_ccwdev_by_busid);EXPORT_SYMBOL(ccw_bus_type);EXPORT_SYMBOL(ccw_device_work);EXPORT_SYMBOL(ccw_device_notify_work);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -