/drivers/s390/cio/css.c
C | 1254 lines | 984 code | 165 blank | 105 comment | 130 complexity | 3001137783c387e3960f10718ad4e7a1 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, AGPL-1.0
- /*
- * driver for channel subsystem
- *
- * Copyright IBM Corp. 2002, 2010
- *
- * Author(s): Arnd Bergmann (arndb@de.ibm.com)
- * Cornelia Huck (cornelia.huck@de.ibm.com)
- */
- #define KMSG_COMPONENT "cio"
- #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/device.h>
- #include <linux/slab.h>
- #include <linux/errno.h>
- #include <linux/list.h>
- #include <linux/reboot.h>
- #include <linux/suspend.h>
- #include <linux/proc_fs.h>
- #include <asm/isc.h>
- #include <asm/crw.h>
- #include "css.h"
- #include "cio.h"
- #include "cio_debug.h"
- #include "ioasm.h"
- #include "chsc.h"
- #include "device.h"
- #include "idset.h"
- #include "chp.h"
- int css_init_done = 0;
- int max_ssid;
- struct channel_subsystem *channel_subsystems[__MAX_CSSID + 1];
- static struct bus_type css_bus_type;
- int
- for_each_subchannel(int(*fn)(struct subchannel_id, void *), void *data)
- {
- struct subchannel_id schid;
- int ret;
- init_subchannel_id(&schid);
- ret = -ENODEV;
- do {
- do {
- ret = fn(schid, data);
- if (ret)
- break;
- } while (schid.sch_no++ < __MAX_SUBCHANNEL);
- schid.sch_no = 0;
- } while (schid.ssid++ < max_ssid);
- return ret;
- }
- struct cb_data {
- void *data;
- struct idset *set;
- int (*fn_known_sch)(struct subchannel *, void *);
- int (*fn_unknown_sch)(struct subchannel_id, void *);
- };
- static int call_fn_known_sch(struct device *dev, void *data)
- {
- struct subchannel *sch = to_subchannel(dev);
- struct cb_data *cb = data;
- int rc = 0;
- idset_sch_del(cb->set, sch->schid);
- if (cb->fn_known_sch)
- rc = cb->fn_known_sch(sch, cb->data);
- return rc;
- }
- static int call_fn_unknown_sch(struct subchannel_id schid, void *data)
- {
- struct cb_data *cb = data;
- int rc = 0;
- if (idset_sch_contains(cb->set, schid))
- rc = cb->fn_unknown_sch(schid, cb->data);
- return rc;
- }
- static int call_fn_all_sch(struct subchannel_id schid, void *data)
- {
- struct cb_data *cb = data;
- struct subchannel *sch;
- int rc = 0;
- sch = get_subchannel_by_schid(schid);
- if (sch) {
- if (cb->fn_known_sch)
- rc = cb->fn_known_sch(sch, cb->data);
- put_device(&sch->dev);
- } else {
- if (cb->fn_unknown_sch)
- rc = cb->fn_unknown_sch(schid, cb->data);
- }
- return rc;
- }
- int for_each_subchannel_staged(int (*fn_known)(struct subchannel *, void *),
- int (*fn_unknown)(struct subchannel_id,
- void *), void *data)
- {
- struct cb_data cb;
- int rc;
- cb.data = data;
- cb.fn_known_sch = fn_known;
- cb.fn_unknown_sch = fn_unknown;
- cb.set = idset_sch_new();
- if (!cb.set)
- /* fall back to brute force scanning in case of oom */
- return for_each_subchannel(call_fn_all_sch, &cb);
- idset_fill(cb.set);
- /* Process registered subchannels. */
- rc = bus_for_each_dev(&css_bus_type, NULL, &cb, call_fn_known_sch);
- if (rc)
- goto out;
- /* Process unregistered subchannels. */
- if (fn_unknown)
- rc = for_each_subchannel(call_fn_unknown_sch, &cb);
- out:
- idset_free(cb.set);
- return rc;
- }
- static void css_sch_todo(struct work_struct *work);
- static struct subchannel *
- css_alloc_subchannel(struct subchannel_id schid)
- {
- struct subchannel *sch;
- int ret;
- sch = kmalloc (sizeof (*sch), GFP_KERNEL | GFP_DMA);
- if (sch == NULL)
- return ERR_PTR(-ENOMEM);
- ret = cio_validate_subchannel (sch, schid);
- if (ret < 0) {
- kfree(sch);
- return ERR_PTR(ret);
- }
- INIT_WORK(&sch->todo_work, css_sch_todo);
- return sch;
- }
- static void
- css_subchannel_release(struct device *dev)
- {
- struct subchannel *sch;
- sch = to_subchannel(dev);
- if (!cio_is_console(sch->schid)) {
- /* Reset intparm to zeroes. */
- sch->config.intparm = 0;
- cio_commit_config(sch);
- kfree(sch->lock);
- kfree(sch);
- }
- }
- static int css_sch_device_register(struct subchannel *sch)
- {
- int ret;
- mutex_lock(&sch->reg_mutex);
- dev_set_name(&sch->dev, "0.%x.%04x", sch->schid.ssid,
- sch->schid.sch_no);
- ret = device_register(&sch->dev);
- mutex_unlock(&sch->reg_mutex);
- return ret;
- }
- /**
- * css_sch_device_unregister - unregister a subchannel
- * @sch: subchannel to be unregistered
- */
- void css_sch_device_unregister(struct subchannel *sch)
- {
- mutex_lock(&sch->reg_mutex);
- if (device_is_registered(&sch->dev))
- device_unregister(&sch->dev);
- mutex_unlock(&sch->reg_mutex);
- }
- EXPORT_SYMBOL_GPL(css_sch_device_unregister);
- static void css_sch_todo(struct work_struct *work)
- {
- struct subchannel *sch;
- enum sch_todo todo;
- sch = container_of(work, struct subchannel, todo_work);
- /* Find out todo. */
- spin_lock_irq(sch->lock);
- todo = sch->todo;
- CIO_MSG_EVENT(4, "sch_todo: sch=0.%x.%04x, todo=%d\n", sch->schid.ssid,
- sch->schid.sch_no, todo);
- sch->todo = SCH_TODO_NOTHING;
- spin_unlock_irq(sch->lock);
- /* Perform todo. */
- if (todo == SCH_TODO_UNREG)
- css_sch_device_unregister(sch);
- /* Release workqueue ref. */
- put_device(&sch->dev);
- }
- /**
- * css_sched_sch_todo - schedule a subchannel operation
- * @sch: subchannel
- * @todo: todo
- *
- * Schedule the operation identified by @todo to be performed on the slow path
- * workqueue. Do nothing if another operation with higher priority is already
- * scheduled. Needs to be called with subchannel lock held.
- */
- void css_sched_sch_todo(struct subchannel *sch, enum sch_todo todo)
- {
- CIO_MSG_EVENT(4, "sch_todo: sched sch=0.%x.%04x todo=%d\n",
- sch->schid.ssid, sch->schid.sch_no, todo);
- if (sch->todo >= todo)
- return;
- /* Get workqueue ref. */
- if (!get_device(&sch->dev))
- return;
- sch->todo = todo;
- if (!queue_work(cio_work_q, &sch->todo_work)) {
- /* Already queued, release workqueue ref. */
- put_device(&sch->dev);
- }
- }
- static void ssd_from_pmcw(struct chsc_ssd_info *ssd, struct pmcw *pmcw)
- {
- int i;
- int mask;
-