?? device.c
字號:
/* * drivers/s390/cio/device.c * bus driver for ccw devices * $Revision: 1.131 $ * * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation * Author(s): Arnd Bergmann (arndb@de.ibm.com) * Cornelia Huck (cohuck@de.ibm.com) * Martin Schwidefsky (schwidefsky@de.ibm.com) */#include <linux/config.h>#include <linux/module.h>#include <linux/init.h>#include <linux/spinlock.h>#include <linux/errno.h>#include <linux/err.h>#include <linux/slab.h>#include <linux/list.h>#include <linux/device.h>#include <linux/workqueue.h>#include <asm/ccwdev.h>#include <asm/cio.h>#include "cio.h"#include "css.h"#include "device.h"#include "ioasm.h"/******************* bus type handling ***********************//* The Linux driver model distinguishes between a bus type and * the bus itself. Of course we only have one channel * subsystem driver and one channel system per machine, but * we still use the abstraction. T.R. says it's a good idea. */static intccw_bus_match (struct device * dev, struct device_driver * drv){ struct ccw_device *cdev = to_ccwdev(dev); struct ccw_driver *cdrv = to_ccwdrv(drv); const struct ccw_device_id *ids = cdrv->ids, *found; if (!ids) return 0; found = ccw_device_id_match(ids, &cdev->id); if (!found) return 0; cdev->id.driver_info = found->driver_info; return 1;}/* * Hotplugging interface for ccw devices. * Heavily modeled on pci and usb hotplug. */static intccw_hotplug (struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size){ struct ccw_device *cdev = to_ccwdev(dev); int i = 0; int length = 0; if (!cdev) return -ENODEV; /* what we want to pass to /sbin/hotplug */ envp[i++] = buffer; length += scnprintf(buffer, buffer_size - length, "CU_TYPE=%04X", cdev->id.cu_type); if ((buffer_size - length <= 0) || (i >= num_envp)) return -ENOMEM; ++length; buffer += length; envp[i++] = buffer; length += scnprintf(buffer, buffer_size - length, "CU_MODEL=%02X", cdev->id.cu_model); if ((buffer_size - length <= 0) || (i >= num_envp)) return -ENOMEM; ++length; buffer += length; /* The next two can be zero, that's ok for us */ envp[i++] = buffer; length += scnprintf(buffer, buffer_size - length, "DEV_TYPE=%04X", cdev->id.dev_type); if ((buffer_size - length <= 0) || (i >= num_envp)) return -ENOMEM; ++length; buffer += length; envp[i++] = buffer; length += scnprintf(buffer, buffer_size - length, "DEV_MODEL=%02X", cdev->id.dev_model); if ((buffer_size - length <= 0) || (i >= num_envp)) return -ENOMEM; envp[i] = 0; return 0;}struct bus_type ccw_bus_type = { .name = "ccw", .match = &ccw_bus_match, .hotplug = &ccw_hotplug,};static int io_subchannel_probe (struct device *);static int io_subchannel_remove (struct device *);void io_subchannel_irq (struct device *);static int io_subchannel_notify(struct device *, int);static void io_subchannel_verify(struct device *);static void io_subchannel_ioterm(struct device *);static void io_subchannel_shutdown(struct device *);struct css_driver io_subchannel_driver = { .subchannel_type = SUBCHANNEL_TYPE_IO, .drv = { .name = "io_subchannel", .bus = &css_bus_type, .probe = &io_subchannel_probe, .remove = &io_subchannel_remove, .shutdown = &io_subchannel_shutdown, }, .irq = io_subchannel_irq, .notify = io_subchannel_notify, .verify = io_subchannel_verify, .termination = io_subchannel_ioterm,};struct workqueue_struct *ccw_device_work;struct workqueue_struct *ccw_device_notify_work;static wait_queue_head_t ccw_device_init_wq;static atomic_t ccw_device_init_count;static int __initinit_ccw_bus_type (void){ int ret; init_waitqueue_head(&ccw_device_init_wq); atomic_set(&ccw_device_init_count, 0); ccw_device_work = create_singlethread_workqueue("cio"); if (!ccw_device_work) return -ENOMEM; /* FIXME: better errno ? */ ccw_device_notify_work = create_singlethread_workqueue("cio_notify"); if (!ccw_device_notify_work) { ret = -ENOMEM; /* FIXME: better errno ? */ goto out_err; } slow_path_wq = create_singlethread_workqueue("kslowcrw"); if (!slow_path_wq) { ret = -ENOMEM; /* FIXME: better errno ? */ goto out_err; } if ((ret = bus_register (&ccw_bus_type))) goto out_err; if ((ret = driver_register(&io_subchannel_driver.drv))) goto out_err; wait_event(ccw_device_init_wq, atomic_read(&ccw_device_init_count) == 0); flush_workqueue(ccw_device_work); return 0;out_err: if (ccw_device_work) destroy_workqueue(ccw_device_work); if (ccw_device_notify_work) destroy_workqueue(ccw_device_notify_work); if (slow_path_wq) destroy_workqueue(slow_path_wq); return ret;}static void __exitcleanup_ccw_bus_type (void){ driver_unregister(&io_subchannel_driver.drv); bus_unregister(&ccw_bus_type); destroy_workqueue(ccw_device_notify_work); destroy_workqueue(ccw_device_work);}subsys_initcall(init_ccw_bus_type);module_exit(cleanup_ccw_bus_type);/************************ device handling **************************//* * A ccw_device has some interfaces in sysfs in addition to the * standard ones. * The following entries are designed to export the information which * resided in 2.4 in /proc/subchannels. Subchannel and device number * are obvious, so they don't have an entry :) * TODO: Split chpids and pimpampom up? Where is "in use" in the tree? */static ssize_tchpids_show (struct device * dev, struct device_attribute *attr, char * buf){ struct subchannel *sch = to_subchannel(dev); struct ssd_info *ssd = &sch->ssd_info; ssize_t ret = 0; int chp; for (chp = 0; chp < 8; chp++) ret += sprintf (buf+ret, "%02x ", ssd->chpid[chp]); ret += sprintf (buf+ret, "\n"); return min((ssize_t)PAGE_SIZE, ret);}static ssize_tpimpampom_show (struct device * dev, struct device_attribute *attr, char * buf){ struct subchannel *sch = to_subchannel(dev); struct pmcw *pmcw = &sch->schib.pmcw; return sprintf (buf, "%02x %02x %02x\n", pmcw->pim, pmcw->pam, pmcw->pom);}static ssize_tdevtype_show (struct device *dev, struct device_attribute *attr, char *buf){ struct ccw_device *cdev = to_ccwdev(dev); struct ccw_device_id *id = &(cdev->id); if (id->dev_type != 0) return sprintf(buf, "%04x/%02x\n", id->dev_type, id->dev_model); else return sprintf(buf, "n/a\n");}static ssize_tcutype_show (struct device *dev, struct device_attribute *attr, char *buf){ struct ccw_device *cdev = to_ccwdev(dev); struct ccw_device_id *id = &(cdev->id); return sprintf(buf, "%04x/%02x\n", id->cu_type, id->cu_model);}static ssize_tonline_show (struct device *dev, struct device_attribute *attr, char *buf){ struct ccw_device *cdev = to_ccwdev(dev); return sprintf(buf, cdev->online ? "1\n" : "0\n");}static voidccw_device_remove_disconnected(struct ccw_device *cdev){ struct subchannel *sch; /* * Forced offline in disconnected state means * 'throw away device'. */ sch = to_subchannel(cdev->dev.parent); device_unregister(&sch->dev); /* Reset intparm to zeroes. */ sch->schib.pmcw.intparm = 0; cio_modify(sch); put_device(&sch->dev);}intccw_device_set_offline(struct ccw_device *cdev){ int ret; if (!cdev) return -ENODEV; if (!cdev->online || !cdev->drv) return -EINVAL; if (cdev->drv->set_offline) { ret = cdev->drv->set_offline(cdev); if (ret != 0) return ret; } cdev->online = 0; spin_lock_irq(cdev->ccwlock); ret = ccw_device_offline(cdev); if (ret == -ENODEV) { if (cdev->private->state != DEV_STATE_NOT_OPER) { cdev->private->state = DEV_STATE_OFFLINE; dev_fsm_event(cdev, DEV_EVENT_NOTOPER); } spin_unlock_irq(cdev->ccwlock); return ret; } spin_unlock_irq(cdev->ccwlock); if (ret == 0) wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); else { pr_debug("ccw_device_offline returned %d, device %s\n", ret, cdev->dev.bus_id); cdev->online = 1; } return ret;}intccw_device_set_online(struct ccw_device *cdev){ int ret; if (!cdev) return -ENODEV; if (cdev->online || !cdev->drv) return -EINVAL; spin_lock_irq(cdev->ccwlock); ret = ccw_device_online(cdev); spin_unlock_irq(cdev->ccwlock); if (ret == 0) wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); else { pr_debug("ccw_device_online returned %d, device %s\n", ret, cdev->dev.bus_id); return ret; } if (cdev->private->state != DEV_STATE_ONLINE) return -ENODEV; if (!cdev->drv->set_online || cdev->drv->set_online(cdev) == 0) { cdev->online = 1; return 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 pr_debug("ccw_device_offline returned %d, device %s\n", ret, cdev->dev.bus_id); return (ret = 0) ? -ENODEV : ret;}static ssize_tonline_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct ccw_device *cdev = to_ccwdev(dev); int i, force, ret; char *tmp; if (atomic_compare_and_swap(0, 1, &cdev->private->onoff)) return -EAGAIN; if (cdev->drv && !try_module_get(cdev->drv->owner)) { atomic_set(&cdev->private->onoff, 0); return -EINVAL; } if (!strncmp(buf, "force\n", count)) { force = 1; i = 1; } else { force = 0; i = simple_strtoul(buf, &tmp, 16); } if (i == 1) { /* Do device recognition, if needed. */ if (cdev->id.cu_type == 0) { ret = ccw_device_recognition(cdev); if (ret) { printk(KERN_WARNING"Couldn't start recognition " "for device %s (ret=%d)\n", cdev->dev.bus_id, ret); goto out; } wait_event(cdev->private->wait_q, cdev->private->flags.recog_done); } if (cdev->drv && cdev->drv->set_online) ccw_device_set_online(cdev); } else if (i == 0) { if (cdev->private->state == DEV_STATE_DISCONNECTED) ccw_device_remove_disconnected(cdev); else if (cdev->drv && cdev->drv->set_offline) ccw_device_set_offline(cdev); } if (force && cdev->private->state == DEV_STATE_BOXED) { ret = ccw_device_stlck(cdev); if (ret) { printk(KERN_WARNING"ccw_device_stlck for device %s " "returned %d!\n", cdev->dev.bus_id, ret); goto out; } /* Do device recognition, if needed. */ if (cdev->id.cu_type == 0) { cdev->private->state = DEV_STATE_NOT_OPER; ret = ccw_device_recognition(cdev); if (ret) { printk(KERN_WARNING"Couldn't start recognition " "for device %s (ret=%d)\n", cdev->dev.bus_id, ret); goto out; } wait_event(cdev->private->wait_q, cdev->private->flags.recog_done); } if (cdev->drv && cdev->drv->set_online) ccw_device_set_online(cdev); } out: if (cdev->drv) module_put(cdev->drv->owner); atomic_set(&cdev->private->onoff, 0); return count;}static ssize_tavailable_show (struct device *dev, struct device_attribute *attr, char *buf){ struct ccw_device *cdev = to_ccwdev(dev); struct subchannel *sch; switch (cdev->private->state) { case DEV_STATE_BOXED: return sprintf(buf, "boxed\n"); case DEV_STATE_DISCONNECTED: case DEV_STATE_DISCONNECTED_SENSE_ID: case DEV_STATE_NOT_OPER: sch = to_subchannel(dev->parent); if (!sch->lpm) return sprintf(buf, "no path\n"); else return sprintf(buf, "no device\n"); default: /* All other states considered fine. */ return sprintf(buf, "good\n"); }}static DEVICE_ATTR(chpids, 0444, chpids_show, NULL);static DEVICE_ATTR(pimpampom, 0444, pimpampom_show, NULL);static DEVICE_ATTR(devtype, 0444, devtype_show, NULL);static DEVICE_ATTR(cutype, 0444, cutype_show, NULL);static DEVICE_ATTR(online, 0644, online_show, online_store);extern struct device_attribute dev_attr_cmb_enable;static DEVICE_ATTR(availability, 0444, available_show, NULL);static struct attribute * subch_attrs[] = { &dev_attr_chpids.attr, &dev_attr_pimpampom.attr, NULL,};static struct attribute_group subch_attr_group = { .attrs = subch_attrs,};static inline intsubchannel_add_files (struct device *dev){ return sysfs_create_group(&dev->kobj, &subch_attr_group);}static struct attribute * ccwdev_attrs[] = { &dev_attr_devtype.attr, &dev_attr_cutype.attr, &dev_attr_online.attr, &dev_attr_cmb_enable.attr, &dev_attr_availability.attr, NULL,};static struct attribute_group ccwdev_attr_group = { .attrs = ccwdev_attrs,};static inline intdevice_add_files (struct device *dev){ return sysfs_create_group(&dev->kobj, &ccwdev_attr_group);}static inline voiddevice_remove_files(struct device *dev){ sysfs_remove_group(&dev->kobj, &ccwdev_attr_group);}/* this is a simple abstraction for device_register that sets the * correct bus type and adds the bus specific files */intccw_device_register(struct ccw_device *cdev){ struct device *dev = &cdev->dev; int ret; dev->bus = &ccw_bus_type; if ((ret = device_add(dev))) return ret; set_bit(1, &cdev->private->registered); if ((ret = device_add_files(dev))) { if (test_and_clear_bit(1, &cdev->private->registered)) device_del(dev); } return ret;}struct match_data { unsigned int devno; struct ccw_device * sibling;};static intmatch_devno(struct device * dev, void * data){ struct match_data * d = (struct match_data *)data; struct ccw_device * cdev; cdev = to_ccwdev(dev); if ((cdev->private->state == DEV_STATE_DISCONNECTED) && (cdev->private->devno == d->devno) && (cdev != d->sibling)) { cdev->private->state = DEV_STATE_NOT_OPER; return 1; } return 0;}static struct ccw_device *get_disc_ccwdev_by_devno(unsigned int devno, struct ccw_device *sibling){ struct device *dev; struct match_data data = { .devno = devno, .sibling = sibling, }; dev = bus_find_device(&ccw_bus_type, NULL, &data, match_devno); return dev ? to_ccwdev(dev) : NULL;}static voidccw_device_add_changed(void *data){ struct ccw_device *cdev; cdev = (struct ccw_device *)data; if (device_add(&cdev->dev)) { put_device(&cdev->dev); return; } set_bit(1, &cdev->private->registered); if (device_add_files(&cdev->dev)) { if (test_and_clear_bit(1, &cdev->private->registered)) device_unregister(&cdev->dev); }}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -