Update Linux to v5.10.109
Sourced from [1]
[1] https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.109.tar.xz
Change-Id: I19bca9fc6762d4e63bcf3e4cba88bbe560d9c76c
Signed-off-by: Olivier Deprez <olivier.deprez@arm.com>
diff --git a/drivers/s390/cio/Makefile b/drivers/s390/cio/Makefile
index f6a8db0..a9235f1 100644
--- a/drivers/s390/cio/Makefile
+++ b/drivers/s390/cio/Makefile
@@ -5,7 +5,7 @@
# The following is required for define_trace.h to find ./trace.h
CFLAGS_trace.o := -I$(src)
-CFLAGS_vfio_ccw_fsm.o := -I$(src)
+CFLAGS_vfio_ccw_trace.o := -I$(src)
obj-y += airq.o blacklist.o chsc.o cio.o css.o chp.o idset.o isc.o \
fcx.o itcw.o crw.o ccwreq.o trace.o ioasm.o
@@ -21,5 +21,5 @@
obj-$(CONFIG_QDIO) += qdio.o
vfio_ccw-objs += vfio_ccw_drv.o vfio_ccw_cp.o vfio_ccw_ops.o vfio_ccw_fsm.o \
- vfio_ccw_async.o
+ vfio_ccw_async.o vfio_ccw_trace.o vfio_ccw_chp.o
obj-$(CONFIG_VFIO_CCW) += vfio_ccw.o
diff --git a/drivers/s390/cio/blacklist.c b/drivers/s390/cio/blacklist.c
index 9cebff8..4dd2eb6 100644
--- a/drivers/s390/cio/blacklist.c
+++ b/drivers/s390/cio/blacklist.c
@@ -399,12 +399,12 @@
sizeof(struct ccwdev_iter));
}
-static const struct file_operations cio_ignore_proc_fops = {
- .open = cio_ignore_proc_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = seq_release_private,
- .write = cio_ignore_write,
+static const struct proc_ops cio_ignore_proc_ops = {
+ .proc_open = cio_ignore_proc_open,
+ .proc_read = seq_read,
+ .proc_lseek = seq_lseek,
+ .proc_release = seq_release_private,
+ .proc_write = cio_ignore_write,
};
static int
@@ -413,7 +413,7 @@
struct proc_dir_entry *entry;
entry = proc_create("cio_ignore", S_IFREG | S_IRUGO | S_IWUSR, NULL,
- &cio_ignore_proc_fops);
+ &cio_ignore_proc_ops);
if (!entry)
return -ENOENT;
return 0;
diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c
index b42a937..483a9ec 100644
--- a/drivers/s390/cio/ccwgroup.c
+++ b/drivers/s390/cio/ccwgroup.c
@@ -485,79 +485,10 @@
gdrv->shutdown(gdev);
}
-static int ccwgroup_pm_prepare(struct device *dev)
-{
- struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
- struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
-
- /* Fail while device is being set online/offline. */
- if (atomic_read(&gdev->onoff))
- return -EAGAIN;
-
- if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
- return 0;
-
- return gdrv->prepare ? gdrv->prepare(gdev) : 0;
-}
-
-static void ccwgroup_pm_complete(struct device *dev)
-{
- struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
- struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
-
- if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
- return;
-
- if (gdrv->complete)
- gdrv->complete(gdev);
-}
-
-static int ccwgroup_pm_freeze(struct device *dev)
-{
- struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
- struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
-
- if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
- return 0;
-
- return gdrv->freeze ? gdrv->freeze(gdev) : 0;
-}
-
-static int ccwgroup_pm_thaw(struct device *dev)
-{
- struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
- struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
-
- if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
- return 0;
-
- return gdrv->thaw ? gdrv->thaw(gdev) : 0;
-}
-
-static int ccwgroup_pm_restore(struct device *dev)
-{
- struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
- struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
-
- if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
- return 0;
-
- return gdrv->restore ? gdrv->restore(gdev) : 0;
-}
-
-static const struct dev_pm_ops ccwgroup_pm_ops = {
- .prepare = ccwgroup_pm_prepare,
- .complete = ccwgroup_pm_complete,
- .freeze = ccwgroup_pm_freeze,
- .thaw = ccwgroup_pm_thaw,
- .restore = ccwgroup_pm_restore,
-};
-
static struct bus_type ccwgroup_bus_type = {
.name = "ccwgroup",
.remove = ccwgroup_remove,
.shutdown = ccwgroup_shutdown,
- .pm = &ccwgroup_pm_ops,
};
bool dev_is_ccwgroup(struct device *dev)
diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c
index 1fd982b..93e2278 100644
--- a/drivers/s390/cio/chp.c
+++ b/drivers/s390/cio/chp.c
@@ -135,7 +135,7 @@
struct channel_path *chp;
struct device *device;
- device = container_of(kobj, struct device, kobj);
+ device = kobj_to_dev(kobj);
chp = to_channelpath(device);
if (chp->cmg == -1)
return 0;
@@ -184,7 +184,7 @@
struct device *device;
unsigned int size;
- device = container_of(kobj, struct device, kobj);
+ device = kobj_to_dev(kobj);
chp = to_channelpath(device);
css = to_css(chp->dev.parent);
diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c
index 980a78d..93aa7ea 100644
--- a/drivers/s390/cio/chsc.c
+++ b/drivers/s390/cio/chsc.c
@@ -57,6 +57,7 @@
case 0x0104:
return -EINVAL;
case 0x0004:
+ case 0x0106: /* "Wrong Channel Parm" for the op 0x003d */
return -EOPNOTSUPP;
case 0x000b:
case 0x0107: /* "Channel busy" for the op 0x003d */
@@ -64,6 +65,8 @@
case 0x0100:
case 0x0102:
return -ENOMEM;
+ case 0x0108: /* "HW limit exceeded" for the op 0x003d */
+ return -EUSERS;
default:
return -EIO;
}
@@ -180,11 +183,12 @@
* @scssc: request and response block for SADC
* @summary_indicator_addr: summary indicator address
* @subchannel_indicator_addr: subchannel indicator address
+ * @isc: Interruption Subclass for this subchannel
*
* Returns 0 on success.
*/
int chsc_sadc(struct subchannel_id schid, struct chsc_scssc_area *scssc,
- u64 summary_indicator_addr, u64 subchannel_indicator_addr)
+ u64 summary_indicator_addr, u64 subchannel_indicator_addr, u8 isc)
{
memset(scssc, 0, sizeof(*scssc));
scssc->request.length = 0x0fe0;
@@ -196,7 +200,7 @@
scssc->ks = PAGE_DEFAULT_KEY >> 4;
scssc->kc = PAGE_DEFAULT_KEY >> 4;
- scssc->isc = QDIO_AIRQ_ISC;
+ scssc->isc = isc;
scssc->schid = schid;
/* enable the time delay disablement facility */
@@ -1110,7 +1114,7 @@
return ret;
}
-int __init chsc_get_cssid(int idx)
+int __init chsc_get_cssid_iid(int idx, u8 *cssid, u8 *iid)
{
struct {
struct chsc_header request;
@@ -1121,7 +1125,8 @@
u32 reserved2[3];
struct {
u8 cssid;
- u32 : 24;
+ u8 iid;
+ u32 : 16;
} list[0];
} *sdcal_area;
int ret;
@@ -1147,8 +1152,10 @@
}
if ((addr_t) &sdcal_area->list[idx] <
- (addr_t) &sdcal_area->response + sdcal_area->response.length)
- ret = sdcal_area->list[idx].cssid;
+ (addr_t) &sdcal_area->response + sdcal_area->response.length) {
+ *cssid = sdcal_area->list[idx].cssid;
+ *iid = sdcal_area->list[idx].iid;
+ }
else
ret = -ENODEV;
exit:
@@ -1256,6 +1263,27 @@
return (rr->response.code == 0x0001) ? 0 : -EIO;
}
+int chsc_stzi(void *page, void *result, size_t size)
+{
+ struct {
+ struct chsc_header request;
+ unsigned int rsvd0[3];
+ struct chsc_header response;
+ char data[];
+ } *rr;
+ int rc;
+
+ memset(page, 0, PAGE_SIZE);
+ rr = page;
+ rr->request.length = 0x0010;
+ rr->request.code = 0x003e;
+ rc = chsc(rr);
+ if (rc)
+ return -EIO;
+ memcpy(result, &rr->data, size);
+ return (rr->response.code == 0x0001) ? 0 : -EIO;
+}
+
int chsc_siosl(struct subchannel_id schid)
{
struct {
@@ -1333,36 +1361,34 @@
EXPORT_SYMBOL_GPL(chsc_scm_info);
/**
- * chsc_pnso_brinfo() - Perform Network-Subchannel Operation, Bridge Info.
+ * chsc_pnso() - Perform Network-Subchannel Operation
* @schid: id of the subchannel on which PNSO is performed
- * @brinfo_area: request and response block for the operation
+ * @pnso_area: request and response block for the operation
+ * @oc: Operation Code
* @resume_token: resume token for multiblock response
* @cnc: Boolean change-notification control
*
- * brinfo_area must be allocated by the caller with get_zeroed_page(GFP_KERNEL)
+ * pnso_area must be allocated by the caller with get_zeroed_page(GFP_KERNEL)
*
* Returns 0 on success.
*/
-int chsc_pnso_brinfo(struct subchannel_id schid,
- struct chsc_pnso_area *brinfo_area,
- struct chsc_brinfo_resume_token resume_token,
- int cnc)
+int chsc_pnso(struct subchannel_id schid, struct chsc_pnso_area *pnso_area,
+ u8 oc, struct chsc_pnso_resume_token resume_token, int cnc)
{
- memset(brinfo_area, 0, sizeof(*brinfo_area));
- brinfo_area->request.length = 0x0030;
- brinfo_area->request.code = 0x003d; /* network-subchannel operation */
- brinfo_area->m = schid.m;
- brinfo_area->ssid = schid.ssid;
- brinfo_area->sch = schid.sch_no;
- brinfo_area->cssid = schid.cssid;
- brinfo_area->oc = 0; /* Store-network-bridging-information list */
- brinfo_area->resume_token = resume_token;
- brinfo_area->n = (cnc != 0);
- if (chsc(brinfo_area))
+ memset(pnso_area, 0, sizeof(*pnso_area));
+ pnso_area->request.length = 0x0030;
+ pnso_area->request.code = 0x003d; /* network-subchannel operation */
+ pnso_area->m = schid.m;
+ pnso_area->ssid = schid.ssid;
+ pnso_area->sch = schid.sch_no;
+ pnso_area->cssid = schid.cssid;
+ pnso_area->oc = oc;
+ pnso_area->resume_token = resume_token;
+ pnso_area->n = (cnc != 0);
+ if (chsc(pnso_area))
return -EIO;
- return chsc_error_from_response(brinfo_area->response.code);
+ return chsc_error_from_response(pnso_area->response.code);
}
-EXPORT_SYMBOL_GPL(chsc_pnso_brinfo);
int chsc_sgib(u32 origin)
{
diff --git a/drivers/s390/cio/chsc.h b/drivers/s390/cio/chsc.h
index e57d68e..c2b83b6 100644
--- a/drivers/s390/cio/chsc.h
+++ b/drivers/s390/cio/chsc.h
@@ -163,7 +163,8 @@
int chsc_get_channel_measurement_chars(struct channel_path *chp);
int chsc_ssqd(struct subchannel_id schid, struct chsc_ssqd_area *ssqd);
int chsc_sadc(struct subchannel_id schid, struct chsc_scssc_area *scssc,
- u64 summary_indicator_addr, u64 subchannel_indicator_addr);
+ u64 summary_indicator_addr, u64 subchannel_indicator_addr,
+ u8 isc);
int chsc_sgib(u32 origin);
int chsc_error_from_response(int response);
@@ -204,54 +205,10 @@
int chsc_scm_info(struct chsc_scm_info *scm_area, u64 token);
-struct chsc_brinfo_resume_token {
- u64 t1;
- u64 t2;
-} __packed;
+int chsc_pnso(struct subchannel_id schid, struct chsc_pnso_area *pnso_area,
+ u8 oc, struct chsc_pnso_resume_token resume_token, int cnc);
-struct chsc_brinfo_naihdr {
- struct chsc_brinfo_resume_token resume_token;
- u32:32;
- u32 instance;
- u32:24;
- u8 naids;
- u32 reserved[3];
-} __packed;
-
-struct chsc_pnso_area {
- struct chsc_header request;
- u8:2;
- u8 m:1;
- u8:5;
- u8:2;
- u8 ssid:2;
- u8 fmt:4;
- u16 sch;
- u8:8;
- u8 cssid;
- u16:16;
- u8 oc;
- u32:24;
- struct chsc_brinfo_resume_token resume_token;
- u32 n:1;
- u32:31;
- u32 reserved[3];
- struct chsc_header response;
- u32:32;
- struct chsc_brinfo_naihdr naihdr;
- union {
- struct qdio_brinfo_entry_l3_ipv6 l3_ipv6[0];
- struct qdio_brinfo_entry_l3_ipv4 l3_ipv4[0];
- struct qdio_brinfo_entry_l2 l2[0];
- } entries;
-} __packed __aligned(PAGE_SIZE);
-
-int chsc_pnso_brinfo(struct subchannel_id schid,
- struct chsc_pnso_area *brinfo_area,
- struct chsc_brinfo_resume_token resume_token,
- int cnc);
-
-int __init chsc_get_cssid(int idx);
+int __init chsc_get_cssid_iid(int idx, u8 *cssid, u8 *iid);
#ifdef CONFIG_SCM_BUS
int scm_update_information(void);
diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c
index 7950ac5..cf2c3c4 100644
--- a/drivers/s390/cio/css.c
+++ b/drivers/s390/cio/css.c
@@ -433,8 +433,8 @@
struct subchannel *sch = to_subchannel(dev);
struct pmcw *pmcw = &sch->schib.pmcw;
- if ((pmcw->st == SUBCHANNEL_TYPE_IO ||
- pmcw->st == SUBCHANNEL_TYPE_MSG) && pmcw->dnv)
+ 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
@@ -871,7 +871,7 @@
if (css_general_characteristics.mcss) {
css->global_pgid.pgid_high.ext_cssid.version = 0x80;
css->global_pgid.pgid_high.ext_cssid.cssid =
- (css->cssid < 0) ? 0 : css->cssid;
+ css->id_valid ? css->cssid : 0;
} else {
css->global_pgid.pgid_high.cpu_addr = stap();
}
@@ -894,7 +894,7 @@
{
struct channel_subsystem *css = to_css(dev);
- if (css->cssid < 0)
+ if (!css->id_valid)
return -EINVAL;
return sprintf(buf, "%x\n", css->cssid);
@@ -992,7 +992,12 @@
css->device.dma_mask = &css->device.coherent_dma_mask;
mutex_init(&css->mutex);
- css->cssid = chsc_get_cssid(nr);
+ ret = chsc_get_cssid_iid(nr, &css->cssid, &css->iid);
+ if (!ret) {
+ css->id_valid = true;
+ pr_info("Partition identifier %01x.%01x\n", css->cssid,
+ css->iid);
+ }
css_generate_pgid(css, (u32) (get_tod_clock() >> 32));
ret = device_register(&css->device);
@@ -1367,20 +1372,6 @@
}
subsys_initcall_sync(channel_subsystem_init_sync);
-void channel_subsystem_reinit(void)
-{
- struct channel_path *chp;
- struct chp_id chpid;
-
- chsc_enable_facility(CHSC_SDA_OC_MSS);
- chp_id_for_each(&chpid) {
- chp = chpid_to_chp(chpid);
- if (chp)
- chp_update_desc(chp);
- }
- cmf_reactivate();
-}
-
#ifdef CONFIG_PROC_FS
static ssize_t cio_settle_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
@@ -1394,18 +1385,17 @@
return ret ? ret : count;
}
-static const struct file_operations cio_settle_proc_fops = {
- .open = nonseekable_open,
- .write = cio_settle_write,
- .llseek = no_llseek,
+static const struct proc_ops cio_settle_proc_ops = {
+ .proc_open = nonseekable_open,
+ .proc_write = cio_settle_write,
+ .proc_lseek = no_llseek,
};
static int __init cio_settle_init(void)
{
struct proc_dir_entry *entry;
- entry = proc_create("cio_settle", S_IWUSR, NULL,
- &cio_settle_proc_fops);
+ entry = proc_create("cio_settle", S_IWUSR, NULL, &cio_settle_proc_ops);
if (!entry)
return -ENOMEM;
return 0;
diff --git a/drivers/s390/cio/css.h b/drivers/s390/cio/css.h
index 8d83290..3f322ea 100644
--- a/drivers/s390/cio/css.h
+++ b/drivers/s390/cio/css.h
@@ -115,7 +115,9 @@
void css_update_ssd_info(struct subchannel *sch);
struct channel_subsystem {
- int cssid;
+ u8 cssid;
+ u8 iid;
+ bool id_valid; /* cssid,iid */
struct channel_path *chps[__MAX_CHPID + 1];
struct device device;
struct pgid global_pgid;
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c
index 23e9227..33280ca 100644
--- a/drivers/s390/cio/device.c
+++ b/drivers/s390/cio/device.c
@@ -1267,7 +1267,7 @@
sch = to_subchannel(cdev->dev.parent);
if ((sch->schib.pmcw.pam & sch->opm) == sch->vpm)
break;
- /* fall through */
+ fallthrough;
case DEV_STATE_DISCONNECTED:
CIO_MSG_EVENT(3, "recovery: trigger 0.%x.%04x\n",
cdev->private->dev_id.ssid,
@@ -2096,7 +2096,7 @@
case CDEV_TODO_UNREG_EVAL:
if (!sch_is_pseudo_sch(sch))
css_schedule_eval(sch->schid);
- /* fall-through */
+ fallthrough;
case CDEV_TODO_UNREG:
if (sch_is_pseudo_sch(sch))
ccw_device_unregister(cdev);
diff --git a/drivers/s390/cio/device.h b/drivers/s390/cio/device.h
index f5c427e..853b6a8 100644
--- a/drivers/s390/cio/device.h
+++ b/drivers/s390/cio/device.h
@@ -96,7 +96,6 @@
int ccw_device_offline(struct ccw_device *);
void ccw_device_update_sense_data(struct ccw_device *);
int ccw_device_test_sense_data(struct ccw_device *);
-void ccw_device_schedule_sch_unregister(struct ccw_device *);
int ccw_purge_blacklisted(void);
void ccw_device_sched_todo(struct ccw_device *cdev, enum cdev_todo todo);
struct ccw_device *get_ccwdev_by_dev_id(struct ccw_dev_id *dev_id);
diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c
index ccecf6b..c533d1d 100644
--- a/drivers/s390/cio/device_ops.c
+++ b/drivers/s390/cio/device_ops.c
@@ -710,6 +710,114 @@
}
EXPORT_SYMBOL_GPL(ccw_device_get_schid);
+/**
+ * ccw_device_pnso() - Perform Network-Subchannel Operation
+ * @cdev: device on which PNSO is performed
+ * @pnso_area: request and response block for the operation
+ * @oc: Operation Code
+ * @resume_token: resume token for multiblock response
+ * @cnc: Boolean change-notification control
+ *
+ * pnso_area must be allocated by the caller with get_zeroed_page(GFP_KERNEL)
+ *
+ * Returns 0 on success.
+ */
+int ccw_device_pnso(struct ccw_device *cdev,
+ struct chsc_pnso_area *pnso_area, u8 oc,
+ struct chsc_pnso_resume_token resume_token, int cnc)
+{
+ struct subchannel_id schid;
+
+ ccw_device_get_schid(cdev, &schid);
+ return chsc_pnso(schid, pnso_area, oc, resume_token, cnc);
+}
+EXPORT_SYMBOL_GPL(ccw_device_pnso);
+
+/**
+ * ccw_device_get_cssid() - obtain Channel Subsystem ID
+ * @cdev: device to obtain the CSSID for
+ * @cssid: The resulting Channel Subsystem ID
+ */
+int ccw_device_get_cssid(struct ccw_device *cdev, u8 *cssid)
+{
+ struct device *sch_dev = cdev->dev.parent;
+ struct channel_subsystem *css = to_css(sch_dev->parent);
+
+ if (css->id_valid)
+ *cssid = css->cssid;
+ return css->id_valid ? 0 : -ENODEV;
+}
+EXPORT_SYMBOL_GPL(ccw_device_get_cssid);
+
+/**
+ * ccw_device_get_iid() - obtain MIF-image ID
+ * @cdev: device to obtain the MIF-image ID for
+ * @iid: The resulting MIF-image ID
+ */
+int ccw_device_get_iid(struct ccw_device *cdev, u8 *iid)
+{
+ struct device *sch_dev = cdev->dev.parent;
+ struct channel_subsystem *css = to_css(sch_dev->parent);
+
+ if (css->id_valid)
+ *iid = css->iid;
+ return css->id_valid ? 0 : -ENODEV;
+}
+EXPORT_SYMBOL_GPL(ccw_device_get_iid);
+
+/**
+ * ccw_device_get_chpid() - obtain Channel Path ID
+ * @cdev: device to obtain the Channel Path ID for
+ * @chp_idx: Index of the channel path
+ * @chpid: The resulting Channel Path ID
+ */
+int ccw_device_get_chpid(struct ccw_device *cdev, int chp_idx, u8 *chpid)
+{
+ struct subchannel *sch = to_subchannel(cdev->dev.parent);
+ int mask;
+
+ if ((chp_idx < 0) || (chp_idx > 7))
+ return -EINVAL;
+ mask = 0x80 >> chp_idx;
+ if (!(sch->schib.pmcw.pim & mask))
+ return -ENODEV;
+
+ *chpid = sch->schib.pmcw.chpid[chp_idx];
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ccw_device_get_chpid);
+
+/**
+ * ccw_device_get_chid() - obtain Channel ID associated with specified CHPID
+ * @cdev: device to obtain the Channel ID for
+ * @chp_idx: Index of the channel path
+ * @chid: The resulting Channel ID
+ */
+int ccw_device_get_chid(struct ccw_device *cdev, int chp_idx, u16 *chid)
+{
+ struct chp_id cssid_chpid;
+ struct channel_path *chp;
+ int rc;
+
+ chp_id_init(&cssid_chpid);
+ rc = ccw_device_get_chpid(cdev, chp_idx, &cssid_chpid.id);
+ if (rc)
+ return rc;
+ chp = chpid_to_chp(cssid_chpid);
+ if (!chp)
+ return -ENODEV;
+
+ mutex_lock(&chp->lock);
+ if (chp->desc_fmt1.flags & 0x10)
+ *chid = chp->desc_fmt1.chid;
+ else
+ rc = -ENODEV;
+ mutex_unlock(&chp->lock);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(ccw_device_get_chid);
+
/*
* Allocate zeroed dma coherent 31 bit addressable memory using
* the subchannels dma pool. Maximal size of allocation supported
@@ -717,13 +825,23 @@
*/
void *ccw_device_dma_zalloc(struct ccw_device *cdev, size_t size)
{
- return cio_gp_dma_zalloc(cdev->private->dma_pool, &cdev->dev, size);
+ void *addr;
+
+ if (!get_device(&cdev->dev))
+ return NULL;
+ addr = cio_gp_dma_zalloc(cdev->private->dma_pool, &cdev->dev, size);
+ if (IS_ERR_OR_NULL(addr))
+ put_device(&cdev->dev);
+ return addr;
}
EXPORT_SYMBOL(ccw_device_dma_zalloc);
void ccw_device_dma_free(struct ccw_device *cdev, void *cpu_addr, size_t size)
{
+ if (!cpu_addr)
+ return;
cio_gp_dma_free(cdev->private->dma_pool, cpu_addr, size);
+ put_device(&cdev->dev);
}
EXPORT_SYMBOL(ccw_device_dma_free);
diff --git a/drivers/s390/cio/idset.c b/drivers/s390/cio/idset.c
index 835de44..45f9c07 100644
--- a/drivers/s390/cio/idset.c
+++ b/drivers/s390/cio/idset.c
@@ -13,7 +13,7 @@
struct idset {
int num_ssid;
int num_id;
- unsigned long bitmap[0];
+ unsigned long bitmap[];
};
static inline unsigned long bitmap_size(int num_ssid, int num_id)
@@ -59,18 +59,6 @@
return test_bit(ssid * set->num_id + id, set->bitmap);
}
-static inline int idset_get_first(struct idset *set, int *ssid, int *id)
-{
- int bitnum;
-
- bitnum = find_first_bit(set->bitmap, set->num_ssid * set->num_id);
- if (bitnum >= set->num_ssid * set->num_id)
- return 0;
- *ssid = bitnum / set->num_id;
- *id = bitnum % set->num_id;
- return 1;
-}
-
struct idset *idset_sch_new(void)
{
return idset_new(max_ssid + 1, __MAX_SUBCHANNEL + 1);
diff --git a/drivers/s390/cio/qdio.h b/drivers/s390/cio/qdio.h
index 3b0a448..cd2df4f 100644
--- a/drivers/s390/cio/qdio.h
+++ b/drivers/s390/cio/qdio.h
@@ -15,7 +15,6 @@
#define QDIO_BUSY_BIT_PATIENCE (100 << 12) /* 100 microseconds */
#define QDIO_BUSY_BIT_RETRY_DELAY 10 /* 10 milliseconds */
#define QDIO_BUSY_BIT_RETRIES 1000 /* = 10s retry time */
-#define QDIO_INPUT_THRESHOLD (500 << 12) /* 500 microseconds */
enum qdio_irq_states {
QDIO_IRQ_STATE_INACTIVE,
@@ -82,6 +81,7 @@
#define QDIO_SIGA_WRITE 0x00
#define QDIO_SIGA_READ 0x01
#define QDIO_SIGA_SYNC 0x02
+#define QDIO_SIGA_WRITEM 0x03
#define QDIO_SIGA_WRITEQ 0x04
#define QDIO_SIGA_QEBSM_FLAG 0x80
@@ -165,34 +165,21 @@
} ____cacheline_aligned;
struct qdio_queue_perf_stat {
- /*
- * Sorted into order-2 buckets: 1, 2-3, 4-7, ... 64-127, 128.
- * Since max. 127 SBALs are scanned reuse entry for 128 as queue full
- * aka 127 SBALs found.
- */
+ /* Sorted into order-2 buckets: 1, 2-3, 4-7, ... 64-127, 128. */
unsigned int nr_sbals[8];
unsigned int nr_sbal_error;
unsigned int nr_sbal_nop;
unsigned int nr_sbal_total;
};
-enum qdio_queue_irq_states {
- QDIO_QUEUE_IRQS_DISABLED,
+enum qdio_irq_poll_states {
+ QDIO_IRQ_DISABLED,
};
struct qdio_input_q {
- /* input buffer acknowledgement flag */
- int polling;
- /* first ACK'ed buffer */
- int ack_start;
- /* how much sbals are acknowledged with qebsm */
- int ack_count;
- /* last time of noticing incoming data */
- u64 timestamp;
- /* upper-layer polling flag */
- unsigned long queue_irq_state;
- /* callback to start upper-layer polling */
- void (*queue_start_poll) (struct ccw_device *, int, unsigned long);
+ /* Batch of SBALs that we processed while polling the queue: */
+ unsigned int batch_start;
+ unsigned int batch_count;
};
struct qdio_output_q {
@@ -226,9 +213,6 @@
*/
int first_to_check;
- /* beginning position for calling the program */
- int first_to_kick;
-
/* number of buffers in use by the adapter */
atomic_t nr_buf_used;
@@ -252,13 +236,9 @@
/* input or output queue */
int is_input_q;
- /* list of thinint input queues */
- struct list_head entry;
-
/* upper-layer program handler */
qdio_handler_t (*handler);
- struct dentry *debugfs_q;
struct qdio_irq *irq_ptr;
struct sl *sl;
/*
@@ -272,8 +252,8 @@
struct qib qib;
u32 *dsci; /* address of device state change indicator */
struct ccw_device *cdev;
+ struct list_head entry; /* list of thinint devices */
struct dentry *debugfs_dev;
- struct dentry *debugfs_perf;
unsigned long int_parm;
struct subchannel_id schid;
@@ -301,6 +281,11 @@
struct qdio_q *input_qs[QDIO_MAX_QUEUES_PER_IRQ];
struct qdio_q *output_qs[QDIO_MAX_QUEUES_PER_IRQ];
+ unsigned int max_input_qs;
+ unsigned int max_output_qs;
+
+ void (*irq_poll)(struct ccw_device *cdev, unsigned long data);
+ unsigned long poll_state;
debug_info_t *debug_area;
struct mutex setup_mutex;
@@ -317,13 +302,15 @@
#define qperf(__qdev, __attr) ((__qdev)->perf_stat.(__attr))
-#define qperf_inc(__q, __attr) \
+#define QDIO_PERF_STAT_INC(__irq, __attr) \
({ \
- struct qdio_irq *qdev = (__q)->irq_ptr; \
+ struct qdio_irq *qdev = __irq; \
if (qdev->perf_stat_enabled) \
(qdev->perf_stat.__attr)++; \
})
+#define qperf_inc(__q, __attr) QDIO_PERF_STAT_INC((__q)->irq_ptr, __attr)
+
static inline void account_sbals_error(struct qdio_q *q, int count)
{
q->q_stats.nr_sbal_error += count;
@@ -355,14 +342,10 @@
for (i = 0; i < irq_ptr->nr_output_qs && \
({ q = irq_ptr->output_qs[i]; 1; }); i++)
-#define prev_buf(bufnr) \
- ((bufnr + QDIO_MAX_BUFFERS_MASK) & QDIO_MAX_BUFFERS_MASK)
-#define next_buf(bufnr) \
- ((bufnr + 1) & QDIO_MAX_BUFFERS_MASK)
-#define add_buf(bufnr, inc) \
- ((bufnr + inc) & QDIO_MAX_BUFFERS_MASK)
-#define sub_buf(bufnr, dec) \
- ((bufnr - dec) & QDIO_MAX_BUFFERS_MASK)
+#define add_buf(bufnr, inc) QDIO_BUFNR((bufnr) + (inc))
+#define next_buf(bufnr) add_buf(bufnr, 1)
+#define sub_buf(bufnr, dec) QDIO_BUFNR((bufnr) - (dec))
+#define prev_buf(bufnr) sub_buf(bufnr, 1)
#define queue_irqs_enabled(q) \
(test_bit(QDIO_QUEUE_IRQS_DISABLED, &q->u.in.queue_irq_state) == 0)
@@ -374,14 +357,11 @@
/* prototypes for thin interrupt */
int qdio_establish_thinint(struct qdio_irq *irq_ptr);
void qdio_shutdown_thinint(struct qdio_irq *irq_ptr);
-void tiqdio_add_input_queues(struct qdio_irq *irq_ptr);
-void tiqdio_remove_input_queues(struct qdio_irq *irq_ptr);
+void tiqdio_add_device(struct qdio_irq *irq_ptr);
+void tiqdio_remove_device(struct qdio_irq *irq_ptr);
void tiqdio_inbound_processing(unsigned long q);
-int tiqdio_allocate_memory(void);
-void tiqdio_free_memory(void);
-int tiqdio_register_thinints(void);
-void tiqdio_unregister_thinints(void);
-void clear_nonshared_ind(struct qdio_irq *);
+int qdio_thinint_init(void);
+void qdio_thinint_exit(void);
int test_nonshared_ind(struct qdio_irq *);
/* prototypes for setup */
@@ -396,12 +376,11 @@
int qdio_setup_get_ssqd(struct qdio_irq *irq_ptr,
struct subchannel_id *schid,
struct qdio_ssqd_desc *data);
-int qdio_setup_irq(struct qdio_initialize *init_data);
-void qdio_print_subchannel_info(struct qdio_irq *irq_ptr,
- struct ccw_device *cdev);
-void qdio_release_memory(struct qdio_irq *irq_ptr);
-int qdio_setup_create_sysfs(struct ccw_device *cdev);
-void qdio_setup_destroy_sysfs(struct ccw_device *cdev);
+int qdio_setup_irq(struct qdio_irq *irq_ptr, struct qdio_initialize *init_data);
+void qdio_shutdown_irq(struct qdio_irq *irq);
+void qdio_print_subchannel_info(struct qdio_irq *irq_ptr);
+void qdio_free_queues(struct qdio_irq *irq_ptr);
+void qdio_free_async_data(struct qdio_irq *irq_ptr);
int qdio_setup_init(void);
void qdio_setup_exit(void);
int qdio_enable_async_operation(struct qdio_output_q *q);
diff --git a/drivers/s390/cio/qdio_debug.c b/drivers/s390/cio/qdio_debug.c
index 35410e6..863d17c 100644
--- a/drivers/s390/cio/qdio_debug.c
+++ b/drivers/s390/cio/qdio_debug.c
@@ -58,30 +58,16 @@
mutex_unlock(&qdio_dbf_list_mutex);
}
-int qdio_allocate_dbf(struct qdio_initialize *init_data,
- struct qdio_irq *irq_ptr)
+int qdio_allocate_dbf(struct qdio_irq *irq_ptr)
{
char text[QDIO_DBF_NAME_LEN];
struct qdio_dbf_entry *new_entry;
- DBF_EVENT("qfmt:%1d", init_data->q_format);
- DBF_HEX(init_data->adapter_name, 8);
- DBF_EVENT("qpff%4x", init_data->qib_param_field_format);
- DBF_HEX(&init_data->qib_param_field, sizeof(void *));
- DBF_HEX(&init_data->input_slib_elements, sizeof(void *));
- DBF_HEX(&init_data->output_slib_elements, sizeof(void *));
- DBF_EVENT("niq:%1d noq:%1d", init_data->no_input_qs,
- init_data->no_output_qs);
- DBF_HEX(&init_data->input_handler, sizeof(void *));
- DBF_HEX(&init_data->output_handler, sizeof(void *));
- DBF_HEX(&init_data->int_parm, sizeof(long));
- DBF_HEX(&init_data->input_sbal_addr_array, sizeof(void *));
- DBF_HEX(&init_data->output_sbal_addr_array, sizeof(void *));
DBF_EVENT("irq:%8lx", (unsigned long)irq_ptr);
/* allocate trace view for the interface */
snprintf(text, QDIO_DBF_NAME_LEN, "qdio_%s",
- dev_name(&init_data->cdev->dev));
+ dev_name(&irq_ptr->cdev->dev));
irq_ptr->debug_area = qdio_get_dbf_entry(text);
if (irq_ptr->debug_area)
DBF_DEV_EVENT(DBF_ERR, irq_ptr, "dbf reused");
@@ -124,13 +110,12 @@
seq_printf(m, "nr_used: %d ftc: %d\n",
atomic_read(&q->nr_buf_used), q->first_to_check);
if (q->is_input_q) {
- seq_printf(m, "polling: %d ack start: %d ack count: %d\n",
- q->u.in.polling, q->u.in.ack_start,
- q->u.in.ack_count);
+ seq_printf(m, "batch start: %u batch count: %u\n",
+ q->u.in.batch_start, q->u.in.batch_count);
seq_printf(m, "DSCI: %x IRQs disabled: %u\n",
*(u8 *)q->irq_ptr->dsci,
- test_bit(QDIO_QUEUE_IRQS_DISABLED,
- &q->u.in.queue_irq_state));
+ test_bit(QDIO_IRQ_DISABLED,
+ &q->irq_ptr->poll_state));
}
seq_printf(m, "SBAL states:\n");
seq_printf(m, "|0 |8 |16 |24 |32 |40 |48 |56 63|\n");
@@ -180,7 +165,7 @@
}
seq_printf(m, "\n1 2.. 4.. 8.. "
- "16.. 32.. 64.. 127\n");
+ "16.. 32.. 64.. 128\n");
for (i = 0; i < ARRAY_SIZE(q->q_stats.nr_sbals); i++)
seq_printf(m, "%-10u ", q->q_stats.nr_sbals[i]);
seq_printf(m, "\nError NOP Total\n%-10u %-10u %-10u\n\n",
@@ -191,6 +176,23 @@
DEFINE_SHOW_ATTRIBUTE(qstat);
+static int ssqd_show(struct seq_file *m, void *v)
+{
+ struct ccw_device *cdev = m->private;
+ struct qdio_ssqd_desc ssqd;
+ int rc;
+
+ rc = qdio_get_ssqd_desc(cdev, &ssqd);
+ if (rc)
+ return rc;
+
+ seq_hex_dump(m, "", DUMP_PREFIX_NONE, 16, 4, &ssqd, sizeof(ssqd),
+ false);
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(ssqd);
+
static char *qperf_names[] = {
"Assumed adapter interrupts",
"QDIO interrupts",
@@ -285,53 +287,37 @@
.release = single_release,
};
-static void setup_debugfs_entry(struct qdio_q *q)
+static void setup_debugfs_entry(struct dentry *parent, struct qdio_q *q)
{
char name[QDIO_DEBUGFS_NAME_LEN];
snprintf(name, QDIO_DEBUGFS_NAME_LEN, "%s_%d",
q->is_input_q ? "input" : "output",
q->nr);
- q->debugfs_q = debugfs_create_file(name, 0444,
- q->irq_ptr->debugfs_dev, q, &qstat_fops);
- if (IS_ERR(q->debugfs_q))
- q->debugfs_q = NULL;
+ debugfs_create_file(name, 0444, parent, q, &qstat_fops);
}
-void qdio_setup_debug_entries(struct qdio_irq *irq_ptr, struct ccw_device *cdev)
+void qdio_setup_debug_entries(struct qdio_irq *irq_ptr)
{
struct qdio_q *q;
int i;
- irq_ptr->debugfs_dev = debugfs_create_dir(dev_name(&cdev->dev),
+ irq_ptr->debugfs_dev = debugfs_create_dir(dev_name(&irq_ptr->cdev->dev),
debugfs_root);
- if (IS_ERR(irq_ptr->debugfs_dev))
- irq_ptr->debugfs_dev = NULL;
-
- irq_ptr->debugfs_perf = debugfs_create_file("statistics",
- S_IFREG | S_IRUGO | S_IWUSR,
- irq_ptr->debugfs_dev, irq_ptr,
- &debugfs_perf_fops);
- if (IS_ERR(irq_ptr->debugfs_perf))
- irq_ptr->debugfs_perf = NULL;
+ debugfs_create_file("statistics", S_IFREG | S_IRUGO | S_IWUSR,
+ irq_ptr->debugfs_dev, irq_ptr, &debugfs_perf_fops);
+ debugfs_create_file("ssqd", 0444, irq_ptr->debugfs_dev, irq_ptr->cdev,
+ &ssqd_fops);
for_each_input_queue(irq_ptr, q, i)
- setup_debugfs_entry(q);
+ setup_debugfs_entry(irq_ptr->debugfs_dev, q);
for_each_output_queue(irq_ptr, q, i)
- setup_debugfs_entry(q);
+ setup_debugfs_entry(irq_ptr->debugfs_dev, q);
}
void qdio_shutdown_debug_entries(struct qdio_irq *irq_ptr)
{
- struct qdio_q *q;
- int i;
-
- for_each_input_queue(irq_ptr, q, i)
- debugfs_remove(q->debugfs_q);
- for_each_output_queue(irq_ptr, q, i)
- debugfs_remove(q->debugfs_q);
- debugfs_remove(irq_ptr->debugfs_perf);
- debugfs_remove(irq_ptr->debugfs_dev);
+ debugfs_remove_recursive(irq_ptr->debugfs_dev);
}
int __init qdio_debug_init(void)
@@ -353,7 +339,7 @@
void qdio_debug_exit(void)
{
qdio_clear_dbf_list();
- debugfs_remove(debugfs_root);
+ debugfs_remove_recursive(debugfs_root);
debug_unregister(qdio_dbf_setup);
debug_unregister(qdio_dbf_error);
}
diff --git a/drivers/s390/cio/qdio_debug.h b/drivers/s390/cio/qdio_debug.h
index f85f5fa..0dfba08 100644
--- a/drivers/s390/cio/qdio_debug.h
+++ b/drivers/s390/cio/qdio_debug.h
@@ -64,10 +64,8 @@
debug_event(dev->debug_area, level, addr, len);
}
-int qdio_allocate_dbf(struct qdio_initialize *init_data,
- struct qdio_irq *irq_ptr);
-void qdio_setup_debug_entries(struct qdio_irq *irq_ptr,
- struct ccw_device *cdev);
+int qdio_allocate_dbf(struct qdio_irq *irq_ptr);
+void qdio_setup_debug_entries(struct qdio_irq *irq_ptr);
void qdio_shutdown_debug_entries(struct qdio_irq *irq_ptr);
int qdio_debug_init(void);
void qdio_debug_exit(void);
diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c
index 5b63c50..3e29c26 100644
--- a/drivers/s390/cio/qdio_main.c
+++ b/drivers/s390/cio/qdio_main.c
@@ -131,7 +131,7 @@
case 96:
/* not all buffers processed */
qperf_inc(q, eqbs_partial);
- DBF_DEV_EVENT(DBF_WARN, q->irq_ptr, "EQBS part:%02x",
+ DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "EQBS part:%02x",
tmp_count);
return count - tmp_count;
case 97:
@@ -143,7 +143,7 @@
DBF_ERROR("%4x EQBS ERROR", SCH_NO(q));
DBF_ERROR("%3d%3d%2d", count, tmp_count, nr);
q->handler(q->irq_ptr->cdev, QDIO_ERROR_GET_BUF_STATE, q->nr,
- q->first_to_kick, count, q->irq_ptr->int_parm);
+ q->first_to_check, count, q->irq_ptr->int_parm);
return 0;
}
}
@@ -191,7 +191,7 @@
DBF_ERROR("%4x SQBS ERROR", SCH_NO(q));
DBF_ERROR("%3d%3d%2d", count, tmp_count, nr);
q->handler(q->irq_ptr->cdev, QDIO_ERROR_SET_BUF_STATE, q->nr,
- q->first_to_kick, count, q->irq_ptr->int_parm);
+ q->first_to_check, count, q->irq_ptr->int_parm);
return 0;
}
}
@@ -254,10 +254,17 @@
if (is_qebsm(q))
return qdio_do_sqbs(q, state, bufnr, count);
+ /* Ensure that all preceding changes to the SBALs are visible: */
+ mb();
+
for (i = 0; i < count; i++) {
- xchg(&q->slsb.val[bufnr], state);
+ WRITE_ONCE(q->slsb.val[bufnr], state);
bufnr = next_buf(bufnr);
}
+
+ /* Make our SLSB changes visible: */
+ mb();
+
return count;
}
@@ -310,18 +317,19 @@
return qdio_siga_sync(q, q->mask, 0);
}
-static int qdio_siga_output(struct qdio_q *q, unsigned int *busy_bit,
- unsigned long aob)
+static int qdio_siga_output(struct qdio_q *q, unsigned int count,
+ unsigned int *busy_bit, unsigned long aob)
{
unsigned long schid = *((u32 *) &q->irq_ptr->schid);
unsigned int fc = QDIO_SIGA_WRITE;
u64 start_time = 0;
int retries = 0, cc;
- unsigned long laob = 0;
- if (aob) {
- fc = QDIO_SIGA_WRITEQ;
- laob = aob;
+ if (queue_type(q) == QDIO_IQDIO_QFMT && !multicast_outbound(q)) {
+ if (count > 1)
+ fc = QDIO_SIGA_WRITEM;
+ else if (aob)
+ fc = QDIO_SIGA_WRITEQ;
}
if (is_qebsm(q)) {
@@ -329,7 +337,7 @@
fc |= QDIO_SIGA_QEBSM_FLAG;
}
again:
- cc = do_siga_output(schid, q->mask, busy_bit, fc, laob);
+ cc = do_siga_output(schid, q->mask, busy_bit, fc, aob);
/* hipersocket busy condition */
if (unlikely(*busy_bit)) {
@@ -392,40 +400,26 @@
static inline void qdio_stop_polling(struct qdio_q *q)
{
- if (!q->u.in.polling)
+ if (!q->u.in.batch_count)
return;
- q->u.in.polling = 0;
qperf_inc(q, stop_polling);
/* show the card that we are not polling anymore */
- if (is_qebsm(q)) {
- set_buf_states(q, q->u.in.ack_start, SLSB_P_INPUT_NOT_INIT,
- q->u.in.ack_count);
- q->u.in.ack_count = 0;
- } else
- set_buf_state(q, q->u.in.ack_start, SLSB_P_INPUT_NOT_INIT);
+ set_buf_states(q, q->u.in.batch_start, SLSB_P_INPUT_NOT_INIT,
+ q->u.in.batch_count);
+ q->u.in.batch_count = 0;
}
static inline void account_sbals(struct qdio_q *q, unsigned int count)
{
- int pos;
-
q->q_stats.nr_sbal_total += count;
- if (count == QDIO_MAX_BUFFERS_MASK) {
- q->q_stats.nr_sbals[7]++;
- return;
- }
- pos = ilog2(count);
- q->q_stats.nr_sbals[pos]++;
+ q->q_stats.nr_sbals[ilog2(count)]++;
}
static void process_buffer_error(struct qdio_q *q, unsigned int start,
int count)
{
- unsigned char state = (q->is_input_q) ? SLSB_P_INPUT_NOT_INIT :
- SLSB_P_OUTPUT_NOT_INIT;
-
q->qdio_error = QDIO_ERROR_SLSB_STATE;
/* special handling for no target buffer empty */
@@ -433,7 +427,7 @@
q->sbal[start]->element[15].sflags == 0x10) {
qperf_inc(q, target_full);
DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "OUTFULL FTC:%02x", start);
- goto set;
+ return;
}
DBF_ERROR("%4x BUF ERROR", SCH_NO(q));
@@ -442,59 +436,18 @@
DBF_ERROR("F14:%2x F15:%2x",
q->sbal[start]->element[14].sflags,
q->sbal[start]->element[15].sflags);
-
-set:
- /*
- * Interrupts may be avoided as long as the error is present
- * so change the buffer state immediately to avoid starvation.
- */
- set_buf_states(q, start, state, count);
}
-static inline void inbound_primed(struct qdio_q *q, unsigned int start,
- int count)
+static inline void inbound_handle_work(struct qdio_q *q, unsigned int start,
+ int count, bool auto_ack)
{
- int new;
+ /* ACK the newest SBAL: */
+ if (!auto_ack)
+ set_buf_state(q, add_buf(start, count - 1), SLSB_P_INPUT_ACK);
- DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "in prim:%1d %02x", q->nr, count);
-
- /* for QEBSM the ACK was already set by EQBS */
- if (is_qebsm(q)) {
- if (!q->u.in.polling) {
- q->u.in.polling = 1;
- q->u.in.ack_count = count;
- q->u.in.ack_start = start;
- return;
- }
-
- /* delete the previous ACK's */
- set_buf_states(q, q->u.in.ack_start, SLSB_P_INPUT_NOT_INIT,
- q->u.in.ack_count);
- q->u.in.ack_count = count;
- q->u.in.ack_start = start;
- return;
- }
-
- /*
- * ACK the newest buffer. The ACK will be removed in qdio_stop_polling
- * or by the next inbound run.
- */
- new = add_buf(start, count - 1);
- if (q->u.in.polling) {
- /* reset the previous ACK but first set the new one */
- set_buf_state(q, new, SLSB_P_INPUT_ACK);
- set_buf_state(q, q->u.in.ack_start, SLSB_P_INPUT_NOT_INIT);
- } else {
- q->u.in.polling = 1;
- set_buf_state(q, new, SLSB_P_INPUT_ACK);
- }
-
- q->u.in.ack_start = new;
- count--;
- if (!count)
- return;
- /* need to change ALL buffers to get more interrupts */
- set_buf_states(q, start, SLSB_P_INPUT_NOT_INIT, count);
+ if (!q->u.in.batch_count)
+ q->u.in.batch_start = start;
+ q->u.in.batch_count += count;
}
static int get_inbound_buffer_frontier(struct qdio_q *q, unsigned int start)
@@ -504,11 +457,7 @@
q->timestamp = get_tod_clock_fast();
- /*
- * Don't check 128 buffers, as otherwise qdio_inbound_q_moved
- * would return 0.
- */
- count = min(atomic_read(&q->nr_buf_used), QDIO_MAX_BUFFERS_MASK);
+ count = atomic_read(&q->nr_buf_used);
if (!count)
return 0;
@@ -522,43 +471,46 @@
switch (state) {
case SLSB_P_INPUT_PRIMED:
- inbound_primed(q, start, count);
+ DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "in prim:%1d %02x", q->nr,
+ count);
+
+ inbound_handle_work(q, start, count, is_qebsm(q));
if (atomic_sub_return(count, &q->nr_buf_used) == 0)
qperf_inc(q, inbound_queue_full);
if (q->irq_ptr->perf_stat_enabled)
account_sbals(q, count);
return count;
case SLSB_P_INPUT_ERROR:
+ DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "in err:%1d %02x", q->nr,
+ count);
+
process_buffer_error(q, start, count);
+ inbound_handle_work(q, start, count, false);
if (atomic_sub_return(count, &q->nr_buf_used) == 0)
qperf_inc(q, inbound_queue_full);
if (q->irq_ptr->perf_stat_enabled)
account_sbals_error(q, count);
return count;
case SLSB_CU_INPUT_EMPTY:
- case SLSB_P_INPUT_NOT_INIT:
- case SLSB_P_INPUT_ACK:
if (q->irq_ptr->perf_stat_enabled)
q->q_stats.nr_sbal_nop++;
DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "in nop:%1d %#02x",
q->nr, start);
return 0;
+ case SLSB_P_INPUT_NOT_INIT:
+ case SLSB_P_INPUT_ACK:
+ /* We should never see this state, throw a WARN: */
default:
- WARN_ON_ONCE(1);
+ dev_WARN_ONCE(&q->irq_ptr->cdev->dev, 1,
+ "found state %#x at index %u on queue %u\n",
+ state, start, q->nr);
return 0;
}
}
static int qdio_inbound_q_moved(struct qdio_q *q, unsigned int start)
{
- int count;
-
- count = get_inbound_buffer_frontier(q, start);
-
- if (count && !is_thinint_irq(q->irq_ptr) && MACHINE_IS_LPAR)
- q->u.in.timestamp = get_tod_clock();
-
- return count;
+ return get_inbound_buffer_frontier(q, start);
}
static inline int qdio_inbound_q_done(struct qdio_q *q, unsigned int start)
@@ -576,42 +528,7 @@
/* more work coming */
return 0;
- if (is_thinint_irq(q->irq_ptr))
- return 1;
-
- /* don't poll under z/VM */
- if (MACHINE_IS_VM)
- return 1;
-
- /*
- * At this point we know, that inbound first_to_check
- * has (probably) not moved (see qdio_inbound_processing).
- */
- if (get_tod_clock_fast() > q->u.in.timestamp + QDIO_INPUT_THRESHOLD) {
- DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "in done:%02x", start);
- return 1;
- } else
- return 0;
-}
-
-static inline void qdio_handle_aobs(struct qdio_q *q, int start, int count)
-{
- unsigned char state = 0;
- int j, b = start;
-
- for (j = 0; j < count; ++j) {
- get_buf_state(q, b, &state, 0);
- if (state == SLSB_P_OUTPUT_PENDING) {
- struct qaob *aob = q->u.out.aobs[b];
- if (aob == NULL)
- continue;
-
- q->u.out.sbal_state[b].flags |=
- QDIO_OUTBUF_STATE_FLAG_PENDING;
- q->u.out.aobs[b] = NULL;
- }
- b = next_buf(b);
- }
+ return 1;
}
static inline unsigned long qdio_aob_for_buffer(struct qdio_output_q *q,
@@ -633,10 +550,9 @@
return phys_aob;
}
-static void qdio_kick_handler(struct qdio_q *q, unsigned int count)
+static void qdio_kick_handler(struct qdio_q *q, unsigned int start,
+ unsigned int count)
{
- int start = q->first_to_kick;
-
if (unlikely(q->irq_ptr->state != QDIO_IRQ_STATE_ACTIVE))
return;
@@ -653,7 +569,6 @@
q->irq_ptr->int_parm);
/* for the next time */
- q->first_to_kick = add_buf(start, count);
q->qdio_error = 0;
}
@@ -677,9 +592,9 @@
if (count == 0)
return;
+ qdio_kick_handler(q, start, count);
start = add_buf(start, count);
q->first_to_check = start;
- qdio_kick_handler(q, count);
if (!qdio_inbound_q_done(q, start)) {
/* means poll time is not yet over */
@@ -705,6 +620,19 @@
__qdio_inbound_processing(q);
}
+static void qdio_check_pending(struct qdio_q *q, unsigned int index)
+{
+ unsigned char state;
+
+ if (get_buf_state(q, index, &state, 0) > 0 &&
+ state == SLSB_P_OUTPUT_PENDING &&
+ q->u.out.aobs[index]) {
+ q->u.out.sbal_state[index].flags |=
+ QDIO_OUTBUF_STATE_FLAG_PENDING;
+ q->u.out.aobs[index] = NULL;
+ }
+}
+
static int get_outbound_buffer_frontier(struct qdio_q *q, unsigned int start)
{
unsigned char state = 0;
@@ -751,11 +679,14 @@
DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "out primed:%1d",
q->nr);
return 0;
- case SLSB_P_OUTPUT_NOT_INIT:
case SLSB_P_OUTPUT_HALTED:
return 0;
+ case SLSB_P_OUTPUT_NOT_INIT:
+ /* We should never see this state, throw a WARN: */
default:
- WARN_ON_ONCE(1);
+ dev_WARN_ONCE(&q->irq_ptr->cdev->dev, 1,
+ "found state %#x at index %u on queue %u\n",
+ state, start, q->nr);
return 0;
}
}
@@ -774,14 +705,20 @@
if (count) {
DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "out moved:%1d", q->nr);
- if (q->u.out.use_cq)
- qdio_handle_aobs(q, start, count);
+
+ if (q->u.out.use_cq) {
+ unsigned int i;
+
+ for (i = 0; i < count; i++)
+ qdio_check_pending(q, QDIO_BUFNR(start + i));
+ }
}
return count;
}
-static int qdio_kick_outbound_q(struct qdio_q *q, unsigned long aob)
+static int qdio_kick_outbound_q(struct qdio_q *q, unsigned int count,
+ unsigned long aob)
{
int retries = 0, cc;
unsigned int busy_bit;
@@ -793,7 +730,7 @@
retry:
qperf_inc(q, siga_write);
- cc = qdio_siga_output(q, &busy_bit, aob);
+ cc = qdio_siga_output(q, count, &busy_bit, aob);
switch (cc) {
case 0:
break;
@@ -834,7 +771,7 @@
count = qdio_outbound_q_moved(q, start);
if (count) {
q->first_to_check = add_buf(start, count);
- qdio_kick_handler(q, count);
+ qdio_kick_handler(q, start, count);
}
if (queue_type(q) == QDIO_ZFCP_QFMT && !pci_out_supported(q->irq_ptr) &&
@@ -888,47 +825,17 @@
qdio_tasklet_schedule(out);
}
-static void __tiqdio_inbound_processing(struct qdio_q *q)
+void tiqdio_inbound_processing(unsigned long data)
{
- unsigned int start = q->first_to_check;
- int count;
+ struct qdio_q *q = (struct qdio_q *)data;
- qperf_inc(q, tasklet_inbound);
if (need_siga_sync(q) && need_siga_sync_after_ai(q))
qdio_sync_queues(q);
/* The interrupt could be caused by a PCI request: */
qdio_check_outbound_pci_queues(q->irq_ptr);
- count = qdio_inbound_q_moved(q, start);
- if (count == 0)
- return;
-
- start = add_buf(start, count);
- q->first_to_check = start;
- qdio_kick_handler(q, count);
-
- if (!qdio_inbound_q_done(q, start)) {
- qperf_inc(q, tasklet_inbound_resched);
- if (!qdio_tasklet_schedule(q))
- return;
- }
-
- qdio_stop_polling(q);
- /*
- * We need to check again to not lose initiative after
- * resetting the ACK state.
- */
- if (!qdio_inbound_q_done(q, start)) {
- qperf_inc(q, tasklet_inbound_resched2);
- qdio_tasklet_schedule(q);
- }
-}
-
-void tiqdio_inbound_processing(unsigned long data)
-{
- struct qdio_q *q = (struct qdio_q *)data;
- __tiqdio_inbound_processing(q);
+ __qdio_inbound_processing(q);
}
static inline void qdio_set_state(struct qdio_irq *irq_ptr,
@@ -958,19 +865,14 @@
if (unlikely(irq_ptr->state != QDIO_IRQ_STATE_ACTIVE))
return;
- for_each_input_queue(irq_ptr, q, i) {
- if (q->u.in.queue_start_poll) {
- /* skip if polling is enabled or already in work */
- if (test_and_set_bit(QDIO_QUEUE_IRQS_DISABLED,
- &q->u.in.queue_irq_state)) {
- qperf_inc(q, int_discarded);
- continue;
- }
- q->u.in.queue_start_poll(q->irq_ptr->cdev, q->nr,
- q->irq_ptr->int_parm);
- } else {
+ if (irq_ptr->irq_poll) {
+ if (!test_and_set_bit(QDIO_IRQ_DISABLED, &irq_ptr->poll_state))
+ irq_ptr->irq_poll(irq_ptr->cdev, irq_ptr->int_parm);
+ else
+ QDIO_PERF_STAT_INC(irq_ptr, int_discarded);
+ } else {
+ for_each_input_queue(irq_ptr, q, i)
tasklet_schedule(&q->tasklet);
- }
}
if (!pci_out_supported(irq_ptr) || !irq_ptr->scan_threshold)
@@ -985,12 +887,11 @@
}
}
-static void qdio_handle_activate_check(struct ccw_device *cdev,
- unsigned long intparm, int cstat, int dstat)
+static void qdio_handle_activate_check(struct qdio_irq *irq_ptr,
+ unsigned long intparm, int cstat,
+ int dstat)
{
- struct qdio_irq *irq_ptr = cdev->private->qdio_data;
struct qdio_q *q;
- int count;
DBF_ERROR("%4x ACT CHECK", irq_ptr->schid.sch_no);
DBF_ERROR("intp :%lx", intparm);
@@ -1005,9 +906,8 @@
goto no_handler;
}
- count = sub_buf(q->first_to_check, q->first_to_kick);
q->handler(q->irq_ptr->cdev, QDIO_ERROR_ACTIVATE,
- q->nr, q->first_to_kick, count, irq_ptr->int_parm);
+ q->nr, q->first_to_check, 0, irq_ptr->int_parm);
no_handler:
qdio_set_state(irq_ptr, QDIO_IRQ_STATE_STOPPED);
/*
@@ -1017,11 +917,9 @@
lgr_info_log();
}
-static void qdio_establish_handle_irq(struct ccw_device *cdev, int cstat,
+static void qdio_establish_handle_irq(struct qdio_irq *irq_ptr, int cstat,
int dstat)
{
- struct qdio_irq *irq_ptr = cdev->private->qdio_data;
-
DBF_DEV_EVENT(DBF_INFO, irq_ptr, "qest irq");
if (cstat)
@@ -1068,7 +966,7 @@
switch (irq_ptr->state) {
case QDIO_IRQ_STATE_INACTIVE:
- qdio_establish_handle_irq(cdev, cstat, dstat);
+ qdio_establish_handle_irq(irq_ptr, cstat, dstat);
break;
case QDIO_IRQ_STATE_CLEANUP:
qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
@@ -1080,7 +978,7 @@
return;
}
if (cstat || dstat)
- qdio_handle_activate_check(cdev, intparm, cstat,
+ qdio_handle_activate_check(irq_ptr, intparm, cstat,
dstat);
break;
case QDIO_IRQ_STATE_STOPPED:
@@ -1113,9 +1011,8 @@
}
EXPORT_SYMBOL_GPL(qdio_get_ssqd_desc);
-static void qdio_shutdown_queues(struct ccw_device *cdev)
+static void qdio_shutdown_queues(struct qdio_irq *irq_ptr)
{
- struct qdio_irq *irq_ptr = cdev->private->qdio_data;
struct qdio_q *q;
int i;
@@ -1128,6 +1025,33 @@
}
}
+static int qdio_cancel_ccw(struct qdio_irq *irq, int how)
+{
+ struct ccw_device *cdev = irq->cdev;
+ int rc;
+
+ spin_lock_irq(get_ccwdev_lock(cdev));
+ qdio_set_state(irq, QDIO_IRQ_STATE_CLEANUP);
+ if (how & QDIO_FLAG_CLEANUP_USING_CLEAR)
+ rc = ccw_device_clear(cdev, QDIO_DOING_CLEANUP);
+ else
+ /* default behaviour is halt */
+ rc = ccw_device_halt(cdev, QDIO_DOING_CLEANUP);
+ spin_unlock_irq(get_ccwdev_lock(cdev));
+ if (rc) {
+ DBF_ERROR("%4x SHUTD ERR", irq->schid.sch_no);
+ DBF_ERROR("rc:%4d", rc);
+ return rc;
+ }
+
+ wait_event_interruptible_timeout(cdev->private->wait_q,
+ irq->state == QDIO_IRQ_STATE_INACTIVE ||
+ irq->state == QDIO_IRQ_STATE_ERR,
+ 10 * HZ);
+
+ return 0;
+}
+
/**
* qdio_shutdown - shut down a qdio subchannel
* @cdev: associated ccw device
@@ -1162,41 +1086,13 @@
*/
qdio_set_state(irq_ptr, QDIO_IRQ_STATE_STOPPED);
- tiqdio_remove_input_queues(irq_ptr);
- qdio_shutdown_queues(cdev);
+ tiqdio_remove_device(irq_ptr);
+ qdio_shutdown_queues(irq_ptr);
qdio_shutdown_debug_entries(irq_ptr);
- /* cleanup subchannel */
- spin_lock_irq(get_ccwdev_lock(cdev));
-
- if (how & QDIO_FLAG_CLEANUP_USING_CLEAR)
- rc = ccw_device_clear(cdev, QDIO_DOING_CLEANUP);
- else
- /* default behaviour is halt */
- rc = ccw_device_halt(cdev, QDIO_DOING_CLEANUP);
- if (rc) {
- DBF_ERROR("%4x SHUTD ERR", irq_ptr->schid.sch_no);
- DBF_ERROR("rc:%4d", rc);
- goto no_cleanup;
- }
-
- qdio_set_state(irq_ptr, QDIO_IRQ_STATE_CLEANUP);
- spin_unlock_irq(get_ccwdev_lock(cdev));
- wait_event_interruptible_timeout(cdev->private->wait_q,
- irq_ptr->state == QDIO_IRQ_STATE_INACTIVE ||
- irq_ptr->state == QDIO_IRQ_STATE_ERR,
- 10 * HZ);
- spin_lock_irq(get_ccwdev_lock(cdev));
-
-no_cleanup:
+ rc = qdio_cancel_ccw(irq_ptr, how);
qdio_shutdown_thinint(irq_ptr);
-
- /* restore interrupt handler */
- if ((void *)cdev->handler == (void *)qdio_int_handler) {
- cdev->handler = irq_ptr->orig_handler;
- cdev->private->intparm = 0;
- }
- spin_unlock_irq(get_ccwdev_lock(cdev));
+ qdio_shutdown_irq(irq_ptr);
qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
mutex_unlock(&irq_ptr->setup_mutex);
@@ -1227,43 +1123,47 @@
cdev->private->qdio_data = NULL;
mutex_unlock(&irq_ptr->setup_mutex);
- qdio_release_memory(irq_ptr);
+ qdio_free_async_data(irq_ptr);
+ qdio_free_queues(irq_ptr);
+ free_page((unsigned long) irq_ptr->qdr);
+ free_page(irq_ptr->chsc_page);
+ free_page((unsigned long) irq_ptr);
return 0;
}
EXPORT_SYMBOL_GPL(qdio_free);
/**
* qdio_allocate - allocate qdio queues and associated data
- * @init_data: initialization data
+ * @cdev: associated ccw device
+ * @no_input_qs: allocate this number of Input Queues
+ * @no_output_qs: allocate this number of Output Queues
*/
-int qdio_allocate(struct qdio_initialize *init_data)
+int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs,
+ unsigned int no_output_qs)
{
struct subchannel_id schid;
struct qdio_irq *irq_ptr;
+ int rc = -ENOMEM;
- ccw_device_get_schid(init_data->cdev, &schid);
+ ccw_device_get_schid(cdev, &schid);
DBF_EVENT("qallocate:%4x", schid.sch_no);
- if ((init_data->no_input_qs && !init_data->input_handler) ||
- (init_data->no_output_qs && !init_data->output_handler))
- return -EINVAL;
-
- if ((init_data->no_input_qs > QDIO_MAX_QUEUES_PER_IRQ) ||
- (init_data->no_output_qs > QDIO_MAX_QUEUES_PER_IRQ))
- return -EINVAL;
-
- if ((!init_data->input_sbal_addr_array) ||
- (!init_data->output_sbal_addr_array))
+ if (no_input_qs > QDIO_MAX_QUEUES_PER_IRQ ||
+ no_output_qs > QDIO_MAX_QUEUES_PER_IRQ)
return -EINVAL;
/* irq_ptr must be in GFP_DMA since it contains ccw1.cda */
irq_ptr = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
if (!irq_ptr)
- goto out_err;
+ return -ENOMEM;
+ irq_ptr->cdev = cdev;
mutex_init(&irq_ptr->setup_mutex);
- if (qdio_allocate_dbf(init_data, irq_ptr))
- goto out_rel;
+ if (qdio_allocate_dbf(irq_ptr))
+ goto err_dbf;
+
+ DBF_DEV_EVENT(DBF_ERR, irq_ptr, "alloc niq:%1u noq:%1u", no_input_qs,
+ no_output_qs);
/*
* Allocate a page for the chsc calls in qdio_establish.
@@ -1273,24 +1173,30 @@
*/
irq_ptr->chsc_page = get_zeroed_page(GFP_KERNEL);
if (!irq_ptr->chsc_page)
- goto out_rel;
+ goto err_chsc;
/* qdr is used in ccw1.cda which is u32 */
irq_ptr->qdr = (struct qdr *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
if (!irq_ptr->qdr)
- goto out_rel;
+ goto err_qdr;
- if (qdio_allocate_qs(irq_ptr, init_data->no_input_qs,
- init_data->no_output_qs))
- goto out_rel;
+ rc = qdio_allocate_qs(irq_ptr, no_input_qs, no_output_qs);
+ if (rc)
+ goto err_queues;
- init_data->cdev->private->qdio_data = irq_ptr;
+ INIT_LIST_HEAD(&irq_ptr->entry);
+ cdev->private->qdio_data = irq_ptr;
qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
return 0;
-out_rel:
- qdio_release_memory(irq_ptr);
-out_err:
- return -ENOMEM;
+
+err_queues:
+ free_page((unsigned long) irq_ptr->qdr);
+err_qdr:
+ free_page(irq_ptr->chsc_page);
+err_chsc:
+err_dbf:
+ free_page((unsigned long) irq_ptr);
+ return rc;
}
EXPORT_SYMBOL_GPL(qdio_allocate);
@@ -1316,33 +1222,62 @@
DBF_EVENT("use_cq:%d", use_cq);
}
+static void qdio_trace_init_data(struct qdio_irq *irq,
+ struct qdio_initialize *data)
+{
+ DBF_DEV_EVENT(DBF_ERR, irq, "qfmt:%1u", data->q_format);
+ DBF_DEV_EVENT(DBF_ERR, irq, "qpff%4x", data->qib_param_field_format);
+ DBF_DEV_HEX(irq, &data->qib_param_field, sizeof(void *), DBF_ERR);
+ DBF_DEV_HEX(irq, &data->input_slib_elements, sizeof(void *), DBF_ERR);
+ DBF_DEV_HEX(irq, &data->output_slib_elements, sizeof(void *), DBF_ERR);
+ DBF_DEV_EVENT(DBF_ERR, irq, "niq:%1u noq:%1u", data->no_input_qs,
+ data->no_output_qs);
+ DBF_DEV_HEX(irq, &data->input_handler, sizeof(void *), DBF_ERR);
+ DBF_DEV_HEX(irq, &data->output_handler, sizeof(void *), DBF_ERR);
+ DBF_DEV_HEX(irq, &data->int_parm, sizeof(long), DBF_ERR);
+ DBF_DEV_HEX(irq, &data->input_sbal_addr_array, sizeof(void *), DBF_ERR);
+ DBF_DEV_HEX(irq, &data->output_sbal_addr_array, sizeof(void *),
+ DBF_ERR);
+}
+
/**
* qdio_establish - establish queues on a qdio subchannel
+ * @cdev: associated ccw device
* @init_data: initialization data
*/
-int qdio_establish(struct qdio_initialize *init_data)
+int qdio_establish(struct ccw_device *cdev,
+ struct qdio_initialize *init_data)
{
- struct ccw_device *cdev = init_data->cdev;
+ struct qdio_irq *irq_ptr = cdev->private->qdio_data;
struct subchannel_id schid;
- struct qdio_irq *irq_ptr;
+ long timeout;
int rc;
ccw_device_get_schid(cdev, &schid);
DBF_EVENT("qestablish:%4x", schid.sch_no);
- irq_ptr = cdev->private->qdio_data;
if (!irq_ptr)
return -ENODEV;
+ if (init_data->no_input_qs > irq_ptr->max_input_qs ||
+ init_data->no_output_qs > irq_ptr->max_output_qs)
+ return -EINVAL;
+
+ if ((init_data->no_input_qs && !init_data->input_handler) ||
+ (init_data->no_output_qs && !init_data->output_handler))
+ return -EINVAL;
+
+ if (!init_data->input_sbal_addr_array ||
+ !init_data->output_sbal_addr_array)
+ return -EINVAL;
+
mutex_lock(&irq_ptr->setup_mutex);
- qdio_setup_irq(init_data);
+ qdio_trace_init_data(irq_ptr, init_data);
+ qdio_setup_irq(irq_ptr, init_data);
rc = qdio_establish_thinint(irq_ptr);
- if (rc) {
- mutex_unlock(&irq_ptr->setup_mutex);
- qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR);
- return rc;
- }
+ if (rc)
+ goto err_thinint;
/* establish q */
irq_ptr->ccw.cmd_code = irq_ptr->equeue.cmd;
@@ -1358,14 +1293,16 @@
if (rc) {
DBF_ERROR("%4x est IO ERR", irq_ptr->schid.sch_no);
DBF_ERROR("rc:%4x", rc);
- mutex_unlock(&irq_ptr->setup_mutex);
- qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR);
- return rc;
+ goto err_ccw_start;
}
- wait_event_interruptible_timeout(cdev->private->wait_q,
- irq_ptr->state == QDIO_IRQ_STATE_ESTABLISHED ||
- irq_ptr->state == QDIO_IRQ_STATE_ERR, HZ);
+ timeout = wait_event_interruptible_timeout(cdev->private->wait_q,
+ irq_ptr->state == QDIO_IRQ_STATE_ESTABLISHED ||
+ irq_ptr->state == QDIO_IRQ_STATE_ERR, HZ);
+ if (timeout <= 0) {
+ rc = (timeout == -ERESTARTSYS) ? -EINTR : -ETIME;
+ goto err_ccw_timeout;
+ }
if (irq_ptr->state != QDIO_IRQ_STATE_ESTABLISHED) {
mutex_unlock(&irq_ptr->setup_mutex);
@@ -1381,9 +1318,19 @@
qdio_init_buf_states(irq_ptr);
mutex_unlock(&irq_ptr->setup_mutex);
- qdio_print_subchannel_info(irq_ptr, cdev);
- qdio_setup_debug_entries(irq_ptr, cdev);
+ qdio_print_subchannel_info(irq_ptr);
+ qdio_setup_debug_entries(irq_ptr);
return 0;
+
+err_ccw_timeout:
+ qdio_cancel_ccw(irq_ptr, QDIO_FLAG_CLEANUP_USING_CLEAR);
+err_ccw_start:
+ qdio_shutdown_thinint(irq_ptr);
+err_thinint:
+ qdio_shutdown_irq(irq_ptr);
+ qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
+ mutex_unlock(&irq_ptr->setup_mutex);
+ return rc;
}
EXPORT_SYMBOL_GPL(qdio_establish);
@@ -1393,14 +1340,13 @@
*/
int qdio_activate(struct ccw_device *cdev)
{
+ struct qdio_irq *irq_ptr = cdev->private->qdio_data;
struct subchannel_id schid;
- struct qdio_irq *irq_ptr;
int rc;
ccw_device_get_schid(cdev, &schid);
DBF_EVENT("qactivate:%4x", schid.sch_no);
- irq_ptr = cdev->private->qdio_data;
if (!irq_ptr)
return -ENODEV;
@@ -1428,7 +1374,7 @@
}
if (is_thinint_irq(irq_ptr))
- tiqdio_add_input_queues(irq_ptr);
+ tiqdio_add_device(irq_ptr);
/* wait for subchannel to become active */
msleep(5);
@@ -1448,25 +1394,6 @@
}
EXPORT_SYMBOL_GPL(qdio_activate);
-static inline int buf_in_between(int bufnr, int start, int count)
-{
- int end = add_buf(start, count);
-
- if (end > start) {
- if (bufnr >= start && bufnr < end)
- return 1;
- else
- return 0;
- }
-
- /* wrap-around case */
- if ((bufnr >= start && bufnr <= QDIO_MAX_BUFFERS_PER_Q) ||
- (bufnr < end))
- return 1;
- else
- return 0;
-}
-
/**
* handle_inbound - reset processed input buffers
* @q: queue containing the buffers
@@ -1477,38 +1404,18 @@
static int handle_inbound(struct qdio_q *q, unsigned int callflags,
int bufnr, int count)
{
- int diff;
+ int overlap;
qperf_inc(q, inbound_call);
- if (!q->u.in.polling)
- goto set;
-
- /* protect against stop polling setting an ACK for an emptied slsb */
- if (count == QDIO_MAX_BUFFERS_PER_Q) {
- /* overwriting everything, just delete polling status */
- q->u.in.polling = 0;
- q->u.in.ack_count = 0;
- goto set;
- } else if (buf_in_between(q->u.in.ack_start, bufnr, count)) {
- if (is_qebsm(q)) {
- /* partial overwrite, just update ack_start */
- diff = add_buf(bufnr, count);
- diff = sub_buf(diff, q->u.in.ack_start);
- q->u.in.ack_count -= diff;
- if (q->u.in.ack_count <= 0) {
- q->u.in.polling = 0;
- q->u.in.ack_count = 0;
- goto set;
- }
- q->u.in.ack_start = add_buf(q->u.in.ack_start, diff);
- }
- else
- /* the only ACK will be deleted, so stop polling */
- q->u.in.polling = 0;
+ /* If any processed SBALs are returned to HW, adjust our tracking: */
+ overlap = min_t(int, count - sub_buf(q->u.in.batch_start, bufnr),
+ q->u.in.batch_count);
+ if (overlap > 0) {
+ q->u.in.batch_start = add_buf(q->u.in.batch_start, overlap);
+ q->u.in.batch_count -= overlap;
}
-set:
count = set_buf_states(q, bufnr, SLSB_CU_INPUT_EMPTY, count);
atomic_add(count, &q->nr_buf_used);
@@ -1526,7 +1433,7 @@
* @count: how many buffers are filled
*/
static int handle_outbound(struct qdio_q *q, unsigned int callflags,
- int bufnr, int count)
+ unsigned int bufnr, unsigned int count)
{
const unsigned int scan_threshold = q->irq_ptr->scan_threshold;
unsigned char state = 0;
@@ -1549,13 +1456,10 @@
if (queue_type(q) == QDIO_IQDIO_QFMT) {
unsigned long phys_aob = 0;
- /* One SIGA-W per buffer required for unicast HSI */
- WARN_ON_ONCE(count > 1 && !multicast_outbound(q));
-
- if (q->u.out.use_cq)
+ if (q->u.out.use_cq && count == 1)
phys_aob = qdio_aob_for_buffer(&q->u.out, bufnr);
- rc = qdio_kick_outbound_q(q, phys_aob);
+ rc = qdio_kick_outbound_q(q, count, phys_aob);
} else if (need_siga_sync(q)) {
rc = qdio_siga_sync_q(q);
} else if (count < QDIO_MAX_BUFFERS_PER_Q &&
@@ -1564,7 +1468,7 @@
/* The previous buffer is not processed yet, tack on. */
qperf_inc(q, fast_requeue);
} else {
- rc = qdio_kick_outbound_q(q, 0);
+ rc = qdio_kick_outbound_q(q, count, 0);
}
/* Let drivers implement their own completion scanning: */
@@ -1593,12 +1497,11 @@
int do_QDIO(struct ccw_device *cdev, unsigned int callflags,
int q_nr, unsigned int bufnr, unsigned int count)
{
- struct qdio_irq *irq_ptr;
+ struct qdio_irq *irq_ptr = cdev->private->qdio_data;
if (bufnr >= QDIO_MAX_BUFFERS_PER_Q || count > QDIO_MAX_BUFFERS_PER_Q)
return -EINVAL;
- irq_ptr = cdev->private->qdio_data;
if (!irq_ptr)
return -ENODEV;
@@ -1620,26 +1523,26 @@
EXPORT_SYMBOL_GPL(do_QDIO);
/**
- * qdio_start_irq - process input buffers
+ * qdio_start_irq - enable interrupt processing for the device
* @cdev: associated ccw_device for the qdio subchannel
- * @nr: input queue number
*
* Return codes
* 0 - success
* 1 - irqs not started since new data is available
*/
-int qdio_start_irq(struct ccw_device *cdev, int nr)
+int qdio_start_irq(struct ccw_device *cdev)
{
struct qdio_q *q;
struct qdio_irq *irq_ptr = cdev->private->qdio_data;
+ unsigned int i;
if (!irq_ptr)
return -ENODEV;
- q = irq_ptr->input_qs[nr];
- clear_nonshared_ind(irq_ptr);
- qdio_stop_polling(q);
- clear_bit(QDIO_QUEUE_IRQS_DISABLED, &q->u.in.queue_irq_state);
+ for_each_input_queue(irq_ptr, q, i)
+ qdio_stop_polling(q);
+
+ clear_bit(QDIO_IRQ_DISABLED, &irq_ptr->poll_state);
/*
* We need to check again to not lose initiative after
@@ -1647,13 +1550,16 @@
*/
if (test_nonshared_ind(irq_ptr))
goto rescan;
- if (!qdio_inbound_q_done(q, q->first_to_check))
- goto rescan;
+
+ for_each_input_queue(irq_ptr, q, i) {
+ if (!qdio_inbound_q_done(q, q->first_to_check))
+ goto rescan;
+ }
+
return 0;
rescan:
- if (test_and_set_bit(QDIO_QUEUE_IRQS_DISABLED,
- &q->u.in.queue_irq_state))
+ if (test_and_set_bit(QDIO_IRQ_DISABLED, &irq_ptr->poll_state))
return 0;
else
return 1;
@@ -1741,117 +1647,25 @@
/**
* qdio_stop_irq - disable interrupt processing for the device
* @cdev: associated ccw_device for the qdio subchannel
- * @nr: input queue number
*
* Return codes
* 0 - interrupts were already disabled
* 1 - interrupts successfully disabled
*/
-int qdio_stop_irq(struct ccw_device *cdev, int nr)
+int qdio_stop_irq(struct ccw_device *cdev)
{
- struct qdio_q *q;
struct qdio_irq *irq_ptr = cdev->private->qdio_data;
if (!irq_ptr)
return -ENODEV;
- q = irq_ptr->input_qs[nr];
- if (test_and_set_bit(QDIO_QUEUE_IRQS_DISABLED,
- &q->u.in.queue_irq_state))
+ if (test_and_set_bit(QDIO_IRQ_DISABLED, &irq_ptr->poll_state))
return 0;
else
return 1;
}
EXPORT_SYMBOL(qdio_stop_irq);
-/**
- * qdio_pnso_brinfo() - perform network subchannel op #0 - bridge info.
- * @schid: Subchannel ID.
- * @cnc: Boolean Change-Notification Control
- * @response: Response code will be stored at this address
- * @cb: Callback function will be executed for each element
- * of the address list
- * @priv: Pointer to pass to the callback function.
- *
- * Performs "Store-network-bridging-information list" operation and calls
- * the callback function for every entry in the list. If "change-
- * notification-control" is set, further changes in the address list
- * will be reported via the IPA command.
- */
-int qdio_pnso_brinfo(struct subchannel_id schid,
- int cnc, u16 *response,
- void (*cb)(void *priv, enum qdio_brinfo_entry_type type,
- void *entry),
- void *priv)
-{
- struct chsc_pnso_area *rr;
- int rc;
- u32 prev_instance = 0;
- int isfirstblock = 1;
- int i, size, elems;
-
- rr = (struct chsc_pnso_area *)get_zeroed_page(GFP_KERNEL);
- if (rr == NULL)
- return -ENOMEM;
- do {
- /* on the first iteration, naihdr.resume_token will be zero */
- rc = chsc_pnso_brinfo(schid, rr, rr->naihdr.resume_token, cnc);
- if (rc != 0 && rc != -EBUSY)
- goto out;
- if (rr->response.code != 1) {
- rc = -EIO;
- continue;
- } else
- rc = 0;
-
- if (cb == NULL)
- continue;
-
- size = rr->naihdr.naids;
- elems = (rr->response.length -
- sizeof(struct chsc_header) -
- sizeof(struct chsc_brinfo_naihdr)) /
- size;
-
- if (!isfirstblock && (rr->naihdr.instance != prev_instance)) {
- /* Inform the caller that they need to scrap */
- /* the data that was already reported via cb */
- rc = -EAGAIN;
- break;
- }
- isfirstblock = 0;
- prev_instance = rr->naihdr.instance;
- for (i = 0; i < elems; i++)
- switch (size) {
- case sizeof(struct qdio_brinfo_entry_l3_ipv6):
- (*cb)(priv, l3_ipv6_addr,
- &rr->entries.l3_ipv6[i]);
- break;
- case sizeof(struct qdio_brinfo_entry_l3_ipv4):
- (*cb)(priv, l3_ipv4_addr,
- &rr->entries.l3_ipv4[i]);
- break;
- case sizeof(struct qdio_brinfo_entry_l2):
- (*cb)(priv, l2_addr_lnid,
- &rr->entries.l2[i]);
- break;
- default:
- WARN_ON_ONCE(1);
- rc = -EIO;
- goto out;
- }
- } while (rr->response.code == 0x0107 || /* channel busy */
- (rr->response.code == 1 && /* list stored */
- /* resume token is non-zero => list incomplete */
- (rr->naihdr.resume_token.t1 || rr->naihdr.resume_token.t2)));
- (*response) = rr->response.code;
-
-out:
- free_page((unsigned long)rr);
- return rc;
-}
-EXPORT_SYMBOL_GPL(qdio_pnso_brinfo);
-
static int __init init_QDIO(void)
{
int rc;
@@ -1862,16 +1676,11 @@
rc = qdio_setup_init();
if (rc)
goto out_debug;
- rc = tiqdio_allocate_memory();
+ rc = qdio_thinint_init();
if (rc)
goto out_cache;
- rc = tiqdio_register_thinints();
- if (rc)
- goto out_ti;
return 0;
-out_ti:
- tiqdio_free_memory();
out_cache:
qdio_setup_exit();
out_debug:
@@ -1881,8 +1690,7 @@
static void __exit exit_QDIO(void)
{
- tiqdio_unregister_thinints();
- tiqdio_free_memory();
+ qdio_thinint_exit();
qdio_setup_exit();
qdio_debug_exit();
}
diff --git a/drivers/s390/cio/qdio_setup.c b/drivers/s390/cio/qdio_setup.c
index 9dc56aa..a5b2e16 100644
--- a/drivers/s390/cio/qdio_setup.c
+++ b/drivers/s390/cio/qdio_setup.c
@@ -9,6 +9,8 @@
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/io.h>
+
+#include <asm/ebcdic.h>
#include <asm/qdio.h>
#include "cio.h"
@@ -135,6 +137,27 @@
}
}
+static void __qdio_free_queues(struct qdio_q **queues, unsigned int count)
+{
+ struct qdio_q *q;
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ q = queues[i];
+ free_page((unsigned long) q->slib);
+ kmem_cache_free(qdio_q_cache, q);
+ }
+}
+
+void qdio_free_queues(struct qdio_irq *irq_ptr)
+{
+ __qdio_free_queues(irq_ptr->input_qs, irq_ptr->max_input_qs);
+ irq_ptr->max_input_qs = 0;
+
+ __qdio_free_queues(irq_ptr->output_qs, irq_ptr->max_output_qs);
+ irq_ptr->max_output_qs = 0;
+}
+
static int __qdio_allocate_qs(struct qdio_q **irq_ptr_qs, int nr_queues)
{
struct qdio_q *q;
@@ -142,16 +165,18 @@
for (i = 0; i < nr_queues; i++) {
q = kmem_cache_zalloc(qdio_q_cache, GFP_KERNEL);
- if (!q)
+ if (!q) {
+ __qdio_free_queues(irq_ptr_qs, i);
return -ENOMEM;
+ }
q->slib = (struct slib *) __get_free_page(GFP_KERNEL);
if (!q->slib) {
kmem_cache_free(qdio_q_cache, q);
+ __qdio_free_queues(irq_ptr_qs, i);
return -ENOMEM;
}
irq_ptr_qs[i] = q;
- INIT_LIST_HEAD(&q->entry);
}
return 0;
}
@@ -163,8 +188,16 @@
rc = __qdio_allocate_qs(irq_ptr->input_qs, nr_input_qs);
if (rc)
return rc;
+
rc = __qdio_allocate_qs(irq_ptr->output_qs, nr_output_qs);
- return rc;
+ if (rc) {
+ __qdio_free_queues(irq_ptr->input_qs, nr_input_qs);
+ return rc;
+ }
+
+ irq_ptr->max_input_qs = nr_input_qs;
+ irq_ptr->max_output_qs = nr_output_qs;
+ return 0;
}
static void setup_queues_misc(struct qdio_q *q, struct qdio_irq *irq_ptr,
@@ -180,7 +213,6 @@
q->mask = 1 << (31 - i);
q->nr = i;
q->handler = handler;
- INIT_LIST_HEAD(&q->entry);
}
static void setup_storage_lists(struct qdio_q *q, struct qdio_irq *irq_ptr,
@@ -215,8 +247,6 @@
struct qdio_initialize *qdio_init)
{
struct qdio_q *q;
- struct qdio_buffer **input_sbal_array = qdio_init->input_sbal_addr_array;
- struct qdio_buffer **output_sbal_array = qdio_init->output_sbal_addr_array;
struct qdio_outbuf_state *output_sbal_state_array =
qdio_init->output_sbal_state_array;
int i;
@@ -226,11 +256,9 @@
setup_queues_misc(q, irq_ptr, qdio_init->input_handler, i);
q->is_input_q = 1;
- q->u.in.queue_start_poll = qdio_init->queue_start_poll_array ?
- qdio_init->queue_start_poll_array[i] : NULL;
- setup_storage_lists(q, irq_ptr, input_sbal_array, i);
- input_sbal_array += QDIO_MAX_BUFFERS_PER_Q;
+ setup_storage_lists(q, irq_ptr,
+ qdio_init->input_sbal_addr_array[i], i);
if (is_thinint_irq(irq_ptr)) {
tasklet_init(&q->tasklet, tiqdio_inbound_processing,
@@ -249,8 +277,8 @@
output_sbal_state_array += QDIO_MAX_BUFFERS_PER_Q;
q->is_input_q = 0;
- setup_storage_lists(q, irq_ptr, output_sbal_array, i);
- output_sbal_array += QDIO_MAX_BUFFERS_PER_Q;
+ setup_storage_lists(q, irq_ptr,
+ qdio_init->output_sbal_addr_array[i], i);
tasklet_init(&q->tasklet, qdio_outbound_processing,
(unsigned long) q);
@@ -353,69 +381,46 @@
DBF_EVENT("3:%4x qib:%4x", irq_ptr->ssqd_desc.qdioac3, irq_ptr->qib.ac);
}
-void qdio_release_memory(struct qdio_irq *irq_ptr)
+void qdio_free_async_data(struct qdio_irq *irq_ptr)
{
struct qdio_q *q;
int i;
- /*
- * Must check queue array manually since irq_ptr->nr_input_queues /
- * irq_ptr->nr_input_queues may not yet be set.
- */
- for (i = 0; i < QDIO_MAX_QUEUES_PER_IRQ; i++) {
- q = irq_ptr->input_qs[i];
- if (q) {
- free_page((unsigned long) q->slib);
- kmem_cache_free(qdio_q_cache, q);
- }
- }
- for (i = 0; i < QDIO_MAX_QUEUES_PER_IRQ; i++) {
+ for (i = 0; i < irq_ptr->max_output_qs; i++) {
q = irq_ptr->output_qs[i];
- if (q) {
- if (q->u.out.use_cq) {
- int n;
+ if (q->u.out.use_cq) {
+ unsigned int n;
- for (n = 0; n < QDIO_MAX_BUFFERS_PER_Q; ++n) {
- struct qaob *aob = q->u.out.aobs[n];
- if (aob) {
- qdio_release_aob(aob);
- q->u.out.aobs[n] = NULL;
- }
+ for (n = 0; n < QDIO_MAX_BUFFERS_PER_Q; n++) {
+ struct qaob *aob = q->u.out.aobs[n];
+
+ if (aob) {
+ qdio_release_aob(aob);
+ q->u.out.aobs[n] = NULL;
}
-
- qdio_disable_async_operation(&q->u.out);
}
- free_page((unsigned long) q->slib);
- kmem_cache_free(qdio_q_cache, q);
+
+ qdio_disable_async_operation(&q->u.out);
}
}
- free_page((unsigned long) irq_ptr->qdr);
- free_page(irq_ptr->chsc_page);
- free_page((unsigned long) irq_ptr);
}
-static void __qdio_allocate_fill_qdr(struct qdio_irq *irq_ptr,
- struct qdio_q **irq_ptr_qs,
- int i, int nr)
+static void qdio_fill_qdr_desc(struct qdesfmt0 *desc, struct qdio_q *queue)
{
- irq_ptr->qdr->qdf0[i + nr].sliba =
- (unsigned long)irq_ptr_qs[i]->slib;
+ desc->sliba = virt_to_phys(queue->slib);
+ desc->sla = virt_to_phys(queue->sl);
+ desc->slsba = virt_to_phys(&queue->slsb);
- irq_ptr->qdr->qdf0[i + nr].sla =
- (unsigned long)irq_ptr_qs[i]->sl;
-
- irq_ptr->qdr->qdf0[i + nr].slsba =
- (unsigned long)&irq_ptr_qs[i]->slsb.val[0];
-
- irq_ptr->qdr->qdf0[i + nr].akey = PAGE_DEFAULT_KEY >> 4;
- irq_ptr->qdr->qdf0[i + nr].bkey = PAGE_DEFAULT_KEY >> 4;
- irq_ptr->qdr->qdf0[i + nr].ckey = PAGE_DEFAULT_KEY >> 4;
- irq_ptr->qdr->qdf0[i + nr].dkey = PAGE_DEFAULT_KEY >> 4;
+ desc->akey = PAGE_DEFAULT_KEY >> 4;
+ desc->bkey = PAGE_DEFAULT_KEY >> 4;
+ desc->ckey = PAGE_DEFAULT_KEY >> 4;
+ desc->dkey = PAGE_DEFAULT_KEY >> 4;
}
static void setup_qdr(struct qdio_irq *irq_ptr,
struct qdio_initialize *qdio_init)
{
+ struct qdesfmt0 *desc = &irq_ptr->qdr->qdf0[0];
int i;
irq_ptr->qdr->qfmt = qdio_init->q_format;
@@ -424,15 +429,14 @@
irq_ptr->qdr->oqdcnt = qdio_init->no_output_qs;
irq_ptr->qdr->iqdsz = sizeof(struct qdesfmt0) / 4; /* size in words */
irq_ptr->qdr->oqdsz = sizeof(struct qdesfmt0) / 4;
- irq_ptr->qdr->qiba = (unsigned long)&irq_ptr->qib;
+ irq_ptr->qdr->qiba = virt_to_phys(&irq_ptr->qib);
irq_ptr->qdr->qkey = PAGE_DEFAULT_KEY >> 4;
for (i = 0; i < qdio_init->no_input_qs; i++)
- __qdio_allocate_fill_qdr(irq_ptr, irq_ptr->input_qs, i, 0);
+ qdio_fill_qdr_desc(desc++, irq_ptr->input_qs[i]);
for (i = 0; i < qdio_init->no_output_qs; i++)
- __qdio_allocate_fill_qdr(irq_ptr, irq_ptr->output_qs, i,
- qdio_init->no_input_qs);
+ qdio_fill_qdr_desc(desc++, irq_ptr->output_qs[i]);
}
static void setup_qib(struct qdio_irq *irq_ptr,
@@ -450,13 +454,14 @@
if (init_data->no_output_qs)
irq_ptr->qib.osliba =
(unsigned long)(irq_ptr->output_qs[0]->slib);
- memcpy(irq_ptr->qib.ebcnam, init_data->adapter_name, 8);
+ memcpy(irq_ptr->qib.ebcnam, dev_name(&irq_ptr->cdev->dev), 8);
+ ASCEBC(irq_ptr->qib.ebcnam, 8);
}
-int qdio_setup_irq(struct qdio_initialize *init_data)
+int qdio_setup_irq(struct qdio_irq *irq_ptr, struct qdio_initialize *init_data)
{
+ struct ccw_device *cdev = irq_ptr->cdev;
struct ciw *ciw;
- struct qdio_irq *irq_ptr = init_data->cdev->private->qdio_data;
memset(&irq_ptr->qib, 0, sizeof(irq_ptr->qib));
memset(&irq_ptr->siga_flag, 0, sizeof(irq_ptr->siga_flag));
@@ -464,8 +469,9 @@
memset(&irq_ptr->ssqd_desc, 0, sizeof(irq_ptr->ssqd_desc));
memset(&irq_ptr->perf_stat, 0, sizeof(irq_ptr->perf_stat));
- irq_ptr->debugfs_dev = irq_ptr->debugfs_perf = NULL;
- irq_ptr->sch_token = irq_ptr->state = irq_ptr->perf_stat_enabled = 0;
+ irq_ptr->debugfs_dev = NULL;
+ irq_ptr->sch_token = irq_ptr->perf_stat_enabled = 0;
+ irq_ptr->state = QDIO_IRQ_STATE_INACTIVE;
/* wipes qib.ac, required by ar7063 */
memset(irq_ptr->qdr, 0, sizeof(struct qdr));
@@ -473,11 +479,17 @@
irq_ptr->int_parm = init_data->int_parm;
irq_ptr->nr_input_qs = init_data->no_input_qs;
irq_ptr->nr_output_qs = init_data->no_output_qs;
- irq_ptr->cdev = init_data->cdev;
irq_ptr->scan_threshold = init_data->scan_threshold;
- ccw_device_get_schid(irq_ptr->cdev, &irq_ptr->schid);
+ ccw_device_get_schid(cdev, &irq_ptr->schid);
setup_queues(irq_ptr, init_data);
+ if (init_data->irq_poll) {
+ irq_ptr->irq_poll = init_data->irq_poll;
+ set_bit(QDIO_IRQ_DISABLED, &irq_ptr->poll_state);
+ } else {
+ irq_ptr->irq_poll = NULL;
+ }
+
setup_qib(irq_ptr, init_data);
set_impl_params(irq_ptr, init_data->qib_param_field_format,
init_data->qib_param_field,
@@ -489,37 +501,48 @@
/* qdr, qib, sls, slsbs, slibs, sbales are filled now */
+ /* set our IRQ handler */
+ spin_lock_irq(get_ccwdev_lock(cdev));
+ irq_ptr->orig_handler = cdev->handler;
+ cdev->handler = qdio_int_handler;
+ spin_unlock_irq(get_ccwdev_lock(cdev));
+
/* get qdio commands */
- ciw = ccw_device_get_ciw(init_data->cdev, CIW_TYPE_EQUEUE);
+ ciw = ccw_device_get_ciw(cdev, CIW_TYPE_EQUEUE);
if (!ciw) {
DBF_ERROR("%4x NO EQ", irq_ptr->schid.sch_no);
return -EINVAL;
}
irq_ptr->equeue = *ciw;
- ciw = ccw_device_get_ciw(init_data->cdev, CIW_TYPE_AQUEUE);
+ ciw = ccw_device_get_ciw(cdev, CIW_TYPE_AQUEUE);
if (!ciw) {
DBF_ERROR("%4x NO AQ", irq_ptr->schid.sch_no);
return -EINVAL;
}
irq_ptr->aqueue = *ciw;
- /* set new interrupt handler */
- spin_lock_irq(get_ccwdev_lock(irq_ptr->cdev));
- irq_ptr->orig_handler = init_data->cdev->handler;
- init_data->cdev->handler = qdio_int_handler;
- spin_unlock_irq(get_ccwdev_lock(irq_ptr->cdev));
return 0;
}
-void qdio_print_subchannel_info(struct qdio_irq *irq_ptr,
- struct ccw_device *cdev)
+void qdio_shutdown_irq(struct qdio_irq *irq)
+{
+ struct ccw_device *cdev = irq->cdev;
+
+ /* restore IRQ handler */
+ spin_lock_irq(get_ccwdev_lock(cdev));
+ cdev->handler = irq->orig_handler;
+ cdev->private->intparm = 0;
+ spin_unlock_irq(get_ccwdev_lock(cdev));
+}
+
+void qdio_print_subchannel_info(struct qdio_irq *irq_ptr)
{
char s[80];
snprintf(s, 80, "qdio: %s %s on SC %x using "
"AI:%d QEBSM:%d PRI:%d TDD:%d SIGA:%s%s%s%s%s\n",
- dev_name(&cdev->dev),
+ dev_name(&irq_ptr->cdev->dev),
(irq_ptr->qib.qfmt == QDIO_QETH_QFMT) ? "OSA" :
((irq_ptr->qib.qfmt == QDIO_ZFCP_QFMT) ? "ZFCP" : "HS"),
irq_ptr->schid.sch_no,
@@ -538,7 +561,7 @@
int qdio_enable_async_operation(struct qdio_output_q *outq)
{
outq->aobs = kcalloc(QDIO_MAX_BUFFERS_PER_Q, sizeof(struct qaob *),
- GFP_ATOMIC);
+ GFP_KERNEL);
if (!outq->aobs) {
outq->use_cq = 0;
return -ENOMEM;
diff --git a/drivers/s390/cio/qdio_thinint.c b/drivers/s390/cio/qdio_thinint.c
index ddf780b..7a440e4 100644
--- a/drivers/s390/cio/qdio_thinint.c
+++ b/drivers/s390/cio/qdio_thinint.c
@@ -39,14 +39,6 @@
static LIST_HEAD(tiq_list);
static DEFINE_MUTEX(tiq_list_lock);
-/* Adapter interrupt definitions */
-static void tiqdio_thinint_handler(struct airq_struct *airq, bool floating);
-
-static struct airq_struct tiqdio_airq = {
- .handler = tiqdio_thinint_handler,
- .isc = QDIO_AIRQ_ISC,
-};
-
static struct indicator_t *q_indicators;
u64 last_ai_time;
@@ -74,31 +66,20 @@
atomic_dec(&ind->count);
}
-void tiqdio_add_input_queues(struct qdio_irq *irq_ptr)
+void tiqdio_add_device(struct qdio_irq *irq_ptr)
{
mutex_lock(&tiq_list_lock);
- list_add_rcu(&irq_ptr->input_qs[0]->entry, &tiq_list);
+ list_add_rcu(&irq_ptr->entry, &tiq_list);
mutex_unlock(&tiq_list_lock);
}
-void tiqdio_remove_input_queues(struct qdio_irq *irq_ptr)
+void tiqdio_remove_device(struct qdio_irq *irq_ptr)
{
- struct qdio_q *q;
-
- q = irq_ptr->input_qs[0];
- if (!q)
- return;
-
mutex_lock(&tiq_list_lock);
- list_del_rcu(&q->entry);
+ list_del_rcu(&irq_ptr->entry);
mutex_unlock(&tiq_list_lock);
synchronize_rcu();
- INIT_LIST_HEAD(&q->entry);
-}
-
-static inline int has_multiple_inq_on_dsci(struct qdio_irq *irq_ptr)
-{
- return irq_ptr->nr_input_qs > 1;
+ INIT_LIST_HEAD(&irq_ptr->entry);
}
static inline int references_shared_dsci(struct qdio_irq *irq_ptr)
@@ -106,26 +87,11 @@
return irq_ptr->dsci == &q_indicators[TIQDIO_SHARED_IND].ind;
}
-static inline int shared_ind(struct qdio_irq *irq_ptr)
-{
- return references_shared_dsci(irq_ptr) ||
- has_multiple_inq_on_dsci(irq_ptr);
-}
-
-void clear_nonshared_ind(struct qdio_irq *irq_ptr)
-{
- if (!is_thinint_irq(irq_ptr))
- return;
- if (shared_ind(irq_ptr))
- return;
- xchg(irq_ptr->dsci, 0);
-}
-
int test_nonshared_ind(struct qdio_irq *irq_ptr)
{
if (!is_thinint_irq(irq_ptr))
return 0;
- if (shared_ind(irq_ptr))
+ if (references_shared_dsci(irq_ptr))
return 0;
if (*irq_ptr->dsci)
return 1;
@@ -145,32 +111,24 @@
struct qdio_q *q;
int i;
- if (!references_shared_dsci(irq) &&
- has_multiple_inq_on_dsci(irq))
+ if (!references_shared_dsci(irq))
xchg(irq->dsci, 0);
+ if (irq->irq_poll) {
+ if (!test_and_set_bit(QDIO_IRQ_DISABLED, &irq->poll_state))
+ irq->irq_poll(irq->cdev, irq->int_parm);
+ else
+ QDIO_PERF_STAT_INC(irq, int_discarded);
+
+ return;
+ }
+
for_each_input_queue(irq, q, i) {
- if (q->u.in.queue_start_poll) {
- /* skip if polling is enabled or already in work */
- if (test_and_set_bit(QDIO_QUEUE_IRQS_DISABLED,
- &q->u.in.queue_irq_state)) {
- qperf_inc(q, int_discarded);
- continue;
- }
-
- /* avoid dsci clear here, done after processing */
- q->u.in.queue_start_poll(irq->cdev, q->nr,
- irq->int_parm);
- } else {
- if (!shared_ind(irq))
- xchg(irq->dsci, 0);
-
- /*
- * Call inbound processing but not directly
- * since that could starve other thinint queues.
- */
- tasklet_schedule(&q->tasklet);
- }
+ /*
+ * Call inbound processing but not directly
+ * since that could starve other thinint queues.
+ */
+ tasklet_schedule(&q->tasklet);
}
}
@@ -182,7 +140,7 @@
static void tiqdio_thinint_handler(struct airq_struct *airq, bool floating)
{
u32 si_used = clear_shared_ind();
- struct qdio_q *q;
+ struct qdio_irq *irq;
last_ai_time = S390_lowcore.int_clock;
inc_irq_stat(IRQIO_QAI);
@@ -190,12 +148,8 @@
/* protect tiq_list entries, only changed in activate or shutdown */
rcu_read_lock();
- /* check for work on all inbound thinint queues */
- list_for_each_entry_rcu(q, &tiq_list, entry) {
- struct qdio_irq *irq;
-
+ list_for_each_entry_rcu(irq, &tiq_list, entry) {
/* only process queues from changed sets */
- irq = q->irq_ptr;
if (unlikely(references_shared_dsci(irq))) {
if (!si_used)
continue;
@@ -204,11 +158,16 @@
tiqdio_call_inq_handlers(irq);
- qperf_inc(q, adapter_int);
+ QDIO_PERF_STAT_INC(irq, adapter_int);
}
rcu_read_unlock();
}
+static struct airq_struct tiqdio_airq = {
+ .handler = tiqdio_thinint_handler,
+ .isc = QDIO_AIRQ_ISC,
+};
+
static int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset)
{
struct chsc_scssc_area *scssc = (void *)irq_ptr->chsc_page;
@@ -224,7 +183,7 @@
}
rc = chsc_sadc(irq_ptr->schid, scssc, summary_indicator_addr,
- subchannel_indicator_addr);
+ subchannel_indicator_addr, tiqdio_airq.isc);
if (rc) {
DBF_ERROR("%4x SSI r:%4x", irq_ptr->schid.sch_no,
scssc->response.code);
@@ -238,34 +197,6 @@
return rc;
}
-/* allocate non-shared indicators and shared indicator */
-int __init tiqdio_allocate_memory(void)
-{
- q_indicators = kcalloc(TIQDIO_NR_INDICATORS,
- sizeof(struct indicator_t),
- GFP_KERNEL);
- if (!q_indicators)
- return -ENOMEM;
- return 0;
-}
-
-void tiqdio_free_memory(void)
-{
- kfree(q_indicators);
-}
-
-int __init tiqdio_register_thinints(void)
-{
- int rc;
-
- rc = register_adapter_interrupt(&tiqdio_airq);
- if (rc) {
- DBF_EVENT("RTI:%x", rc);
- return rc;
- }
- return 0;
-}
-
int qdio_establish_thinint(struct qdio_irq *irq_ptr)
{
int rc;
@@ -293,8 +224,27 @@
put_indicator(irq_ptr->dsci);
}
-void __exit tiqdio_unregister_thinints(void)
+int __init qdio_thinint_init(void)
+{
+ int rc;
+
+ q_indicators = kcalloc(TIQDIO_NR_INDICATORS, sizeof(struct indicator_t),
+ GFP_KERNEL);
+ if (!q_indicators)
+ return -ENOMEM;
+
+ rc = register_adapter_interrupt(&tiqdio_airq);
+ if (rc) {
+ DBF_EVENT("RTI:%x", rc);
+ kfree(q_indicators);
+ return rc;
+ }
+ return 0;
+}
+
+void __exit qdio_thinint_exit(void)
{
WARN_ON(!list_empty(&tiq_list));
unregister_adapter_interrupt(&tiqdio_airq);
+ kfree(q_indicators);
}
diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c
new file mode 100644
index 0000000..13b26a1
--- /dev/null
+++ b/drivers/s390/cio/vfio_ccw_chp.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Channel path related status regions for vfio_ccw
+ *
+ * Copyright IBM Corp. 2020
+ *
+ * Author(s): Farhan Ali <alifm@linux.ibm.com>
+ * Eric Farman <farman@linux.ibm.com>
+ */
+
+#include <linux/slab.h>
+#include <linux/vfio.h>
+#include "vfio_ccw_private.h"
+
+static ssize_t vfio_ccw_schib_region_read(struct vfio_ccw_private *private,
+ char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ unsigned int i = VFIO_CCW_OFFSET_TO_INDEX(*ppos) - VFIO_CCW_NUM_REGIONS;
+ loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK;
+ struct ccw_schib_region *region;
+ int ret;
+
+ if (pos + count > sizeof(*region))
+ return -EINVAL;
+
+ mutex_lock(&private->io_mutex);
+ region = private->region[i].data;
+
+ if (cio_update_schib(private->sch)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ memcpy(region, &private->sch->schib, sizeof(*region));
+
+ if (copy_to_user(buf, (void *)region + pos, count)) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = count;
+
+out:
+ mutex_unlock(&private->io_mutex);
+ return ret;
+}
+
+static ssize_t vfio_ccw_schib_region_write(struct vfio_ccw_private *private,
+ const char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ return -EINVAL;
+}
+
+
+static void vfio_ccw_schib_region_release(struct vfio_ccw_private *private,
+ struct vfio_ccw_region *region)
+{
+
+}
+
+static const struct vfio_ccw_regops vfio_ccw_schib_region_ops = {
+ .read = vfio_ccw_schib_region_read,
+ .write = vfio_ccw_schib_region_write,
+ .release = vfio_ccw_schib_region_release,
+};
+
+int vfio_ccw_register_schib_dev_regions(struct vfio_ccw_private *private)
+{
+ return vfio_ccw_register_dev_region(private,
+ VFIO_REGION_SUBTYPE_CCW_SCHIB,
+ &vfio_ccw_schib_region_ops,
+ sizeof(struct ccw_schib_region),
+ VFIO_REGION_INFO_FLAG_READ,
+ private->schib_region);
+}
+
+static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
+ char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ unsigned int i = VFIO_CCW_OFFSET_TO_INDEX(*ppos) - VFIO_CCW_NUM_REGIONS;
+ loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK;
+ struct ccw_crw_region *region;
+ struct vfio_ccw_crw *crw;
+ int ret;
+
+ if (pos + count > sizeof(*region))
+ return -EINVAL;
+
+ crw = list_first_entry_or_null(&private->crw,
+ struct vfio_ccw_crw, next);
+
+ if (crw)
+ list_del(&crw->next);
+
+ mutex_lock(&private->io_mutex);
+ region = private->region[i].data;
+
+ if (crw)
+ memcpy(®ion->crw, &crw->crw, sizeof(region->crw));
+
+ if (copy_to_user(buf, (void *)region + pos, count))
+ ret = -EFAULT;
+ else
+ ret = count;
+
+ region->crw = 0;
+
+ mutex_unlock(&private->io_mutex);
+
+ kfree(crw);
+
+ /* Notify the guest if more CRWs are on our queue */
+ if (!list_empty(&private->crw) && private->crw_trigger)
+ eventfd_signal(private->crw_trigger, 1);
+
+ return ret;
+}
+
+static ssize_t vfio_ccw_crw_region_write(struct vfio_ccw_private *private,
+ const char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ return -EINVAL;
+}
+
+static void vfio_ccw_crw_region_release(struct vfio_ccw_private *private,
+ struct vfio_ccw_region *region)
+{
+
+}
+
+static const struct vfio_ccw_regops vfio_ccw_crw_region_ops = {
+ .read = vfio_ccw_crw_region_read,
+ .write = vfio_ccw_crw_region_write,
+ .release = vfio_ccw_crw_region_release,
+};
+
+int vfio_ccw_register_crw_dev_regions(struct vfio_ccw_private *private)
+{
+ return vfio_ccw_register_dev_region(private,
+ VFIO_REGION_SUBTYPE_CCW_CRW,
+ &vfio_ccw_crw_region_ops,
+ sizeof(struct ccw_crw_region),
+ VFIO_REGION_INFO_FLAG_READ,
+ private->crw_region);
+}
diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index 9628e0f..8d1b277 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -8,6 +8,7 @@
* Xiao Feng Ren <renxiaof@linux.vnet.ibm.com>
*/
+#include <linux/ratelimit.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/iommu.h>
@@ -625,15 +626,16 @@
* the target channel program from @orb->cmd.iova to the new ccwchain(s).
*
* Limitations:
- * 1. Supports only prefetch enabled mode.
- * 2. Supports idal(c64) ccw chaining.
- * 3. Supports 4k idaw.
+ * 1. Supports idal(c64) ccw chaining.
+ * 2. Supports 4k idaw.
*
* Returns:
* %0 on success and a negative error value on failure.
*/
int cp_init(struct channel_program *cp, struct device *mdev, union orb *orb)
{
+ /* custom ratelimit used to avoid flood during guest IPL */
+ static DEFINE_RATELIMIT_STATE(ratelimit_state, 5 * HZ, 1);
int ret;
/* this is an error in the caller */
@@ -641,11 +643,14 @@
return -EBUSY;
/*
- * XXX:
- * Only support prefetch enable mode now.
+ * We only support prefetching the channel program. We assume all channel
+ * programs executed by supported guests likewise support prefetching.
+ * Executing a channel program that does not specify prefetching will
+ * typically not cause an error, but a warning is issued to help identify
+ * the problem if something does break.
*/
- if (!orb->cmd.pfch)
- return -EOPNOTSUPP;
+ if (!orb->cmd.pfch && __ratelimit(&ratelimit_state))
+ dev_warn(mdev, "Prefetching channel program even though prefetch not specified in ORB");
INIT_LIST_HEAD(&cp->ccwchain_list);
memcpy(&cp->orb, orb, sizeof(*orb));
diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h
index 7cdc380..ba31240 100644
--- a/drivers/s390/cio/vfio_ccw_cp.h
+++ b/drivers/s390/cio/vfio_ccw_cp.h
@@ -15,6 +15,7 @@
#include <asm/scsw.h>
#include "orb.h"
+#include "vfio_ccw_trace.h"
/*
* Max length for ccw chain.
diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c
index fd590d1..9b61e9b 100644
--- a/drivers/s390/cio/vfio_ccw_drv.c
+++ b/drivers/s390/cio/vfio_ccw_drv.c
@@ -19,6 +19,7 @@
#include <asm/isc.h>
+#include "chp.h"
#include "ioasm.h"
#include "css.h"
#include "vfio_ccw_private.h"
@@ -26,6 +27,8 @@
struct workqueue_struct *vfio_ccw_work_q;
static struct kmem_cache *vfio_ccw_io_region;
static struct kmem_cache *vfio_ccw_cmd_region;
+static struct kmem_cache *vfio_ccw_schib_region;
+static struct kmem_cache *vfio_ccw_crw_region;
debug_info_t *vfio_ccw_debug_msg_id;
debug_info_t *vfio_ccw_debug_trace_id;
@@ -113,6 +116,16 @@
eventfd_signal(private->io_trigger, 1);
}
+static void vfio_ccw_crw_todo(struct work_struct *work)
+{
+ struct vfio_ccw_private *private;
+
+ private = container_of(work, struct vfio_ccw_private, crw_work);
+
+ if (!list_empty(&private->crw) && private->crw_trigger)
+ eventfd_signal(private->crw_trigger, 1);
+}
+
/*
* Css driver callbacks
*/
@@ -124,6 +137,18 @@
vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_INTERRUPT);
}
+static void vfio_ccw_free_regions(struct vfio_ccw_private *private)
+{
+ if (private->crw_region)
+ kmem_cache_free(vfio_ccw_crw_region, private->crw_region);
+ if (private->schib_region)
+ kmem_cache_free(vfio_ccw_schib_region, private->schib_region);
+ if (private->cmd_region)
+ kmem_cache_free(vfio_ccw_cmd_region, private->cmd_region);
+ if (private->io_region)
+ kmem_cache_free(vfio_ccw_io_region, private->io_region);
+}
+
static int vfio_ccw_sch_probe(struct subchannel *sch)
{
struct pmcw *pmcw = &sch->schib.pmcw;
@@ -155,6 +180,18 @@
if (!private->cmd_region)
goto out_free;
+ private->schib_region = kmem_cache_zalloc(vfio_ccw_schib_region,
+ GFP_KERNEL | GFP_DMA);
+
+ if (!private->schib_region)
+ goto out_free;
+
+ private->crw_region = kmem_cache_zalloc(vfio_ccw_crw_region,
+ GFP_KERNEL | GFP_DMA);
+
+ if (!private->crw_region)
+ goto out_free;
+
private->sch = sch;
dev_set_drvdata(&sch->dev, private);
mutex_init(&private->io_mutex);
@@ -167,7 +204,9 @@
if (ret)
goto out_free;
+ INIT_LIST_HEAD(&private->crw);
INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo);
+ INIT_WORK(&private->crw_work, vfio_ccw_crw_todo);
atomic_set(&private->avail, 1);
private->state = VFIO_CCW_STATE_STANDBY;
@@ -189,10 +228,7 @@
cio_disable_subchannel(sch);
out_free:
dev_set_drvdata(&sch->dev, NULL);
- if (private->cmd_region)
- kmem_cache_free(vfio_ccw_cmd_region, private->cmd_region);
- if (private->io_region)
- kmem_cache_free(vfio_ccw_io_region, private->io_region);
+ vfio_ccw_free_regions(private);
kfree(private->cp.guest_cp);
kfree(private);
return ret;
@@ -201,15 +237,20 @@
static int vfio_ccw_sch_remove(struct subchannel *sch)
{
struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev);
+ struct vfio_ccw_crw *crw, *temp;
vfio_ccw_sch_quiesce(sch);
+ list_for_each_entry_safe(crw, temp, &private->crw, next) {
+ list_del(&crw->next);
+ kfree(crw);
+ }
+
vfio_ccw_mdev_unreg(sch);
dev_set_drvdata(&sch->dev, NULL);
- kmem_cache_free(vfio_ccw_cmd_region, private->cmd_region);
- kmem_cache_free(vfio_ccw_io_region, private->io_region);
+ vfio_ccw_free_regions(private);
kfree(private->cp.guest_cp);
kfree(private);
@@ -266,6 +307,83 @@
return rc;
}
+static void vfio_ccw_queue_crw(struct vfio_ccw_private *private,
+ unsigned int rsc,
+ unsigned int erc,
+ unsigned int rsid)
+{
+ struct vfio_ccw_crw *crw;
+
+ /*
+ * If unable to allocate a CRW, just drop the event and
+ * carry on. The guest will either see a later one or
+ * learn when it issues its own store subchannel.
+ */
+ crw = kzalloc(sizeof(*crw), GFP_ATOMIC);
+ if (!crw)
+ return;
+
+ /*
+ * Build the CRW based on the inputs given to us.
+ */
+ crw->crw.rsc = rsc;
+ crw->crw.erc = erc;
+ crw->crw.rsid = rsid;
+
+ list_add_tail(&crw->next, &private->crw);
+ queue_work(vfio_ccw_work_q, &private->crw_work);
+}
+
+static int vfio_ccw_chp_event(struct subchannel *sch,
+ struct chp_link *link, int event)
+{
+ struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev);
+ int mask = chp_ssd_get_mask(&sch->ssd_info, link);
+ int retry = 255;
+
+ if (!private || !mask)
+ return 0;
+
+ trace_vfio_ccw_chp_event(private->sch->schid, mask, event);
+ VFIO_CCW_MSG_EVENT(2, "%pUl (%x.%x.%04x): mask=0x%x event=%d\n",
+ mdev_uuid(private->mdev), sch->schid.cssid,
+ sch->schid.ssid, sch->schid.sch_no,
+ mask, event);
+
+ if (cio_update_schib(sch))
+ return -ENODEV;
+
+ switch (event) {
+ case CHP_VARY_OFF:
+ /* Path logically turned off */
+ sch->opm &= ~mask;
+ sch->lpm &= ~mask;
+ if (sch->schib.pmcw.lpum & mask)
+ cio_cancel_halt_clear(sch, &retry);
+ break;
+ case CHP_OFFLINE:
+ /* Path is gone */
+ if (sch->schib.pmcw.lpum & mask)
+ cio_cancel_halt_clear(sch, &retry);
+ vfio_ccw_queue_crw(private, CRW_RSC_CPATH, CRW_ERC_PERRN,
+ link->chpid.id);
+ break;
+ case CHP_VARY_ON:
+ /* Path logically turned on */
+ sch->opm |= mask;
+ sch->lpm |= mask;
+ break;
+ case CHP_ONLINE:
+ /* Path became available */
+ sch->lpm |= mask & sch->opm;
+ vfio_ccw_queue_crw(private, CRW_RSC_CPATH, CRW_ERC_INIT,
+ link->chpid.id);
+ break;
+ }
+
+ return 0;
+}
+
static struct css_device_id vfio_ccw_sch_ids[] = {
{ .match_flags = 0x1, .type = SUBCHANNEL_TYPE_IO, },
{ /* end of list */ },
@@ -283,6 +401,7 @@
.remove = vfio_ccw_sch_remove,
.shutdown = vfio_ccw_sch_shutdown,
.sch_event = vfio_ccw_sch_event,
+ .chp_event = vfio_ccw_chp_event,
};
static int __init vfio_ccw_debug_init(void)
@@ -312,6 +431,14 @@
debug_unregister(vfio_ccw_debug_trace_id);
}
+static void vfio_ccw_destroy_regions(void)
+{
+ kmem_cache_destroy(vfio_ccw_crw_region);
+ kmem_cache_destroy(vfio_ccw_schib_region);
+ kmem_cache_destroy(vfio_ccw_cmd_region);
+ kmem_cache_destroy(vfio_ccw_io_region);
+}
+
static int __init vfio_ccw_sch_init(void)
{
int ret;
@@ -344,6 +471,26 @@
goto out_err;
}
+ vfio_ccw_schib_region = kmem_cache_create_usercopy("vfio_ccw_schib_region",
+ sizeof(struct ccw_schib_region), 0,
+ SLAB_ACCOUNT, 0,
+ sizeof(struct ccw_schib_region), NULL);
+
+ if (!vfio_ccw_schib_region) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+
+ vfio_ccw_crw_region = kmem_cache_create_usercopy("vfio_ccw_crw_region",
+ sizeof(struct ccw_crw_region), 0,
+ SLAB_ACCOUNT, 0,
+ sizeof(struct ccw_crw_region), NULL);
+
+ if (!vfio_ccw_crw_region) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+
isc_register(VFIO_CCW_ISC);
ret = css_driver_register(&vfio_ccw_sch_driver);
if (ret) {
@@ -354,8 +501,7 @@
return ret;
out_err:
- kmem_cache_destroy(vfio_ccw_cmd_region);
- kmem_cache_destroy(vfio_ccw_io_region);
+ vfio_ccw_destroy_regions();
destroy_workqueue(vfio_ccw_work_q);
vfio_ccw_debug_exit();
return ret;
@@ -365,8 +511,7 @@
{
css_driver_unregister(&vfio_ccw_sch_driver);
isc_unregister(VFIO_CCW_ISC);
- kmem_cache_destroy(vfio_ccw_io_region);
- kmem_cache_destroy(vfio_ccw_cmd_region);
+ vfio_ccw_destroy_regions();
destroy_workqueue(vfio_ccw_work_q);
vfio_ccw_debug_exit();
}
diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c
index 4a1e727..e435a9c 100644
--- a/drivers/s390/cio/vfio_ccw_fsm.c
+++ b/drivers/s390/cio/vfio_ccw_fsm.c
@@ -15,9 +15,6 @@
#include "ioasm.h"
#include "vfio_ccw_private.h"
-#define CREATE_TRACE_POINTS
-#include "vfio_ccw_trace.h"
-
static int fsm_io_helper(struct vfio_ccw_private *private)
{
struct subchannel *sch;
@@ -321,8 +318,9 @@
}
err_out:
- trace_vfio_ccw_io_fctl(scsw->cmd.fctl, schid,
- io_region->ret_code, errstr);
+ private->state = VFIO_CCW_STATE_IDLE;
+ trace_vfio_ccw_fsm_io_request(scsw->cmd.fctl, schid,
+ io_region->ret_code, errstr);
}
/*
@@ -344,6 +342,10 @@
/* should not happen? */
cmd_region->ret_code = -EINVAL;
}
+
+ trace_vfio_ccw_fsm_async_request(get_schid(private),
+ cmd_region->command,
+ cmd_region->ret_code);
}
/*
diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
index 15df0a5..2280f51 100644
--- a/drivers/s390/cio/vfio_ccw_ops.c
+++ b/drivers/s390/cio/vfio_ccw_ops.c
@@ -172,8 +172,22 @@
ret = vfio_ccw_register_async_dev_regions(private);
if (ret)
- vfio_unregister_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY,
- &private->nb);
+ goto out_unregister;
+
+ ret = vfio_ccw_register_schib_dev_regions(private);
+ if (ret)
+ goto out_unregister;
+
+ ret = vfio_ccw_register_crw_dev_regions(private);
+ if (ret)
+ goto out_unregister;
+
+ return ret;
+
+out_unregister:
+ vfio_ccw_unregister_dev_regions(private);
+ vfio_unregister_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY,
+ &private->nb);
return ret;
}
@@ -181,7 +195,6 @@
{
struct vfio_ccw_private *private =
dev_get_drvdata(mdev_parent_dev(mdev));
- int i;
if ((private->state != VFIO_CCW_STATE_NOT_OPER) &&
(private->state != VFIO_CCW_STATE_STANDBY)) {
@@ -191,15 +204,9 @@
}
cp_free(&private->cp);
+ vfio_ccw_unregister_dev_regions(private);
vfio_unregister_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY,
&private->nb);
-
- for (i = 0; i < private->num_regions; i++)
- private->region[i].ops->release(private, &private->region[i]);
-
- private->num_regions = 0;
- kfree(private->region);
- private->region = NULL;
}
static ssize_t vfio_ccw_mdev_read_io_region(struct vfio_ccw_private *private,
@@ -269,8 +276,6 @@
}
vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_IO_REQ);
- if (region->ret_code != 0)
- private->state = VFIO_CCW_STATE_IDLE;
ret = (region->ret_code != 0) ? region->ret_code : count;
out_unlock:
@@ -384,17 +389,22 @@
static int vfio_ccw_mdev_get_irq_info(struct vfio_irq_info *info)
{
- if (info->index != VFIO_CCW_IO_IRQ_INDEX)
+ switch (info->index) {
+ case VFIO_CCW_IO_IRQ_INDEX:
+ case VFIO_CCW_CRW_IRQ_INDEX:
+ info->count = 1;
+ info->flags = VFIO_IRQ_INFO_EVENTFD;
+ break;
+ default:
return -EINVAL;
-
- info->count = 1;
- info->flags = VFIO_IRQ_INFO_EVENTFD;
+ }
return 0;
}
static int vfio_ccw_mdev_set_irqs(struct mdev_device *mdev,
uint32_t flags,
+ uint32_t index,
void __user *data)
{
struct vfio_ccw_private *private;
@@ -404,7 +414,17 @@
return -EINVAL;
private = dev_get_drvdata(mdev_parent_dev(mdev));
- ctx = &private->io_trigger;
+
+ switch (index) {
+ case VFIO_CCW_IO_IRQ_INDEX:
+ ctx = &private->io_trigger;
+ break;
+ case VFIO_CCW_CRW_IRQ_INDEX:
+ ctx = &private->crw_trigger;
+ break;
+ default:
+ return -EINVAL;
+ }
switch (flags & VFIO_IRQ_SET_DATA_TYPE_MASK) {
case VFIO_IRQ_SET_DATA_NONE:
@@ -482,6 +502,17 @@
return 0;
}
+void vfio_ccw_unregister_dev_regions(struct vfio_ccw_private *private)
+{
+ int i;
+
+ for (i = 0; i < private->num_regions; i++)
+ private->region[i].ops->release(private, &private->region[i]);
+ private->num_regions = 0;
+ kfree(private->region);
+ private->region = NULL;
+}
+
static ssize_t vfio_ccw_mdev_ioctl(struct mdev_device *mdev,
unsigned int cmd,
unsigned long arg)
@@ -565,7 +596,7 @@
return ret;
data = (void __user *)(arg + minsz);
- return vfio_ccw_mdev_set_irqs(mdev, hdr.flags, data);
+ return vfio_ccw_mdev_set_irqs(mdev, hdr.flags, hdr.index, data);
}
case VFIO_DEVICE_RESET:
return vfio_ccw_mdev_reset(mdev);
diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h
index bbe9bab..8723156 100644
--- a/drivers/s390/cio/vfio_ccw_private.h
+++ b/drivers/s390/cio/vfio_ccw_private.h
@@ -17,6 +17,7 @@
#include <linux/eventfd.h>
#include <linux/workqueue.h>
#include <linux/vfio_ccw.h>
+#include <asm/crw.h>
#include <asm/debug.h>
#include "css.h"
@@ -53,8 +54,16 @@
unsigned int subtype,
const struct vfio_ccw_regops *ops,
size_t size, u32 flags, void *data);
+void vfio_ccw_unregister_dev_regions(struct vfio_ccw_private *private);
int vfio_ccw_register_async_dev_regions(struct vfio_ccw_private *private);
+int vfio_ccw_register_schib_dev_regions(struct vfio_ccw_private *private);
+int vfio_ccw_register_crw_dev_regions(struct vfio_ccw_private *private);
+
+struct vfio_ccw_crw {
+ struct list_head next;
+ struct crw crw;
+};
/**
* struct vfio_ccw_private
@@ -68,6 +77,8 @@
* @io_mutex: protect against concurrent update of I/O regions
* @region: additional regions for other subchannel operations
* @cmd_region: MMIO region for asynchronous I/O commands other than START
+ * @schib_region: MMIO region for SCHIB information
+ * @crw_region: MMIO region for getting channel report words
* @num_regions: number of additional regions
* @cp: channel program for the current I/O operation
* @irb: irb info received from interrupt
@@ -86,14 +97,19 @@
struct mutex io_mutex;
struct vfio_ccw_region *region;
struct ccw_cmd_region *cmd_region;
+ struct ccw_schib_region *schib_region;
+ struct ccw_crw_region *crw_region;
int num_regions;
struct channel_program cp;
struct irb irb;
union scsw scsw;
+ struct list_head crw;
struct eventfd_ctx *io_trigger;
+ struct eventfd_ctx *crw_trigger;
struct work_struct io_work;
+ struct work_struct crw_work;
} __aligned(8);
extern int vfio_ccw_mdev_reg(struct subchannel *sch);
@@ -135,6 +151,7 @@
static inline void vfio_ccw_fsm_event(struct vfio_ccw_private *private,
int event)
{
+ trace_vfio_ccw_fsm_event(private->sch->schid, private->state, event);
vfio_ccw_jumptable[private->state][event](private, event);
}
diff --git a/drivers/s390/cio/vfio_ccw_trace.c b/drivers/s390/cio/vfio_ccw_trace.c
new file mode 100644
index 0000000..4a02059
--- /dev/null
+++ b/drivers/s390/cio/vfio_ccw_trace.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tracepoint definitions for vfio_ccw
+ *
+ * Copyright IBM Corp. 2019
+ * Author(s): Eric Farman <farman@linux.ibm.com>
+ */
+
+#define CREATE_TRACE_POINTS
+#include "vfio_ccw_trace.h"
+
+EXPORT_TRACEPOINT_SYMBOL(vfio_ccw_chp_event);
+EXPORT_TRACEPOINT_SYMBOL(vfio_ccw_fsm_async_request);
+EXPORT_TRACEPOINT_SYMBOL(vfio_ccw_fsm_event);
+EXPORT_TRACEPOINT_SYMBOL(vfio_ccw_fsm_io_request);
diff --git a/drivers/s390/cio/vfio_ccw_trace.h b/drivers/s390/cio/vfio_ccw_trace.h
index b1da53d..62fb305 100644
--- a/drivers/s390/cio/vfio_ccw_trace.h
+++ b/drivers/s390/cio/vfio_ccw_trace.h
@@ -1,5 +1,5 @@
-/* SPDX-License-Identifier: GPL-2.0
- * Tracepoints for vfio_ccw driver
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Tracepoints for vfio_ccw driver
*
* Copyright IBM Corp. 2018
*
@@ -7,6 +7,8 @@
* Halil Pasic <pasic@linux.vnet.ibm.com>
*/
+#include "cio.h"
+
#undef TRACE_SYSTEM
#define TRACE_SYSTEM vfio_ccw
@@ -15,28 +17,118 @@
#include <linux/tracepoint.h>
-TRACE_EVENT(vfio_ccw_io_fctl,
+TRACE_EVENT(vfio_ccw_chp_event,
+ TP_PROTO(struct subchannel_id schid,
+ int mask,
+ int event),
+ TP_ARGS(schid, mask, event),
+
+ TP_STRUCT__entry(
+ __field(u8, cssid)
+ __field(u8, ssid)
+ __field(u16, sch_no)
+ __field(int, mask)
+ __field(int, event)
+ ),
+
+ TP_fast_assign(
+ __entry->cssid = schid.cssid;
+ __entry->ssid = schid.ssid;
+ __entry->sch_no = schid.sch_no;
+ __entry->mask = mask;
+ __entry->event = event;
+ ),
+
+ TP_printk("schid=%x.%x.%04x mask=0x%x event=%d",
+ __entry->cssid,
+ __entry->ssid,
+ __entry->sch_no,
+ __entry->mask,
+ __entry->event)
+);
+
+TRACE_EVENT(vfio_ccw_fsm_async_request,
+ TP_PROTO(struct subchannel_id schid,
+ int command,
+ int errno),
+ TP_ARGS(schid, command, errno),
+
+ TP_STRUCT__entry(
+ __field(u8, cssid)
+ __field(u8, ssid)
+ __field(u16, sch_no)
+ __field(int, command)
+ __field(int, errno)
+ ),
+
+ TP_fast_assign(
+ __entry->cssid = schid.cssid;
+ __entry->ssid = schid.ssid;
+ __entry->sch_no = schid.sch_no;
+ __entry->command = command;
+ __entry->errno = errno;
+ ),
+
+ TP_printk("schid=%x.%x.%04x command=0x%x errno=%d",
+ __entry->cssid,
+ __entry->ssid,
+ __entry->sch_no,
+ __entry->command,
+ __entry->errno)
+);
+
+TRACE_EVENT(vfio_ccw_fsm_event,
+ TP_PROTO(struct subchannel_id schid, int state, int event),
+ TP_ARGS(schid, state, event),
+
+ TP_STRUCT__entry(
+ __field(u8, cssid)
+ __field(u8, ssid)
+ __field(u16, schno)
+ __field(int, state)
+ __field(int, event)
+ ),
+
+ TP_fast_assign(
+ __entry->cssid = schid.cssid;
+ __entry->ssid = schid.ssid;
+ __entry->schno = schid.sch_no;
+ __entry->state = state;
+ __entry->event = event;
+ ),
+
+ TP_printk("schid=%x.%x.%04x state=%d event=%d",
+ __entry->cssid, __entry->ssid, __entry->schno,
+ __entry->state,
+ __entry->event)
+);
+
+TRACE_EVENT(vfio_ccw_fsm_io_request,
TP_PROTO(int fctl, struct subchannel_id schid, int errno, char *errstr),
TP_ARGS(fctl, schid, errno, errstr),
TP_STRUCT__entry(
+ __field(u8, cssid)
+ __field(u8, ssid)
+ __field(u16, sch_no)
__field(int, fctl)
- __field_struct(struct subchannel_id, schid)
__field(int, errno)
__field(char*, errstr)
),
TP_fast_assign(
+ __entry->cssid = schid.cssid;
+ __entry->ssid = schid.ssid;
+ __entry->sch_no = schid.sch_no;
__entry->fctl = fctl;
- __entry->schid = schid;
__entry->errno = errno;
__entry->errstr = errstr;
),
- TP_printk("schid=%x.%x.%04x fctl=%x errno=%d info=%s",
- __entry->schid.cssid,
- __entry->schid.ssid,
- __entry->schid.sch_no,
+ TP_printk("schid=%x.%x.%04x fctl=0x%x errno=%d info=%s",
+ __entry->cssid,
+ __entry->ssid,
+ __entry->sch_no,
__entry->fctl,
__entry->errno,
__entry->errstr)