css.c

来自「linux 内核源代码」· C语言 代码 · 共 841 行 · 第 1/2 页

C
841
字号
/* *  drivers/s390/cio/css.c *  driver for channel subsystem * *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, *			 IBM Corporation *    Author(s): Arnd Bergmann (arndb@de.ibm.com) *		 Cornelia Huck (cornelia.huck@de.ibm.com) */#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 "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;static int need_reprobe = 0;static int max_ssid = 0;struct channel_subsystem *channel_subsystems[__MAX_CSSID + 1];int css_characteristics_avail = 0;intfor_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;}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);	}	if (sch->st != SUBCHANNEL_TYPE_IO) {		/* For now we ignore all non-io subchannels. */		kfree(sch);		return ERR_PTR(-EINVAL);	}	/* 	 * Set intparm to subchannel address.	 * This is fine even on 64bit since the subchannel is always located	 * under 2G.	 */	sch->schib.pmcw.intparm = (__u32)(unsigned long)sch;	ret = cio_modify(sch);	if (ret) {		kfree(sch->lock);		kfree(sch);		return ERR_PTR(ret);	}	return sch;}static voidcss_free_subchannel(struct subchannel *sch){	if (sch) {		/* Reset intparm to zeroes. */		sch->schib.pmcw.intparm = 0;		cio_modify(sch);		kfree(sch->lock);		kfree(sch);	}}static voidcss_subchannel_release(struct device *dev){	struct subchannel *sch;	sch = to_subchannel(dev);	if (!cio_is_console(sch->schid)) {		kfree(sch->lock);		kfree(sch);	}}static int css_sch_device_register(struct subchannel *sch){	int ret;	mutex_lock(&sch->reg_mutex);	ret = device_register(&sch->dev);	mutex_unlock(&sch->reg_mutex);	return ret;}void css_sch_device_unregister(struct subchannel *sch){	mutex_lock(&sch->reg_mutex);	device_unregister(&sch->dev);	mutex_unlock(&sch->reg_mutex);}static void ssd_from_pmcw(struct chsc_ssd_info *ssd, struct pmcw *pmcw){	int i;	int mask;	memset(ssd, 0, sizeof(struct chsc_ssd_info));	ssd->path_mask = pmcw->pim;	for (i = 0; i < 8; i++) {		mask = 0x80 >> i;		if (pmcw->pim & mask) {			chp_id_init(&ssd->chpid[i]);			ssd->chpid[i].id = pmcw->chpid[i];		}	}}static void ssd_register_chpids(struct chsc_ssd_info *ssd){	int i;	int mask;	for (i = 0; i < 8; i++) {		mask = 0x80 >> i;		if (ssd->path_mask & mask)			if (!chp_is_registered(ssd->chpid[i]))				chp_new(ssd->chpid[i]);	}}void css_update_ssd_info(struct subchannel *sch){	int ret;	if (cio_is_console(sch->schid)) {		/* Console is initialized too early for functions requiring		 * memory allocation. */		ssd_from_pmcw(&sch->ssd_info, &sch->schib.pmcw);	} else {		ret = chsc_get_ssd_info(sch->schid, &sch->ssd_info);		if (ret)			ssd_from_pmcw(&sch->ssd_info, &sch->schib.pmcw);		ssd_register_chpids(&sch->ssd_info);	}}static int css_register_subchannel(struct subchannel *sch){	int ret;	/* Initialize the subchannel structure */	sch->dev.parent = &channel_subsystems[0]->device;	sch->dev.bus = &css_bus_type;	sch->dev.release = &css_subchannel_release;	sch->dev.groups = subch_attr_groups;	/*	 * We don't want to generate uevents for I/O subchannels that don't	 * have a working ccw device behind them since they will be	 * unregistered before they can be used anyway, so we delay the add	 * uevent until after device recognition was successful.	 */	if (!cio_is_console(sch->schid))		/* Console is special, no need to suppress. */		sch->dev.uevent_suppress = 1;	css_update_ssd_info(sch);	/* make it known to the system */	ret = css_sch_device_register(sch);	if (ret) {		CIO_MSG_EVENT(0, "Could not register sch 0.%x.%04x: %d\n",			      sch->schid.ssid, sch->schid.sch_no, ret);		return ret;	}	return ret;}static int css_probe_device(struct subchannel_id schid){	int ret;	struct subchannel *sch;	sch = css_alloc_subchannel(schid);	if (IS_ERR(sch))		return PTR_ERR(sch);	ret = css_register_subchannel(sch);	if (ret)		css_free_subchannel(sch);	return ret;}static intcheck_subchannel(struct device * dev, void * data){	struct subchannel *sch;	struct subchannel_id *schid = data;	sch = to_subchannel(dev);	return schid_equal(&sch->schid, schid);}struct subchannel *get_subchannel_by_schid(struct subchannel_id schid){	struct device *dev;	dev = bus_find_device(&css_bus_type, NULL,			      &schid, check_subchannel);	return dev ? to_subchannel(dev) : NULL;}static int css_get_subchannel_status(struct subchannel *sch){	struct schib schib;	if (stsch(sch->schid, &schib) || !schib.pmcw.dnv)		return CIO_GONE;	if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev))		return CIO_REVALIDATE;	if (!sch->lpm)		return CIO_NO_PATH;	return CIO_OPER;}static int css_evaluate_known_subchannel(struct subchannel *sch, int slow){	int event, ret, disc;	unsigned long flags;	enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE } action;	spin_lock_irqsave(sch->lock, flags);	disc = device_is_disconnected(sch);	if (disc && slow) {		/* Disconnected devices are evaluated directly only.*/		spin_unlock_irqrestore(sch->lock, flags);		return 0;	}	/* No interrupt after machine check - kill pending timers. */	device_kill_pending_timer(sch);	if (!disc && !slow) {		/* Non-disconnected devices are evaluated on the slow path. */		spin_unlock_irqrestore(sch->lock, flags);		return -EAGAIN;	}	event = css_get_subchannel_status(sch);	CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n",		      sch->schid.ssid, sch->schid.sch_no, event,		      disc ? "disconnected" : "normal",		      slow ? "slow" : "fast");	/* Analyze subchannel status. */	action = NONE;	switch (event) {	case CIO_NO_PATH:		if (disc) {			/* Check if paths have become available. */			action = REPROBE;			break;		}		/* fall through */	case CIO_GONE:		/* Prevent unwanted effects when opening lock. */		cio_disable_subchannel(sch);		device_set_disconnected(sch);		/* Ask driver what to do with device. */		action = UNREGISTER;		if (sch->driver && sch->driver->notify) {			spin_unlock_irqrestore(sch->lock, flags);			ret = sch->driver->notify(&sch->dev, event);			spin_lock_irqsave(sch->lock, flags);			if (ret)				action = NONE;		}		break;	case CIO_REVALIDATE:		/* Device will be removed, so no notify necessary. */		if (disc)			/* Reprobe because immediate unregister might block. */			action = REPROBE;		else			action = UNREGISTER_PROBE;		break;	case CIO_OPER:		if (disc)			/* Get device operational again. */			action = REPROBE;		break;	}	/* Perform action. */	ret = 0;	switch (action) {	case UNREGISTER:	case UNREGISTER_PROBE:		/* Unregister device (will use subchannel lock). */		spin_unlock_irqrestore(sch->lock, flags);		css_sch_device_unregister(sch);		spin_lock_irqsave(sch->lock, flags);		/* Reset intparm to zeroes. */		sch->schib.pmcw.intparm = 0;		cio_modify(sch);		break;	case REPROBE:		device_trigger_reprobe(sch);		break;	default:		break;	}	spin_unlock_irqrestore(sch->lock, flags);	/* Probe if necessary. */	if (action == UNREGISTER_PROBE)		ret = css_probe_device(sch->schid);	return ret;}static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow){	struct schib schib;	if (!slow) {		/* Will be done on the slow path. */		return -EAGAIN;	}	if (stsch_err(schid, &schib) || !schib.pmcw.dnv) {		/* Unusable - ignore. */		return 0;	}	CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, unknown, "			 "slow path.\n", schid.ssid, schid.sch_no, CIO_OPER);	return css_probe_device(schid);}static void css_evaluate_subchannel(struct subchannel_id schid, int slow){	struct subchannel *sch;	int ret;	sch = get_subchannel_by_schid(schid);	if (sch) {		ret = css_evaluate_known_subchannel(sch, slow);		put_device(&sch->dev);	} else		ret = css_evaluate_new_subchannel(schid, slow);	if (ret == -EAGAIN)		css_schedule_eval(schid);}static struct idset *slow_subchannel_set;static spinlock_t slow_subchannel_lock;static int __init slow_subchannel_init(void){	spin_lock_init(&slow_subchannel_lock);	slow_subchannel_set = idset_sch_new();	if (!slow_subchannel_set) {		CIO_MSG_EVENT(0, "could not allocate slow subchannel set\n");		return -ENOMEM;	}	return 0;}static void css_slow_path_func(struct work_struct *unused){	struct subchannel_id schid;	CIO_TRACE_EVENT(4, "slowpath");	spin_lock_irq(&slow_subchannel_lock);	init_subchannel_id(&schid);	while (idset_sch_get_first(slow_subchannel_set, &schid)) {		idset_sch_del(slow_subchannel_set, schid);		spin_unlock_irq(&slow_subchannel_lock);		css_evaluate_subchannel(schid, 1);		spin_lock_irq(&slow_subchannel_lock);	}	spin_unlock_irq(&slow_subchannel_lock);}static DECLARE_WORK(slow_path_work, css_slow_path_func);struct workqueue_struct *slow_path_wq;void css_schedule_eval(struct subchannel_id schid){	unsigned long flags;	spin_lock_irqsave(&slow_subchannel_lock, flags);	idset_sch_add(slow_subchannel_set, schid);	queue_work(slow_path_wq, &slow_path_work);	spin_unlock_irqrestore(&slow_subchannel_lock, flags);}void css_schedule_eval_all(void){

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?