1415 lines · 32.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.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 pr_fmt(fmt) "cio: " fmt #include <linux/export.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/proc_fs.h>#include <linux/genalloc.h>#include <linux/dma-mapping.h>#include <asm/isc.h>#include <asm/crw.h> #include "css.h"#include "cio.h"#include "blacklist.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; #define MAX_CSS_IDX 0struct channel_subsystem *channel_subsystems[MAX_CSS_IDX + 1];static const struct bus_type css_bus_type; intfor_each_subchannel(int(*fn)(struct subchannel_id, void *), void *data){	struct subchannel_id schid;	int ret; 	init_subchannel_id(&schid);	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; 	if (cb->set)		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; 	if (fn_known && !fn_unknown) {		/* Skip idset allocation in case of known-only loop. */		cb.set = NULL;		return bus_for_each_dev(&css_bus_type, NULL, &cb,					call_fn_known_sch);	} 	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 void css_sch_create_locks(struct subchannel *sch){	spin_lock_init(&sch->lock);	mutex_init(&sch->reg_mutex);} static void css_subchannel_release(struct device *dev){	struct subchannel *sch = to_subchannel(dev); 	sch->config.intparm = 0;	cio_commit_config(sch);	kfree(sch);} static int css_validate_subchannel(struct subchannel_id schid,				   struct schib *schib){	int err; 	switch (schib->pmcw.st) {	case SUBCHANNEL_TYPE_IO:	case SUBCHANNEL_TYPE_MSG:		if (!css_sch_is_valid(schib))			err = -ENODEV;		else if (is_blacklisted(schid.ssid, schib->pmcw.dev)) {			CIO_MSG_EVENT(6, "Blacklisted device detected "				      "at devno %04X, subchannel set %x\n",				      schib->pmcw.dev, schid.ssid);			err = -ENODEV;		} else			err = 0;		break;	default:		err = 0;	}	if (err)		goto out; 	CIO_MSG_EVENT(4, "Subchannel 0.%x.%04x reports subchannel type %04X\n",		      schid.ssid, schid.sch_no, schib->pmcw.st);out:	return err;} struct subchannel *css_alloc_subchannel(struct subchannel_id schid,					struct schib *schib){	struct subchannel *sch;	int ret; 	ret = css_validate_subchannel(schid, schib);	if (ret < 0)		return ERR_PTR(ret); 	sch = kzalloc_obj(*sch, GFP_KERNEL | GFP_DMA);	if (!sch)		return ERR_PTR(-ENOMEM); 	sch->schid = schid;	sch->schib = *schib;	sch->st = schib->pmcw.st; 	css_sch_create_locks(sch); 	INIT_WORK(&sch->todo_work, css_sch_todo);	sch->dev.release = &css_subchannel_release;	sch->dev.dma_mask = &sch->dma_mask;	device_initialize(&sch->dev);	/*	 * The physical addresses for some of the dma structures that can	 * belong to a subchannel need to fit 31 bit width (e.g. ccw).	 */	ret = dma_set_coherent_mask(&sch->dev, DMA_BIT_MASK(31));	if (ret)		goto err;	/*	 * But we don't have such restrictions imposed on the stuff that	 * is handled by the streaming API.	 */	ret = dma_set_mask(&sch->dev, DMA_BIT_MASK(64));	if (ret)		goto err; 	return sch; err:	put_device(&sch->dev);	return ERR_PTR(ret);} 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_add(&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 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)			chp_new(ssd->chpid[i]);	}} void css_update_ssd_info(struct subchannel *sch){	int ret; 	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 ssize_t type_show(struct device *dev, struct device_attribute *attr,			 char *buf){	struct subchannel *sch = to_subchannel(dev); 	return sysfs_emit(buf, "%01x\n", sch->st);} static DEVICE_ATTR_RO(type); static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,			     char *buf){	struct subchannel *sch = to_subchannel(dev); 	return sysfs_emit(buf, "css:t%01X\n", sch->st);} static DEVICE_ATTR_RO(modalias); static struct attribute *subch_attrs[] = {	&dev_attr_type.attr,	&dev_attr_modalias.attr,	NULL,}; static struct attribute_group subch_attr_group = {	.attrs = subch_attrs,}; static const struct attribute_group *default_subch_attr_groups[] = {	&subch_attr_group,	NULL,}; static ssize_t chpids_show(struct device *dev,			   struct device_attribute *attr,			   char *buf){	struct subchannel *sch = to_subchannel(dev);	struct chsc_ssd_info *ssd = &sch->ssd_info;	ssize_t ret = 0;	int mask;	int chp; 	for (chp = 0; chp < 8; chp++) {		mask = 0x80 >> chp;		if (ssd->path_mask & mask)			ret += sysfs_emit_at(buf, ret, "%02x ", ssd->chpid[chp].id);		else			ret += sysfs_emit_at(buf, ret, "00 ");	}	ret += sysfs_emit_at(buf, ret, "\n");	return ret;}static DEVICE_ATTR_RO(chpids); static ssize_t pimpampom_show(struct device *dev,			      struct device_attribute *attr,			      char *buf){	struct subchannel *sch = to_subchannel(dev);	struct pmcw *pmcw = &sch->schib.pmcw; 	return sysfs_emit(buf, "%02x %02x %02x\n",			  pmcw->pim, pmcw->pam, pmcw->pom);}static DEVICE_ATTR_RO(pimpampom); static ssize_t dev_busid_show(struct device *dev,			      struct device_attribute *attr,			      char *buf){	struct subchannel *sch = to_subchannel(dev);	struct pmcw *pmcw = &sch->schib.pmcw; 	if ((pmcw->st == SUBCHANNEL_TYPE_IO && pmcw->dnv) ||	    (pmcw->st == SUBCHANNEL_TYPE_MSG && pmcw->w))		return sysfs_emit(buf, "0.%x.%04x\n", sch->schid.ssid,				  pmcw->dev);	else		return sysfs_emit(buf, "none\n");}static DEVICE_ATTR_RO(dev_busid); static struct attribute *io_subchannel_type_attrs[] = {	&dev_attr_chpids.attr,	&dev_attr_pimpampom.attr,	&dev_attr_dev_busid.attr,	NULL,};ATTRIBUTE_GROUPS(io_subchannel_type); static const struct device_type io_subchannel_type = {	.groups = io_subchannel_type_groups,}; 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.groups = default_subch_attr_groups; 	if (sch->st == SUBCHANNEL_TYPE_IO)		sch->dev.type = &io_subchannel_type; 	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, struct schib *schib){	struct subchannel *sch;	int ret; 	sch = css_alloc_subchannel(schid, schib);	if (IS_ERR(sch))		return PTR_ERR(sch); 	ret = css_register_subchannel(sch);	if (ret)		put_device(&sch->dev); 	return ret;} static intcheck_subchannel(struct device *dev, const void *data){	struct subchannel *sch;	struct subchannel_id *schid = (void *)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;} /** * css_sch_is_valid() - check if a subchannel is valid * @schib: subchannel information block for the subchannel */int css_sch_is_valid(struct schib *schib){	if ((schib->pmcw.st == SUBCHANNEL_TYPE_IO) && !schib->pmcw.dnv)		return 0;	if ((schib->pmcw.st == SUBCHANNEL_TYPE_MSG) && !schib->pmcw.w)		return 0;	return 1;}EXPORT_SYMBOL_GPL(css_sch_is_valid); static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow){	struct schib schib;	int ccode; 	if (!slow) {		/* Will be done on the slow path. */		return -EAGAIN;	}	/*	 * The first subchannel that is not-operational (ccode==3)	 * indicates that there aren't any more devices available.	 * If stsch gets an exception, it means the current subchannel set	 * is not valid.	 */	ccode = stsch(schid, &schib);	if (ccode)		return (ccode == 3) ? -ENXIO : ccode; 	return css_probe_device(schid, &schib);} static int css_evaluate_known_subchannel(struct subchannel *sch, int slow){	int ret = 0;

Lines 1–500 of 1415.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t