From 66aec647216f129b8560dba738303a8486481c53 Mon Sep 17 00:00:00 2001 From: Peter Oberparleiter Date: Mon, 13 Feb 2017 13:38:17 +0100 Subject: s390/sclp: Add support for Store Data SCLP interface Add functions to retrieve data associated with an SCLP Store Data entity. Automatically retrieve data for the "config" entity during boot and make that data available to user-space via sysfs: /sys/firmware/sclp_sd/config/data Reading from this file will return config data contents. /sys/firmware/sclp_sd/config/reload Writing to this file will cause the latest version of data related to the config entity to be read from the SCLP interface. Generate a KOBJ_CHANGE whenever new data is retrieved. Signed-off-by: Peter Oberparleiter Signed-off-by: Martin Schwidefsky --- drivers/s390/char/Makefile | 2 +- drivers/s390/char/sclp.h | 2 + drivers/s390/char/sclp_sd.c | 569 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 572 insertions(+), 1 deletion(-) create mode 100644 drivers/s390/char/sclp_sd.c (limited to 'drivers/s390') diff --git a/drivers/s390/char/Makefile b/drivers/s390/char/Makefile index a2b33a22c82a..d049e2d74484 100644 --- a/drivers/s390/char/Makefile +++ b/drivers/s390/char/Makefile @@ -23,7 +23,7 @@ CFLAGS_REMOVE_sclp_early_core.o += $(CC_FLAGS_EXPOLINE) obj-y += ctrlchar.o keyboard.o defkeymap.o sclp.o sclp_rw.o sclp_quiesce.o \ sclp_cmd.o sclp_config.o sclp_cpi_sys.o sclp_ocf.o sclp_ctl.o \ - sclp_early.o sclp_early_core.o + sclp_early.o sclp_early_core.o sclp_sd.o obj-$(CONFIG_TN3270) += raw3270.o obj-$(CONFIG_TN3270_CONSOLE) += con3270.o diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h index f41f6e2ca063..9b10f12d3f53 100644 --- a/drivers/s390/char/sclp.h +++ b/drivers/s390/char/sclp.h @@ -28,6 +28,7 @@ #define EVTYP_PMSGCMD 0x09 #define EVTYP_ASYNC 0x0A #define EVTYP_CTLPROGIDENT 0x0B +#define EVTYP_STORE_DATA 0x0C #define EVTYP_ERRNOTIFY 0x18 #define EVTYP_VT220MSG 0x1A #define EVTYP_SDIAS 0x1C @@ -42,6 +43,7 @@ #define EVTYP_PMSGCMD_MASK SCLP_EVTYP_MASK(EVTYP_PMSGCMD) #define EVTYP_ASYNC_MASK SCLP_EVTYP_MASK(EVTYP_ASYNC) #define EVTYP_CTLPROGIDENT_MASK SCLP_EVTYP_MASK(EVTYP_CTLPROGIDENT) +#define EVTYP_STORE_DATA_MASK SCLP_EVTYP_MASK(EVTYP_STORE_DATA) #define EVTYP_ERRNOTIFY_MASK SCLP_EVTYP_MASK(EVTYP_ERRNOTIFY) #define EVTYP_VT220MSG_MASK SCLP_EVTYP_MASK(EVTYP_VT220MSG) #define EVTYP_SDIAS_MASK SCLP_EVTYP_MASK(EVTYP_SDIAS) diff --git a/drivers/s390/char/sclp_sd.c b/drivers/s390/char/sclp_sd.c new file mode 100644 index 000000000000..99f41db5123b --- /dev/null +++ b/drivers/s390/char/sclp_sd.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SCLP Store Data support and sysfs interface + * + * Copyright IBM Corp. 2017 + */ + +#define KMSG_COMPONENT "sclp_sd" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sclp.h" + +#define SD_EQ_STORE_DATA 0 +#define SD_EQ_HALT 1 +#define SD_EQ_SIZE 2 + +#define SD_DI_CONFIG 3 + +struct sclp_sd_evbuf { + struct evbuf_header hdr; + u8 eq; + u8 di; + u8 rflags; + u64 :56; + u32 id; + u16 :16; + u8 fmt; + u8 status; + u64 sat; + u64 sa; + u32 esize; + u32 dsize; +} __packed; + +struct sclp_sd_sccb { + struct sccb_header hdr; + struct sclp_sd_evbuf evbuf; +} __packed __aligned(PAGE_SIZE); + +/** + * struct sclp_sd_data - Result of a Store Data request + * @esize_bytes: Resulting esize in bytes + * @dsize_bytes: Resulting dsize in bytes + * @data: Pointer to data - must be released using vfree() + */ +struct sclp_sd_data { + size_t esize_bytes; + size_t dsize_bytes; + void *data; +}; + +/** + * struct sclp_sd_listener - Listener for asynchronous Store Data response + * @list: For enqueueing this struct + * @id: Event ID of response to listen for + * @completion: Can be used to wait for response + * @evbuf: Contains the resulting Store Data response after completion + */ +struct sclp_sd_listener { + struct list_head list; + u32 id; + struct completion completion; + struct sclp_sd_evbuf evbuf; +}; + +/** + * struct sclp_sd_file - Sysfs representation of a Store Data entity + * @kobj: Kobject + * @data_attr: Attribute for accessing data contents + * @data_mutex: Mutex to serialize access and updates to @data + * @data: Data associated with this entity + * @di: DI value associated with this entity + */ +struct sclp_sd_file { + struct kobject kobj; + struct bin_attribute data_attr; + struct mutex data_mutex; + struct sclp_sd_data data; + u8 di; +}; +#define to_sd_file(x) container_of(x, struct sclp_sd_file, kobj) + +static struct kset *sclp_sd_kset; +static struct sclp_sd_file *config_file; + +static LIST_HEAD(sclp_sd_queue); +static DEFINE_SPINLOCK(sclp_sd_queue_lock); + +/** + * sclp_sd_listener_add() - Add listener for Store Data responses + * @listener: Listener to add + */ +static void sclp_sd_listener_add(struct sclp_sd_listener *listener) +{ + spin_lock_irq(&sclp_sd_queue_lock); + list_add_tail(&listener->list, &sclp_sd_queue); + spin_unlock_irq(&sclp_sd_queue_lock); +} + +/** + * sclp_sd_listener_remove() - Remove listener for Store Data responses + * @listener: Listener to remove + */ +static void sclp_sd_listener_remove(struct sclp_sd_listener *listener) +{ + spin_lock_irq(&sclp_sd_queue_lock); + list_del(&listener->list); + spin_unlock_irq(&sclp_sd_queue_lock); +} + +/** + * sclp_sd_listener_init() - Initialize a Store Data response listener + * @id: Event ID to listen for + * + * Initialize a listener for asynchronous Store Data responses. This listener + * can afterwards be used to wait for a specific response and to retrieve + * the associated response data. + */ +static void sclp_sd_listener_init(struct sclp_sd_listener *listener, u32 id) +{ + memset(listener, 0, sizeof(*listener)); + listener->id = id; + init_completion(&listener->completion); +} + +/** + * sclp_sd_receiver() - Receiver for Store Data events + * @evbuf_hdr: Header of received events + * + * Process Store Data events and complete listeners with matching event IDs. + */ +static void sclp_sd_receiver(struct evbuf_header *evbuf_hdr) +{ + struct sclp_sd_evbuf *evbuf = (struct sclp_sd_evbuf *) evbuf_hdr; + struct sclp_sd_listener *listener; + int found = 0; + + pr_debug("received event (id=0x%08x)\n", evbuf->id); + spin_lock(&sclp_sd_queue_lock); + list_for_each_entry(listener, &sclp_sd_queue, list) { + if (listener->id != evbuf->id) + continue; + + listener->evbuf = *evbuf; + complete(&listener->completion); + found = 1; + break; + } + spin_unlock(&sclp_sd_queue_lock); + + if (!found) + pr_debug("unsolicited event (id=0x%08x)\n", evbuf->id); +} + +static struct sclp_register sclp_sd_register = { + .send_mask = EVTYP_STORE_DATA_MASK, + .receive_mask = EVTYP_STORE_DATA_MASK, + .receiver_fn = sclp_sd_receiver, +}; + +/** + * sclp_sd_sync() - Perform Store Data request synchronously + * @page: Address of work page - must be below 2GB + * @eq: Input EQ value + * @di: Input DI value + * @sat: Input SAT value + * @sa: Input SA value used to specify the address of the target buffer + * @dsize_ptr: Optional pointer to input and output DSIZE value + * @esize_ptr: Optional pointer to output ESIZE value + * + * Perform Store Data request with specified parameters and wait for completion. + * + * Return %0 on success and store resulting DSIZE and ESIZE values in + * @dsize_ptr and @esize_ptr (if provided). Return non-zero on error. + */ +static int sclp_sd_sync(unsigned long page, u8 eq, u8 di, u64 sat, u64 sa, + u32 *dsize_ptr, u32 *esize_ptr) +{ + struct sclp_sd_sccb *sccb = (void *) page; + struct sclp_sd_listener listener; + struct sclp_sd_evbuf *evbuf; + int rc; + + sclp_sd_listener_init(&listener, (u32) (addr_t) sccb); + sclp_sd_listener_add(&listener); + + /* Prepare SCCB */ + memset(sccb, 0, PAGE_SIZE); + sccb->hdr.length = sizeof(sccb->hdr) + sizeof(sccb->evbuf); + evbuf = &sccb->evbuf; + evbuf->hdr.length = sizeof(*evbuf); + evbuf->hdr.type = EVTYP_STORE_DATA; + evbuf->eq = eq; + evbuf->di = di; + evbuf->id = listener.id; + evbuf->fmt = 1; + evbuf->sat = sat; + evbuf->sa = sa; + if (dsize_ptr) + evbuf->dsize = *dsize_ptr; + + /* Perform command */ + pr_debug("request (eq=%d, di=%d, id=0x%08x)\n", eq, di, listener.id); + rc = sclp_sync_request(SCLP_CMDW_WRITE_EVENT_DATA, sccb); + pr_debug("request done (rc=%d)\n", rc); + if (rc) + goto out; + + /* Evaluate response */ + if (sccb->hdr.response_code == 0x73f0) { + pr_debug("event not supported\n"); + rc = -EIO; + goto out_remove; + } + if (sccb->hdr.response_code != 0x0020 || !(evbuf->hdr.flags & 0x80)) { + rc = -EIO; + goto out; + } + if (!(evbuf->rflags & 0x80)) { + rc = wait_for_completion_interruptible(&listener.completion); + if (rc) + goto out; + evbuf = &listener.evbuf; + } + switch (evbuf->status) { + case 0: + if (dsize_ptr) + *dsize_ptr = evbuf->dsize; + if (esize_ptr) + *esize_ptr = evbuf->esize; + pr_debug("success (dsize=%u, esize=%u)\n", evbuf->dsize, + evbuf->esize); + break; + case 3: + rc = -ENOENT; + break; + default: + rc = -EIO; + break; + + } + +out: + if (rc && rc != -ENOENT) { + /* Provide some information about what went wrong */ + pr_warn("Store Data request failed (eq=%d, di=%d, " + "response=0x%04x, flags=0x%02x, status=%d, rc=%d)\n", + eq, di, sccb->hdr.response_code, evbuf->hdr.flags, + evbuf->status, rc); + } + +out_remove: + sclp_sd_listener_remove(&listener); + + return rc; +} + +/** + * sclp_sd_store_data() - Obtain data for specified Store Data entity + * @result: Resulting data + * @di: DI value associated with this entity + * + * Perform a series of Store Data requests to obtain the size and contents of + * the specified Store Data entity. + * + * Return: + * %0: Success - result is stored in @result. @result->data must be + * released using vfree() after use. + * %-ENOENT: No data available for this entity + * %<0: Other error + */ +static int sclp_sd_store_data(struct sclp_sd_data *result, u8 di) +{ + u32 dsize = 0, esize = 0; + unsigned long page, asce = 0; + void *data = NULL; + int rc; + + page = __get_free_page(GFP_KERNEL | GFP_DMA); + if (!page) + return -ENOMEM; + + /* Get size */ + rc = sclp_sd_sync(page, SD_EQ_SIZE, di, 0, 0, &dsize, &esize); + if (rc) + goto out; + if (dsize == 0) + goto out_result; + + /* Allocate memory */ + data = vzalloc((size_t) dsize * PAGE_SIZE); + if (!data) { + rc = -ENOMEM; + goto out; + } + + /* Get translation table for buffer */ + asce = base_asce_alloc((unsigned long) data, dsize); + if (!asce) { + vfree(data); + rc = -ENOMEM; + goto out; + } + + /* Get data */ + rc = sclp_sd_sync(page, SD_EQ_STORE_DATA, di, asce, (u64) data, &dsize, + &esize); + if (rc) { + /* Cancel running request if interrupted */ + if (rc == -ERESTARTSYS) + sclp_sd_sync(page, SD_EQ_HALT, di, 0, 0, NULL, NULL); + vfree(data); + goto out; + } + +out_result: + result->esize_bytes = (size_t) esize * PAGE_SIZE; + result->dsize_bytes = (size_t) dsize * PAGE_SIZE; + result->data = data; + +out: + base_asce_free(asce); + free_page(page); + + return rc; +} + +/** + * sclp_sd_data_reset() - Reset Store Data result buffer + * @data: Data buffer to reset + * + * Reset @data to initial state and release associated memory. + */ +static void sclp_sd_data_reset(struct sclp_sd_data *data) +{ + vfree(data->data); + data->data = NULL; + data->dsize_bytes = 0; + data->esize_bytes = 0; +} + +/** + * sclp_sd_file_release() - Release function for sclp_sd_file object + * @kobj: Kobject embedded in sclp_sd_file object + */ +static void sclp_sd_file_release(struct kobject *kobj) +{ + struct sclp_sd_file *sd_file = to_sd_file(kobj); + + sclp_sd_data_reset(&sd_file->data); + kfree(sd_file); +} + +/** + * sclp_sd_file_update() - Update contents of sclp_sd_file object + * @sd_file: Object to update + * + * Obtain the current version of data associated with the Store Data entity + * @sd_file. + * + * On success, return %0 and generate a KOBJ_CHANGE event to indicate that the + * data may have changed. Return non-zero otherwise. + */ +static int sclp_sd_file_update(struct sclp_sd_file *sd_file) +{ + const char *name = kobject_name(&sd_file->kobj); + struct sclp_sd_data data; + int rc; + + rc = sclp_sd_store_data(&data, sd_file->di); + if (rc) { + if (rc == -ENOENT) { + pr_info("No data is available for the %s data entity\n", + name); + } + return rc; + } + + mutex_lock(&sd_file->data_mutex); + sclp_sd_data_reset(&sd_file->data); + sd_file->data = data; + mutex_unlock(&sd_file->data_mutex); + + pr_info("A %zu-byte %s data entity was retrieved\n", data.dsize_bytes, + name); + kobject_uevent(&sd_file->kobj, KOBJ_CHANGE); + + return 0; +} + +/** + * sclp_sd_file_update_async() - Wrapper for asynchronous update call + * @data: Object to update + */ +static void sclp_sd_file_update_async(void *data, async_cookie_t cookie) +{ + struct sclp_sd_file *sd_file = data; + + sclp_sd_file_update(sd_file); +} + +/** + * reload_store() - Store function for "reload" sysfs attribute + * @kobj: Kobject of sclp_sd_file object + * + * Initiate a reload of the data associated with an sclp_sd_file object. + */ +static ssize_t reload_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct sclp_sd_file *sd_file = to_sd_file(kobj); + + sclp_sd_file_update(sd_file); + + return count; +} + +static struct kobj_attribute reload_attr = __ATTR_WO(reload); + +static struct attribute *sclp_sd_file_default_attrs[] = { + &reload_attr.attr, + NULL, +}; + +static struct kobj_type sclp_sd_file_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .release = sclp_sd_file_release, + .default_attrs = sclp_sd_file_default_attrs, +}; + +/** + * data_read() - Read function for "read" sysfs attribute + * @kobj: Kobject of sclp_sd_file object + * @buffer: Target buffer + * @off: Requested file offset + * @size: Requested number of bytes + * + * Store the requested portion of the Store Data entity contents into the + * specified buffer. Return the number of bytes stored on success, or %0 + * on EOF. + */ +static ssize_t data_read(struct file *file, struct kobject *kobj, + struct bin_attribute *attr, char *buffer, + loff_t off, size_t size) +{ + struct sclp_sd_file *sd_file = to_sd_file(kobj); + size_t data_size; + char *data; + + mutex_lock(&sd_file->data_mutex); + + data = sd_file->data.data; + data_size = sd_file->data.dsize_bytes; + if (!data || off >= data_size) { + size = 0; + } else { + if (off + size > data_size) + size = data_size - off; + memcpy(buffer, data + off, size); + } + + mutex_unlock(&sd_file->data_mutex); + + return size; +} + +/** + * sclp_sd_file_create() - Add a sysfs file representing a Store Data entity + * @name: Name of file + * @di: DI value associated with this entity + * + * Create a sysfs directory with the given @name located under + * + * /sys/firmware/sclp_sd/ + * + * The files in this directory can be used to access the contents of the Store + * Data entity associated with @DI. + * + * Return pointer to resulting sclp_sd_file object on success, %NULL otherwise. + * The object must be freed by calling kobject_put() on the embedded kobject + * pointer after use. + */ +static __init struct sclp_sd_file *sclp_sd_file_create(const char *name, u8 di) +{ + struct sclp_sd_file *sd_file; + int rc; + + sd_file = kzalloc(sizeof(*sd_file), GFP_KERNEL); + if (!sd_file) + return NULL; + sd_file->di = di; + mutex_init(&sd_file->data_mutex); + + /* Create kobject located under /sys/firmware/sclp_sd/ */ + sd_file->kobj.kset = sclp_sd_kset; + rc = kobject_init_and_add(&sd_file->kobj, &sclp_sd_file_ktype, NULL, + "%s", name); + if (rc) { + kobject_put(&sd_file->kobj); + return NULL; + } + + sysfs_bin_attr_init(&sd_file->data_attr); + sd_file->data_attr.attr.name = "data"; + sd_file->data_attr.attr.mode = 0444; + sd_file->data_attr.read = data_read; + + rc = sysfs_create_bin_file(&sd_file->kobj, &sd_file->data_attr); + if (rc) { + kobject_put(&sd_file->kobj); + return NULL; + } + + /* + * For completeness only - users interested in entity data should listen + * for KOBJ_CHANGE instead. + */ + kobject_uevent(&sd_file->kobj, KOBJ_ADD); + + /* Don't let a slow Store Data request delay further initialization */ + async_schedule(sclp_sd_file_update_async, sd_file); + + return sd_file; +} + +/** + * sclp_sd_init() - Initialize sclp_sd support and register sysfs files + */ +static __init int sclp_sd_init(void) +{ + int rc; + + rc = sclp_register(&sclp_sd_register); + if (rc) + return rc; + + /* Create kset named "sclp_sd" located under /sys/firmware/ */ + rc = -ENOMEM; + sclp_sd_kset = kset_create_and_add("sclp_sd", NULL, firmware_kobj); + if (!sclp_sd_kset) + goto err_kset; + + rc = -EINVAL; + config_file = sclp_sd_file_create("config", SD_DI_CONFIG); + if (!config_file) + goto err_config; + + return 0; + +err_config: + kset_unregister(sclp_sd_kset); +err_kset: + sclp_unregister(&sclp_sd_register); + + return rc; +} +device_initcall(sclp_sd_init); -- cgit v1.2.1 From ddc1c9453550eabd6284021b167f708982df3ca9 Mon Sep 17 00:00:00 2001 From: Stefan Haberland Date: Tue, 19 Dec 2017 16:18:38 +0100 Subject: s390/dasd: configurable IFCC handling Make the behavior in case of constant IFCC/CCC errors configurable. Add a sysfs attribute to switch between path disabled after threshold exceeded (default) and message only. Reviewed-by: Jan Hoeppner Reviewed-by: Sebastian Ott Signed-off-by: Stefan Haberland Signed-off-by: Martin Schwidefsky --- drivers/s390/block/dasd_3990_erp.c | 17 +++++++++++++-- drivers/s390/block/dasd_devmap.c | 43 +++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/block/dasd_3990_erp.c b/drivers/s390/block/dasd_3990_erp.c index ee14d8e45c97..ee73b0607e47 100644 --- a/drivers/s390/block/dasd_3990_erp.c +++ b/drivers/s390/block/dasd_3990_erp.c @@ -2214,15 +2214,28 @@ static void dasd_3990_erp_disable_path(struct dasd_device *device, __u8 lpum) { int pos = pathmask_to_pos(lpum); + if (!(device->features & DASD_FEATURE_PATH_AUTODISABLE)) { + dev_err(&device->cdev->dev, + "Path %x.%02x (pathmask %02x) is operational despite excessive IFCCs\n", + device->path[pos].cssid, device->path[pos].chpid, lpum); + goto out; + } + /* no remaining path, cannot disable */ - if (!(dasd_path_get_opm(device) & ~lpum)) - return; + if (!(dasd_path_get_opm(device) & ~lpum)) { + dev_err(&device->cdev->dev, + "Last path %x.%02x (pathmask %02x) is operational despite excessive IFCCs\n", + device->path[pos].cssid, device->path[pos].chpid, lpum); + goto out; + } dev_err(&device->cdev->dev, "Path %x.%02x (pathmask %02x) is disabled - IFCC threshold exceeded\n", device->path[pos].cssid, device->path[pos].chpid, lpum); dasd_path_remove_opm(device, lpum); dasd_path_add_ifccpm(device, lpum); + +out: device->path[pos].errorclk = 0; atomic_set(&device->path[pos].error_count, 0); } diff --git a/drivers/s390/block/dasd_devmap.c b/drivers/s390/block/dasd_devmap.c index e7cd28ff1984..b9ebb565ee2c 100644 --- a/drivers/s390/block/dasd_devmap.c +++ b/drivers/s390/block/dasd_devmap.c @@ -1550,9 +1550,49 @@ dasd_path_threshold_store(struct device *dev, struct device_attribute *attr, dasd_put_device(device); return count; } - static DEVICE_ATTR(path_threshold, 0644, dasd_path_threshold_show, dasd_path_threshold_store); + +/* + * configure if path is disabled after IFCC/CCC error threshold is + * exceeded + */ +static ssize_t +dasd_path_autodisable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dasd_devmap *devmap; + int flag; + + devmap = dasd_find_busid(dev_name(dev)); + if (!IS_ERR(devmap)) + flag = (devmap->features & DASD_FEATURE_PATH_AUTODISABLE) != 0; + else + flag = (DASD_FEATURE_DEFAULT & + DASD_FEATURE_PATH_AUTODISABLE) != 0; + return snprintf(buf, PAGE_SIZE, flag ? "1\n" : "0\n"); +} + +static ssize_t +dasd_path_autodisable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int val; + int rc; + + if (kstrtouint(buf, 0, &val) || val > 1) + return -EINVAL; + + rc = dasd_set_feature(to_ccwdev(dev), + DASD_FEATURE_PATH_AUTODISABLE, val); + + return rc ? : count; +} + +static DEVICE_ATTR(path_autodisable, 0644, + dasd_path_autodisable_show, + dasd_path_autodisable_store); /* * interval for IFCC/CCC checks * meaning time with no IFCC/CCC error before the error counter @@ -1623,6 +1663,7 @@ static struct attribute * dasd_attrs[] = { &dev_attr_host_access_count.attr, &dev_attr_path_masks.attr, &dev_attr_path_threshold.attr, + &dev_attr_path_autodisable.attr, &dev_attr_path_interval.attr, &dev_attr_path_reset.attr, &dev_attr_hpf.attr, -- cgit v1.2.1 From 0ee5f8dcd69d3db8d4e63e79f2962d328c48531f Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Tue, 23 Jan 2018 15:07:09 +0100 Subject: s390/sclp: clean up, use sccb_mask_t where appropriate Replace hardcoded instances where 32 or unsigned int (or long) is used for SCLP event masks, and replace with sizeof(sccb_mask_t) and sccb_mask_t respectively. This improves readability and prepares for when we will increase sccb_mask_t to 64 bits. Reviewed-by: Heiko Carstens Signed-off-by: Claudio Imbrenda Signed-off-by: Martin Schwidefsky --- drivers/s390/char/sclp.c | 6 +++--- drivers/s390/char/sclp.h | 6 +++--- drivers/s390/char/sclp_early_core.c | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c index e4e2df7a478e..fe85f9aaf359 100644 --- a/drivers/s390/char/sclp.c +++ b/drivers/s390/char/sclp.c @@ -417,7 +417,7 @@ sclp_dispatch_evbufs(struct sccb_header *sccb) reg = NULL; list_for_each(l, &sclp_reg_list) { reg = list_entry(l, struct sclp_register, list); - if (reg->receive_mask & (1 << (32 - evbuf->type))) + if (reg->receive_mask & SCLP_EVTYP_MASK(evbuf->type)) break; else reg = NULL; @@ -748,7 +748,7 @@ EXPORT_SYMBOL(sclp_remove_processed); /* Prepare init mask request. Called while sclp_lock is locked. */ static inline void -__sclp_make_init_req(u32 receive_mask, u32 send_mask) +__sclp_make_init_req(sccb_mask_t receive_mask, sccb_mask_t send_mask) { struct init_sccb *sccb; @@ -761,7 +761,7 @@ __sclp_make_init_req(u32 receive_mask, u32 send_mask) sclp_init_req.callback = NULL; sclp_init_req.callback_data = NULL; sclp_init_req.sccb = sccb; - sccb->header.length = sizeof(struct init_sccb); + sccb->header.length = sizeof(*sccb); sccb->mask_length = sizeof(sccb_mask_t); sccb->receive_mask = receive_mask; sccb->send_mask = send_mask; diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h index 9b10f12d3f53..e31aaf266154 100644 --- a/drivers/s390/char/sclp.h +++ b/drivers/s390/char/sclp.h @@ -18,7 +18,7 @@ #define MAX_KMEM_PAGES (sizeof(unsigned long) << 3) #define SCLP_CONSOLE_PAGES 6 -#define SCLP_EVTYP_MASK(T) (1U << (32 - (T))) +#define SCLP_EVTYP_MASK(T) (1UL << (sizeof(sccb_mask_t) * BITS_PER_BYTE - (T))) #define EVTYP_OPCMD 0x01 #define EVTYP_MSG 0x02 @@ -230,8 +230,8 @@ void sclp_early_wait_irq(void); int sclp_early_cmd(sclp_cmdw_t cmd, void *sccb); unsigned int sclp_early_con_check_linemode(struct init_sccb *sccb); int sclp_early_set_event_mask(struct init_sccb *sccb, - unsigned long receive_mask, - unsigned long send_mask); + sccb_mask_t receive_mask, + sccb_mask_t send_mask); /* useful inlines */ diff --git a/drivers/s390/char/sclp_early_core.c b/drivers/s390/char/sclp_early_core.c index 17b0c67f3e8d..e33d8be518d1 100644 --- a/drivers/s390/char/sclp_early_core.c +++ b/drivers/s390/char/sclp_early_core.c @@ -142,8 +142,8 @@ static void sclp_early_print_vt220(const char *str, unsigned int len) } int sclp_early_set_event_mask(struct init_sccb *sccb, - unsigned long receive_mask, - unsigned long send_mask) + sccb_mask_t receive_mask, + sccb_mask_t send_mask) { memset(sccb, 0, sizeof(*sccb)); sccb->header.length = sizeof(*sccb); -- cgit v1.2.1 From b843563518c1e06521c446b9a043b7d352df02e0 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Tue, 23 Jan 2018 16:41:38 +0100 Subject: s390/sclp: generic event mask accessors Switch the layout of the event masks to be a generic buffer, and implement accessors to retrieve the values of the masks. This will be needed in the next patches, where we will eventually switch the mask size to 64 bits. Reviewed-by: Heiko Carstens Signed-off-by: Claudio Imbrenda Signed-off-by: Martin Schwidefsky --- drivers/s390/char/sclp.c | 29 ++++++++++++---------- drivers/s390/char/sclp.h | 48 +++++++++++++++++++++++++++++++++---- drivers/s390/char/sclp_early.c | 2 +- drivers/s390/char/sclp_early_core.c | 19 +++++++++++---- 4 files changed, 75 insertions(+), 23 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c index fe85f9aaf359..59e3219ce5c9 100644 --- a/drivers/s390/char/sclp.c +++ b/drivers/s390/char/sclp.c @@ -618,9 +618,12 @@ struct sclp_statechangebuf { u16 _zeros : 12; u16 mask_length; u64 sclp_active_facility_mask; - sccb_mask_t sclp_receive_mask; - sccb_mask_t sclp_send_mask; - u32 read_data_function_mask; + u8 masks[2 * 1021 + 4]; /* variable length */ + /* + * u8 sclp_receive_mask[mask_length]; + * u8 sclp_send_mask[mask_length]; + * u32 read_data_function_mask; + */ } __attribute__((packed)); @@ -631,14 +634,14 @@ sclp_state_change_cb(struct evbuf_header *evbuf) unsigned long flags; struct sclp_statechangebuf *scbuf; + BUILD_BUG_ON(sizeof(struct sclp_statechangebuf) > PAGE_SIZE); + scbuf = (struct sclp_statechangebuf *) evbuf; - if (scbuf->mask_length != sizeof(sccb_mask_t)) - return; spin_lock_irqsave(&sclp_lock, flags); if (scbuf->validity_sclp_receive_mask) - sclp_receive_mask = scbuf->sclp_receive_mask; + sclp_receive_mask = sccb_get_recv_mask(scbuf); if (scbuf->validity_sclp_send_mask) - sclp_send_mask = scbuf->sclp_send_mask; + sclp_send_mask = sccb_get_send_mask(scbuf); spin_unlock_irqrestore(&sclp_lock, flags); if (scbuf->validity_sclp_active_facility_mask) sclp.facilities = scbuf->sclp_active_facility_mask; @@ -763,10 +766,10 @@ __sclp_make_init_req(sccb_mask_t receive_mask, sccb_mask_t send_mask) sclp_init_req.sccb = sccb; sccb->header.length = sizeof(*sccb); sccb->mask_length = sizeof(sccb_mask_t); - sccb->receive_mask = receive_mask; - sccb->send_mask = send_mask; - sccb->sclp_receive_mask = 0; - sccb->sclp_send_mask = 0; + sccb_set_recv_mask(sccb, receive_mask); + sccb_set_send_mask(sccb, send_mask); + sccb_set_sclp_recv_mask(sccb, 0); + sccb_set_sclp_send_mask(sccb, 0); } /* Start init mask request. If calculate is non-zero, calculate the mask as @@ -822,8 +825,8 @@ sclp_init_mask(int calculate) sccb->header.response_code == 0x20) { /* Successful request */ if (calculate) { - sclp_receive_mask = sccb->sclp_receive_mask; - sclp_send_mask = sccb->sclp_send_mask; + sclp_receive_mask = sccb_get_sclp_recv_mask(sccb); + sclp_send_mask = sccb_get_sclp_send_mask(sccb); } else { sclp_receive_mask = 0; sclp_send_mask = 0; diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h index e31aaf266154..ee44d169f10f 100644 --- a/drivers/s390/char/sclp.h +++ b/drivers/s390/char/sclp.h @@ -100,12 +100,51 @@ struct init_sccb { struct sccb_header header; u16 _reserved; u16 mask_length; - sccb_mask_t receive_mask; - sccb_mask_t send_mask; - sccb_mask_t sclp_receive_mask; - sccb_mask_t sclp_send_mask; + u8 masks[4 * 1021]; /* variable length */ + /* + * u8 receive_mask[mask_length]; + * u8 send_mask[mask_length]; + * u8 sclp_receive_mask[mask_length]; + * u8 sclp_send_mask[mask_length]; + */ } __attribute__((packed)); +static inline sccb_mask_t sccb_get_mask(u8 *masks, size_t len, int i) +{ + sccb_mask_t res = 0; + + memcpy(&res, masks + i * len, min(sizeof(res), len)); + return res; +} + +static inline void sccb_set_mask(u8 *masks, size_t len, int i, sccb_mask_t val) +{ + memset(masks + i * len, 0, len); + memcpy(masks + i * len, &val, min(sizeof(val), len)); +} + +#define sccb_get_generic_mask(sccb, i) \ +({ \ + __typeof__(sccb) __sccb = sccb; \ + \ + sccb_get_mask(__sccb->masks, __sccb->mask_length, i); \ +}) +#define sccb_get_recv_mask(sccb) sccb_get_generic_mask(sccb, 0) +#define sccb_get_send_mask(sccb) sccb_get_generic_mask(sccb, 1) +#define sccb_get_sclp_recv_mask(sccb) sccb_get_generic_mask(sccb, 2) +#define sccb_get_sclp_send_mask(sccb) sccb_get_generic_mask(sccb, 3) + +#define sccb_set_generic_mask(sccb, i, val) \ +({ \ + __typeof__(sccb) __sccb = sccb; \ + \ + sccb_set_mask(__sccb->masks, __sccb->mask_length, i, val); \ +}) +#define sccb_set_recv_mask(sccb, val) sccb_set_generic_mask(sccb, 0, val) +#define sccb_set_send_mask(sccb, val) sccb_set_generic_mask(sccb, 1, val) +#define sccb_set_sclp_recv_mask(sccb, val) sccb_set_generic_mask(sccb, 2, val) +#define sccb_set_sclp_send_mask(sccb, val) sccb_set_generic_mask(sccb, 3, val) + struct read_cpu_info_sccb { struct sccb_header header; u16 nr_configured; @@ -229,6 +268,7 @@ extern char sclp_early_sccb[PAGE_SIZE]; void sclp_early_wait_irq(void); int sclp_early_cmd(sclp_cmdw_t cmd, void *sccb); unsigned int sclp_early_con_check_linemode(struct init_sccb *sccb); +unsigned int sclp_early_con_check_vt220(struct init_sccb *sccb); int sclp_early_set_event_mask(struct init_sccb *sccb, sccb_mask_t receive_mask, sccb_mask_t send_mask); diff --git a/drivers/s390/char/sclp_early.c b/drivers/s390/char/sclp_early.c index 6b1891539c84..9a74abb9224d 100644 --- a/drivers/s390/char/sclp_early.c +++ b/drivers/s390/char/sclp_early.c @@ -249,7 +249,7 @@ static void __init sclp_early_console_detect(struct init_sccb *sccb) if (sccb->header.response_code != 0x20) return; - if (sccb->sclp_send_mask & EVTYP_VT220MSG_MASK) + if (sclp_early_con_check_vt220(sccb)) sclp.has_vt220 = 1; if (sclp_early_con_check_linemode(sccb)) diff --git a/drivers/s390/char/sclp_early_core.c b/drivers/s390/char/sclp_early_core.c index e33d8be518d1..c8c53260f4b7 100644 --- a/drivers/s390/char/sclp_early_core.c +++ b/drivers/s390/char/sclp_early_core.c @@ -148,8 +148,8 @@ int sclp_early_set_event_mask(struct init_sccb *sccb, memset(sccb, 0, sizeof(*sccb)); sccb->header.length = sizeof(*sccb); sccb->mask_length = sizeof(sccb_mask_t); - sccb->receive_mask = receive_mask; - sccb->send_mask = send_mask; + sccb_set_recv_mask(sccb, receive_mask); + sccb_set_send_mask(sccb, send_mask); if (sclp_early_cmd(SCLP_CMDW_WRITE_EVENT_MASK, sccb)) return -EIO; if (sccb->header.response_code != 0x20) @@ -159,19 +159,28 @@ int sclp_early_set_event_mask(struct init_sccb *sccb, unsigned int sclp_early_con_check_linemode(struct init_sccb *sccb) { - if (!(sccb->sclp_send_mask & EVTYP_OPCMD_MASK)) + if (!(sccb_get_sclp_send_mask(sccb) & EVTYP_OPCMD_MASK)) return 0; - if (!(sccb->sclp_receive_mask & (EVTYP_MSG_MASK | EVTYP_PMSGCMD_MASK))) + if (!(sccb_get_sclp_recv_mask(sccb) & (EVTYP_MSG_MASK | EVTYP_PMSGCMD_MASK))) return 0; return 1; } +unsigned int sclp_early_con_check_vt220(struct init_sccb *sccb) +{ + if (sccb_get_sclp_send_mask(sccb) & EVTYP_VT220MSG_MASK) + return 1; + return 0; +} + static int sclp_early_setup(int disable, int *have_linemode, int *have_vt220) { unsigned long receive_mask, send_mask; struct init_sccb *sccb; int rc; + BUILD_BUG_ON(sizeof(struct init_sccb) > PAGE_SIZE); + *have_linemode = *have_vt220 = 0; sccb = (struct init_sccb *) &sclp_early_sccb; receive_mask = disable ? 0 : EVTYP_OPCMD_MASK; @@ -180,7 +189,7 @@ static int sclp_early_setup(int disable, int *have_linemode, int *have_vt220) if (rc) return rc; *have_linemode = sclp_early_con_check_linemode(sccb); - *have_vt220 = sccb->send_mask & EVTYP_VT220MSG_MASK; + *have_vt220 = !!(sccb_get_send_mask(sccb) & EVTYP_VT220MSG_MASK); return rc; } -- cgit v1.2.1 From 0b0d1173d8aef75e821c0cceedb0e8178834ec1b Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Tue, 23 Jan 2018 16:50:43 +0100 Subject: s390/sclp: 32 bit event mask compatibility mode Qemu before version 2.11 does not implement the architecture correctly, and does not allow for a mask size of size different than 4. This patch introduces a compatibility mode for such systems, forcing the mask sizes to 4. Since the mask size is currently still 4 anyway, this patch should have no impact whatsoever by itself, but it will be needed when the mask size is increased to 64 bits in the next patch. Reviewed-by: Heiko Carstens Signed-off-by: Claudio Imbrenda Signed-off-by: Martin Schwidefsky --- drivers/s390/char/sclp.c | 23 ++++++++++++++++------- drivers/s390/char/sclp.h | 3 +++ drivers/s390/char/sclp_early_core.c | 15 ++++++++++++++- 3 files changed, 33 insertions(+), 8 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c index 59e3219ce5c9..e9aa71cdfc44 100644 --- a/drivers/s390/char/sclp.c +++ b/drivers/s390/char/sclp.c @@ -765,7 +765,10 @@ __sclp_make_init_req(sccb_mask_t receive_mask, sccb_mask_t send_mask) sclp_init_req.callback_data = NULL; sclp_init_req.sccb = sccb; sccb->header.length = sizeof(*sccb); - sccb->mask_length = sizeof(sccb_mask_t); + if (sclp_mask_compat_mode) + sccb->mask_length = SCLP_MASK_SIZE_COMPAT; + else + sccb->mask_length = sizeof(sccb_mask_t); sccb_set_recv_mask(sccb, receive_mask); sccb_set_send_mask(sccb, send_mask); sccb_set_sclp_recv_mask(sccb, 0); @@ -977,12 +980,18 @@ sclp_check_interface(void) irq_subclass_unregister(IRQ_SUBCLASS_SERVICE_SIGNAL); spin_lock_irqsave(&sclp_lock, flags); del_timer(&sclp_request_timer); - if (sclp_init_req.status == SCLP_REQ_DONE && - sccb->header.response_code == 0x20) { - rc = 0; - break; - } else - rc = -EBUSY; + rc = -EBUSY; + if (sclp_init_req.status == SCLP_REQ_DONE) { + if (sccb->header.response_code == 0x20) { + rc = 0; + break; + } else if (sccb->header.response_code == 0x74f0) { + if (!sclp_mask_compat_mode) { + sclp_mask_compat_mode = true; + retry = 0; + } + } + } } unregister_external_irq(EXT_IRQ_SERVICE_SIG, sclp_check_handler); spin_unlock_irqrestore(&sclp_lock, flags); diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h index ee44d169f10f..45e6ffdc7f08 100644 --- a/drivers/s390/char/sclp.h +++ b/drivers/s390/char/sclp.h @@ -109,6 +109,8 @@ struct init_sccb { */ } __attribute__((packed)); +#define SCLP_MASK_SIZE_COMPAT 4 + static inline sccb_mask_t sccb_get_mask(u8 *masks, size_t len, int i) { sccb_mask_t res = 0; @@ -262,6 +264,7 @@ extern int sclp_init_state; extern int sclp_console_pages; extern int sclp_console_drop; extern unsigned long sclp_console_full; +extern bool sclp_mask_compat_mode; extern char sclp_early_sccb[PAGE_SIZE]; diff --git a/drivers/s390/char/sclp_early_core.c b/drivers/s390/char/sclp_early_core.c index c8c53260f4b7..5f8d9ea69ebd 100644 --- a/drivers/s390/char/sclp_early_core.c +++ b/drivers/s390/char/sclp_early_core.c @@ -14,6 +14,11 @@ char sclp_early_sccb[PAGE_SIZE] __aligned(PAGE_SIZE) __section(.data); int sclp_init_state __section(.data) = sclp_init_state_uninitialized; +/* + * Used to keep track of the size of the event masks. Qemu until version 2.11 + * only supports 4 and needs a workaround. + */ +bool sclp_mask_compat_mode; void sclp_early_wait_irq(void) { @@ -145,13 +150,21 @@ int sclp_early_set_event_mask(struct init_sccb *sccb, sccb_mask_t receive_mask, sccb_mask_t send_mask) { +retry: memset(sccb, 0, sizeof(*sccb)); sccb->header.length = sizeof(*sccb); - sccb->mask_length = sizeof(sccb_mask_t); + if (sclp_mask_compat_mode) + sccb->mask_length = SCLP_MASK_SIZE_COMPAT; + else + sccb->mask_length = sizeof(sccb_mask_t); sccb_set_recv_mask(sccb, receive_mask); sccb_set_send_mask(sccb, send_mask); if (sclp_early_cmd(SCLP_CMDW_WRITE_EVENT_MASK, sccb)) return -EIO; + if ((sccb->header.response_code == 0x74f0) && !sclp_mask_compat_mode) { + sclp_mask_compat_mode = true; + goto retry; + } if (sccb->header.response_code != 0x20) return -EIO; return 0; -- cgit v1.2.1 From f8f6e27c1a79359091f16d3a3e9fc8b07c70d7c2 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Tue, 23 Jan 2018 16:54:11 +0100 Subject: s390/sclp: 64 bit event mask Change the size of the sclp mask to 64 bits. Reviewed-by: Heiko Carstens Signed-off-by: Claudio Imbrenda Signed-off-by: Martin Schwidefsky --- drivers/s390/char/sclp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/s390') diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h index 45e6ffdc7f08..1fe4918088e7 100644 --- a/drivers/s390/char/sclp.h +++ b/drivers/s390/char/sclp.h @@ -87,7 +87,7 @@ enum sclp_pm_event { #define SCLP_PANIC_PRIO 1 #define SCLP_PANIC_PRIO_CLIENT 0 -typedef u32 sccb_mask_t; /* ATTENTION: assumes 32bit mask !!! */ +typedef u64 sccb_mask_t; struct sccb_header { u16 length; -- cgit v1.2.1 From a1fc8181eb7858400d39bff42e9d31a2e8b80c83 Mon Sep 17 00:00:00 2001 From: Stefan Haberland Date: Mon, 19 Feb 2018 12:24:39 +0100 Subject: s390/dasd: remove unneeded sanity check Reported by smatch that the usage of cqr->block is inconsistent. The sanity check is not needed because _dasd_requeue_request already checks for a valid cqr->block pointer and all referenced ERP requests have a valid cqr->block pointer as well since it is copied during ERP process. Signed-off-by: Stefan Haberland Reviewed-by: Jan Hoeppner Signed-off-by: Martin Schwidefsky --- drivers/s390/block/dasd.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c index ecef8e73d40b..c89c26e40165 100644 --- a/drivers/s390/block/dasd.c +++ b/drivers/s390/block/dasd.c @@ -3918,8 +3918,13 @@ static int dasd_generic_requeue_all_requests(struct dasd_device *device) cqr = refers; } - if (cqr->block) - list_del_init(&cqr->blocklist); + /* + * _dasd_requeue_request already checked for a valid + * blockdevice, no need to check again + * all erp requests (cqr->refers) have a cqr->block + * pointer copy from the original cqr + */ + list_del_init(&cqr->blocklist); cqr->block->base->discipline->free_cp( cqr, (struct request *) cqr->callback_data); } -- cgit v1.2.1 From 5628683cf77bc8dad3336a9b254e55f728f7c70e Mon Sep 17 00:00:00 2001 From: Stefan Haberland Date: Thu, 8 Feb 2018 16:55:28 +0100 Subject: s390/dasd: set timestamps unconditionally Set the XRC timestamps even if XRC is not supported by the storage server to help debugging the storage server firmware. Do not advertise valid time stamps if the system time could not be obtained. Reviewed-by: Sebastian Ott Reviewed-by: Peter Oberparleiter Signed-off-by: Stefan Haberland Signed-off-by: Martin Schwidefsky --- drivers/s390/block/dasd_eckd.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 29397a9dba68..85b6a70ce334 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -214,24 +214,25 @@ static void set_ch_t(struct ch_t *geo, __u32 cyl, __u8 head) geo->head |= head; } -static int check_XRC(struct ccw1 *ccw, struct DE_eckd_data *data, +static int set_timestamp(struct ccw1 *ccw, struct DE_eckd_data *data, struct dasd_device *device) { struct dasd_eckd_private *private = device->private; int rc; - if (!private->rdc_data.facilities.XRC_supported) + rc = get_phys_clock(&data->ep_sys_time); + /* + * Ignore return code if XRC is not supported or + * sync clock is switched off + */ + if ((rc && !private->rdc_data.facilities.XRC_supported) || + rc == -EOPNOTSUPP || rc == -EACCES) return 0; /* switch on System Time Stamp - needed for XRC Support */ data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid' */ data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */ - rc = get_phys_clock(&data->ep_sys_time); - /* Ignore return code if sync clock is switched off. */ - if (rc == -EOPNOTSUPP || rc == -EACCES) - rc = 0; - if (ccw) { ccw->count = sizeof(struct DE_eckd_data); ccw->flags |= CCW_FLAG_SLI; @@ -286,12 +287,12 @@ define_extent(struct ccw1 *ccw, struct DE_eckd_data *data, unsigned int trk, case DASD_ECKD_CCW_WRITE_KD_MT: data->mask.perm = 0x02; data->attributes.operation = private->attrib.operation; - rc = check_XRC(ccw, data, device); + rc = set_timestamp(ccw, data, device); break; case DASD_ECKD_CCW_WRITE_CKD: case DASD_ECKD_CCW_WRITE_CKD_MT: data->attributes.operation = DASD_BYPASS_CACHE; - rc = check_XRC(ccw, data, device); + rc = set_timestamp(ccw, data, device); break; case DASD_ECKD_CCW_ERASE: case DASD_ECKD_CCW_WRITE_HOME_ADDRESS: @@ -299,7 +300,7 @@ define_extent(struct ccw1 *ccw, struct DE_eckd_data *data, unsigned int trk, data->mask.perm = 0x3; data->mask.auth = 0x1; data->attributes.operation = DASD_BYPASS_CACHE; - rc = check_XRC(ccw, data, device); + rc = set_timestamp(ccw, data, device); break; case DASD_ECKD_CCW_WRITE_FULL_TRACK: data->mask.perm = 0x03; @@ -310,7 +311,7 @@ define_extent(struct ccw1 *ccw, struct DE_eckd_data *data, unsigned int trk, data->mask.perm = 0x02; data->attributes.operation = private->attrib.operation; data->blk_size = blksize; - rc = check_XRC(ccw, data, device); + rc = set_timestamp(ccw, data, device); break; default: dev_err(&device->cdev->dev, @@ -3440,7 +3441,7 @@ static int prepare_itcw(struct itcw *itcw, dedata->mask.perm = 0x02; dedata->attributes.operation = basepriv->attrib.operation; dedata->blk_size = blksize; - rc = check_XRC(NULL, dedata, basedev); + rc = set_timestamp(NULL, dedata, basedev); dedata->ga_extended |= 0x42; lredata->operation.orientation = 0x0; lredata->operation.operation = 0x3F; -- cgit v1.2.1 From 9851bc77e62499957567e7c39a5beba7d6de6296 Mon Sep 17 00:00:00 2001 From: Cornelia Huck Date: Thu, 22 Feb 2018 15:35:43 +0100 Subject: vfio-ccw: fence off transport mode vfio-ccw only supports command mode for channel programs, not transport mode. User space is supposed to already take care of that and pass us command-mode ORBs only, but better make sure and return an error to the caller instead of trying to process tcws as ccws. Reviewed-by: Dong Jia Shi Acked-by: Halil Pasic Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_fsm.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index c30420c517b1..ff6963ad6e39 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -124,6 +124,11 @@ static void fsm_io_request(struct vfio_ccw_private *private, if (scsw->cmd.fctl & SCSW_FCTL_START_FUNC) { orb = (union orb *)io_region->orb_area; + /* Don't try to build a cp if transport mode is specified. */ + if (orb->tm.b) { + io_region->ret_code = -EOPNOTSUPP; + goto err_out; + } io_region->ret_code = cp_init(&private->cp, mdev_dev(mdev), orb); if (io_region->ret_code) -- cgit v1.2.1 From aa0f2dd4de202c185b1156b1d43eb23e457e9a3c Mon Sep 17 00:00:00 2001 From: Farhan Ali Date: Thu, 22 Feb 2018 11:22:23 -0500 Subject: s390/char : Rename EBCDIC keymap variables The Linux Virtual Terminal (VT) layer provides a default keymap which is compiled when VT layer is enabled. But at the same time we are also compiling the EBCDIC keymap and this causes the linker to complain. So let's rename the EBCDIC keymap variables to prevent linker conflict. Signed-off-by: Farhan Ali Acked-by: Christian Borntraeger Reviewed-by: Thomas Huth Message-Id: Signed-off-by: Christian Borntraeger Signed-off-by: Martin Schwidefsky --- drivers/s390/char/defkeymap.c | 66 ++++++++++++++++++++++--------------------- drivers/s390/char/keyboard.c | 32 ++++++++++----------- drivers/s390/char/keyboard.h | 11 ++++++++ 3 files changed, 61 insertions(+), 48 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/char/defkeymap.c b/drivers/s390/char/defkeymap.c index 98a5c459a1bf..0f71ecff42fa 100644 --- a/drivers/s390/char/defkeymap.c +++ b/drivers/s390/char/defkeymap.c @@ -9,7 +9,9 @@ #include #include -u_short plain_map[NR_KEYS] = { +#include "keyboard.h" + +u_short ebc_plain_map[NR_KEYS] = { 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, @@ -85,12 +87,12 @@ static u_short shift_ctrl_map[NR_KEYS] = { 0xf20a, 0xf108, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, }; -ushort *key_maps[MAX_NR_KEYMAPS] = { - plain_map, shift_map, NULL, NULL, +ushort *ebc_key_maps[MAX_NR_KEYMAPS] = { + ebc_plain_map, shift_map, NULL, NULL, ctrl_map, shift_ctrl_map, NULL, }; -unsigned int keymap_count = 4; +unsigned int ebc_keymap_count = 4; /* @@ -99,7 +101,7 @@ unsigned int keymap_count = 4; * the default and allocate dynamically in chunks of 512 bytes. */ -char func_buf[] = { +char ebc_func_buf[] = { '\033', '[', '[', 'A', 0, '\033', '[', '[', 'B', 0, '\033', '[', '[', 'C', 0, @@ -123,37 +125,37 @@ char func_buf[] = { }; -char *funcbufptr = func_buf; -int funcbufsize = sizeof(func_buf); -int funcbufleft = 0; /* space left */ - -char *func_table[MAX_NR_FUNC] = { - func_buf + 0, - func_buf + 5, - func_buf + 10, - func_buf + 15, - func_buf + 20, - func_buf + 25, - func_buf + 31, - func_buf + 37, - func_buf + 43, - func_buf + 49, - func_buf + 55, - func_buf + 61, - func_buf + 67, - func_buf + 73, - func_buf + 79, - func_buf + 85, - func_buf + 91, - func_buf + 97, - func_buf + 103, - func_buf + 109, +char *ebc_funcbufptr = ebc_func_buf; +int ebc_funcbufsize = sizeof(ebc_func_buf); +int ebc_funcbufleft = 0; /* space left */ + +char *ebc_func_table[MAX_NR_FUNC] = { + ebc_func_buf + 0, + ebc_func_buf + 5, + ebc_func_buf + 10, + ebc_func_buf + 15, + ebc_func_buf + 20, + ebc_func_buf + 25, + ebc_func_buf + 31, + ebc_func_buf + 37, + ebc_func_buf + 43, + ebc_func_buf + 49, + ebc_func_buf + 55, + ebc_func_buf + 61, + ebc_func_buf + 67, + ebc_func_buf + 73, + ebc_func_buf + 79, + ebc_func_buf + 85, + ebc_func_buf + 91, + ebc_func_buf + 97, + ebc_func_buf + 103, + ebc_func_buf + 109, NULL, }; -struct kbdiacruc accent_table[MAX_DIACR] = { +struct kbdiacruc ebc_accent_table[MAX_DIACR] = { {'^', 'c', 0003}, {'^', 'd', 0004}, {'^', 'z', 0032}, {'^', 0012, 0000}, }; -unsigned int accent_table_size = 4; +unsigned int ebc_accent_table_size = 4; diff --git a/drivers/s390/char/keyboard.c b/drivers/s390/char/keyboard.c index 5b505fdaedec..db1fbf9b00b5 100644 --- a/drivers/s390/char/keyboard.c +++ b/drivers/s390/char/keyboard.c @@ -54,24 +54,24 @@ kbd_alloc(void) { kbd = kzalloc(sizeof(struct kbd_data), GFP_KERNEL); if (!kbd) goto out; - kbd->key_maps = kzalloc(sizeof(key_maps), GFP_KERNEL); + kbd->key_maps = kzalloc(sizeof(ebc_key_maps), GFP_KERNEL); if (!kbd->key_maps) goto out_kbd; - for (i = 0; i < ARRAY_SIZE(key_maps); i++) { - if (key_maps[i]) { - kbd->key_maps[i] = kmemdup(key_maps[i], + for (i = 0; i < ARRAY_SIZE(ebc_key_maps); i++) { + if (ebc_key_maps[i]) { + kbd->key_maps[i] = kmemdup(ebc_key_maps[i], sizeof(u_short) * NR_KEYS, GFP_KERNEL); if (!kbd->key_maps[i]) goto out_maps; } } - kbd->func_table = kzalloc(sizeof(func_table), GFP_KERNEL); + kbd->func_table = kzalloc(sizeof(ebc_func_table), GFP_KERNEL); if (!kbd->func_table) goto out_maps; - for (i = 0; i < ARRAY_SIZE(func_table); i++) { - if (func_table[i]) { - kbd->func_table[i] = kstrdup(func_table[i], + for (i = 0; i < ARRAY_SIZE(ebc_func_table); i++) { + if (ebc_func_table[i]) { + kbd->func_table[i] = kstrdup(ebc_func_table[i], GFP_KERNEL); if (!kbd->func_table[i]) goto out_func; @@ -81,22 +81,22 @@ kbd_alloc(void) { kzalloc(sizeof(fn_handler_fn *) * NR_FN_HANDLER, GFP_KERNEL); if (!kbd->fn_handler) goto out_func; - kbd->accent_table = kmemdup(accent_table, + kbd->accent_table = kmemdup(ebc_accent_table, sizeof(struct kbdiacruc) * MAX_DIACR, GFP_KERNEL); if (!kbd->accent_table) goto out_fn_handler; - kbd->accent_table_size = accent_table_size; + kbd->accent_table_size = ebc_accent_table_size; return kbd; out_fn_handler: kfree(kbd->fn_handler); out_func: - for (i = 0; i < ARRAY_SIZE(func_table); i++) + for (i = 0; i < ARRAY_SIZE(ebc_func_table); i++) kfree(kbd->func_table[i]); kfree(kbd->func_table); out_maps: - for (i = 0; i < ARRAY_SIZE(key_maps); i++) + for (i = 0; i < ARRAY_SIZE(ebc_key_maps); i++) kfree(kbd->key_maps[i]); kfree(kbd->key_maps); out_kbd: @@ -112,10 +112,10 @@ kbd_free(struct kbd_data *kbd) kfree(kbd->accent_table); kfree(kbd->fn_handler); - for (i = 0; i < ARRAY_SIZE(func_table); i++) + for (i = 0; i < ARRAY_SIZE(ebc_func_table); i++) kfree(kbd->func_table[i]); kfree(kbd->func_table); - for (i = 0; i < ARRAY_SIZE(key_maps); i++) + for (i = 0; i < ARRAY_SIZE(ebc_key_maps); i++) kfree(kbd->key_maps[i]); kfree(kbd->key_maps); kfree(kbd); @@ -131,7 +131,7 @@ kbd_ascebc(struct kbd_data *kbd, unsigned char *ascebc) int i, j, k; memset(ascebc, 0x40, 256); - for (i = 0; i < ARRAY_SIZE(key_maps); i++) { + for (i = 0; i < ARRAY_SIZE(ebc_key_maps); i++) { keymap = kbd->key_maps[i]; if (!keymap) continue; @@ -158,7 +158,7 @@ kbd_ebcasc(struct kbd_data *kbd, unsigned char *ebcasc) int i, j, k; memset(ebcasc, ' ', 256); - for (i = 0; i < ARRAY_SIZE(key_maps); i++) { + for (i = 0; i < ARRAY_SIZE(ebc_key_maps); i++) { keymap = kbd->key_maps[i]; if (!keymap) continue; diff --git a/drivers/s390/char/keyboard.h b/drivers/s390/char/keyboard.h index a074d9711628..c467589c7f45 100644 --- a/drivers/s390/char/keyboard.h +++ b/drivers/s390/char/keyboard.h @@ -14,6 +14,17 @@ struct kbd_data; +extern int ebc_funcbufsize, ebc_funcbufleft; +extern char *ebc_func_table[MAX_NR_FUNC]; +extern char ebc_func_buf[]; +extern char *ebc_funcbufptr; +extern unsigned int ebc_keymap_count; + +extern struct kbdiacruc ebc_accent_table[]; +extern unsigned int ebc_accent_table_size; +extern unsigned short *ebc_key_maps[MAX_NR_KEYMAPS]; +extern unsigned short ebc_plain_map[NR_KEYS]; + typedef void (fn_handler_fn)(struct kbd_data *); /* -- cgit v1.2.1 From 936b2161df6af5d9407d243e0257f0c2c8e59a66 Mon Sep 17 00:00:00 2001 From: Christian Borntraeger Date: Wed, 21 Feb 2018 11:54:07 +0000 Subject: s390/sclp_tty: enable line mode tty even if there is an ascii console commit 8f50af49f564d4e5 ("s390/console: Make preferred console handling more consistent") created a separate console state for the ascii console. This has the side effect that we register no tty for the line mode interface as soon as there an ascii interface as default console. Under KVM this results in no getty program on the line mode tty if the guest has both types of interfaces. As we can have multiple ttys at the same time we do not want to disable the tty on sclp_line0 under KVM. So instead of checking for the console mode, we now check for the presence of the sclp line mode interface. As z/VM multiplexes the line mode interface on the 32xx screen we continue to disable the line mode tty for the z/VM case. CC: Peter Oberparleiter Fixes: 8f50af49f564d4e5 ("s390/console: Make preferred console handling more consistent") Signed-off-by: Christian Borntraeger Signed-off-by: Martin Schwidefsky --- drivers/s390/char/sclp_tty.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/s390') diff --git a/drivers/s390/char/sclp_tty.c b/drivers/s390/char/sclp_tty.c index 9f7b87d6d434..5aff8b684eb2 100644 --- a/drivers/s390/char/sclp_tty.c +++ b/drivers/s390/char/sclp_tty.c @@ -502,7 +502,10 @@ sclp_tty_init(void) int i; int rc; - if (!CONSOLE_IS_SCLP) + /* z/VM multiplexes the line mode output on the 32xx screen */ + if (MACHINE_IS_VM && !CONSOLE_IS_SCLP) + return 0; + if (!sclp.has_linemode) return 0; driver = alloc_tty_driver(1); if (!driver) -- cgit v1.2.1 From c9f52c2ddb285738219ff2231a96ba3c8f5bd746 Mon Sep 17 00:00:00 2001 From: Christian Borntraeger Date: Wed, 28 Feb 2018 08:26:40 +0000 Subject: s390/defkeymap: fix global init to zero Signed-off-by: Christian Borntraeger Signed-off-by: Martin Schwidefsky --- drivers/s390/char/defkeymap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/s390') diff --git a/drivers/s390/char/defkeymap.c b/drivers/s390/char/defkeymap.c index 0f71ecff42fa..60845d467a1b 100644 --- a/drivers/s390/char/defkeymap.c +++ b/drivers/s390/char/defkeymap.c @@ -127,7 +127,7 @@ char ebc_func_buf[] = { char *ebc_funcbufptr = ebc_func_buf; int ebc_funcbufsize = sizeof(ebc_func_buf); -int ebc_funcbufleft = 0; /* space left */ +int ebc_funcbufleft; /* space left */ char *ebc_func_table[MAX_NR_FUNC] = { ebc_func_buf + 0, -- cgit v1.2.1 From 152485bf76907ac7a2cc0a63b0822b23ef25da56 Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Wed, 6 Dec 2017 08:53:33 +0100 Subject: s390/qdio: simplify math in get_*_buffer_frontier() When determining the buffer count that get_buf_states() should be queried for, 'count' is capped at 127 buffers. So the check q->first_to_check == (q->first_to_check + count) % 128 can be reduced to count == 0 This helps to emphasize that get_buf_states() is really only called with count > 0. Signed-off-by: Julian Wiedmann Reviewed-by: Benjamin Block Signed-off-by: Martin Schwidefsky --- drivers/s390/cio/qdio_main.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index d5b02de02a3a..6b340e6e29ac 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -502,8 +502,8 @@ static inline void inbound_primed(struct qdio_q *q, int count) static int get_inbound_buffer_frontier(struct qdio_q *q) { - int count, stop; unsigned char state = 0; + int count; q->timestamp = get_tod_clock_fast(); @@ -512,9 +512,7 @@ static int get_inbound_buffer_frontier(struct qdio_q *q) * would return 0. */ count = min(atomic_read(&q->nr_buf_used), QDIO_MAX_BUFFERS_MASK); - stop = add_buf(q->first_to_check, count); - - if (q->first_to_check == stop) + if (!count) goto out; /* @@ -734,8 +732,8 @@ void qdio_inbound_processing(unsigned long data) static int get_outbound_buffer_frontier(struct qdio_q *q) { - int count, stop; unsigned char state = 0; + int count; q->timestamp = get_tod_clock_fast(); @@ -751,8 +749,7 @@ static int get_outbound_buffer_frontier(struct qdio_q *q) * would return 0. */ count = min(atomic_read(&q->nr_buf_used), QDIO_MAX_BUFFERS_MASK); - stop = add_buf(q->first_to_check, count); - if (q->first_to_check == stop) + if (!count) goto out; count = get_buf_states(q, q->first_to_check, &state, count, 0, 1); -- cgit v1.2.1 From 0cf1e05157b9e5530dcc3ca9fec9bf617fc93375 Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Wed, 7 Mar 2018 14:01:01 +0100 Subject: s390/qdio: don't merge ERROR output buffers On an Output queue, both EMPTY and PENDING buffer states imply that the buffer is ready for completion-processing by the upper-layer drivers. So for a non-QEBSM Output queue, get_buf_states() merges mixed batches of PENDING and EMPTY buffers into one large batch of EMPTY buffers. The upper-layer driver (ie. qeth) later distuingishes PENDING from EMPTY by inspecting the slsb_state for QDIO_OUTBUF_STATE_FLAG_PENDING. But the merge logic in get_buf_states() contains a bug that causes us to erronously also merge ERROR buffers into such a batch of EMPTY buffers (ERROR is 0xaf, EMPTY is 0xa1; so ERROR & EMPTY == EMPTY). Effectively, most outbound ERROR buffers are currently discarded silently and processed as if they had succeeded. Note that this affects _all_ non-QEBSM device types, not just IQD with CQ. Fix it by explicitly spelling out the exact conditions for merging. For extracting the "get initial state" part out of the loop, this relies on the fact that get_buf_states() is never called with a count of 0. The QEBSM path already strictly requires this, and the two callers with variable 'count' make sure of it. Fixes: 104ea556ee7f ("qdio: support asynchronous delivery of storage blocks") Cc: #v3.2+ Signed-off-by: Julian Wiedmann Reviewed-by: Ursula Braun Reviewed-by: Benjamin Block Signed-off-by: Martin Schwidefsky --- drivers/s390/cio/qdio_main.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index 6b340e6e29ac..bd26df85f559 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -214,7 +214,10 @@ again: return 0; } -/* returns number of examined buffers and their common state in *state */ +/* + * Returns number of examined buffers and their common state in *state. + * Requested number of buffers-to-examine must be > 0. + */ static inline int get_buf_states(struct qdio_q *q, unsigned int bufnr, unsigned char *state, unsigned int count, int auto_ack, int merge_pending) @@ -225,17 +228,23 @@ static inline int get_buf_states(struct qdio_q *q, unsigned int bufnr, if (is_qebsm(q)) return qdio_do_eqbs(q, state, bufnr, count, auto_ack); - for (i = 0; i < count; i++) { - if (!__state) { - __state = q->slsb.val[bufnr]; - if (merge_pending && __state == SLSB_P_OUTPUT_PENDING) - __state = SLSB_P_OUTPUT_EMPTY; - } else if (merge_pending) { - if ((q->slsb.val[bufnr] & __state) != __state) - break; - } else if (q->slsb.val[bufnr] != __state) - break; + /* get initial state: */ + __state = q->slsb.val[bufnr]; + if (merge_pending && __state == SLSB_P_OUTPUT_PENDING) + __state = SLSB_P_OUTPUT_EMPTY; + + for (i = 1; i < count; i++) { bufnr = next_buf(bufnr); + + /* merge PENDING into EMPTY: */ + if (merge_pending && + q->slsb.val[bufnr] == SLSB_P_OUTPUT_PENDING && + __state == SLSB_P_OUTPUT_EMPTY) + continue; + + /* stop if next state differs from initial state: */ + if (q->slsb.val[bufnr] != __state) + break; } *state = __state; return i; -- cgit v1.2.1 From c11a3dfd6fedd5266c2f9d7286981dc804dfb7cc Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Wed, 7 Mar 2018 14:19:43 +0100 Subject: s390/qdio: restrict buffer merging to eligible devices Only attempt to merge PENDING into EMPTY buffers for devices where the PENDING state is actually expected (ie. IQD with CQ). This might speed up the hot path a little bit. Signed-off-by: Julian Wiedmann Reviewed-by: Ursula Braun Reviewed-by: Benjamin Block Signed-off-by: Martin Schwidefsky --- drivers/s390/cio/qdio_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index bd26df85f559..63c6e9cf958f 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -761,7 +761,8 @@ static int get_outbound_buffer_frontier(struct qdio_q *q) if (!count) goto out; - count = get_buf_states(q, q->first_to_check, &state, count, 0, 1); + count = get_buf_states(q, q->first_to_check, &state, count, 0, + q->u.out.use_cq); if (!count) goto out; -- cgit v1.2.1 From dae55b6fef58530c13df074bcc182c096609339e Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Mon, 5 Mar 2018 09:39:38 +0100 Subject: s390/qdio: don't retry EQBS after CCQ 96 Immediate retry of EQBS after CCQ 96 means that we potentially misreport the state of buffers inspected during the first EQBS call. This occurs when 1. the first EQBS finds all inspected buffers still in the initial state set by the driver (ie INPUT EMPTY or OUTPUT PRIMED), 2. the EQBS terminates early with CCQ 96, and 3. by the time that the second EQBS comes around, the state of those previously inspected buffers has changed. If the state reported by the second EQBS is 'driver-owned', all we know is that the previous buffers are driver-owned now as well. But we can't tell if they all have the same state. So for instance - the second EQBS reports OUTPUT EMPTY, but any number of the previous buffers could be OUTPUT ERROR by now, - the second EQBS reports OUTPUT ERROR, but any number of the previous buffers could be OUTPUT EMPTY by now. Effectively, this can result in both over- and underreporting of errors. If the state reported by the second EQBS is 'HW-owned', that doesn't guarantee that the previous buffers have not been switched to driver-owned in the mean time. So for instance - the second EQBS reports INPUT EMPTY, but any number of the previous buffers could be INPUT PRIMED (or INPUT ERROR) by now. This would result in failure to process pending work on the queue. If it's the final check before yielding initiative, this can cause a (temporary) queue stall due to IRQ avoidance. Fixes: 25f269f17316 ("[S390] qdio: EQBS retry after CCQ 96") Cc: #v3.2+ Signed-off-by: Julian Wiedmann Reviewed-by: Benjamin Block Signed-off-by: Martin Schwidefsky --- drivers/s390/cio/qdio_main.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index 63c6e9cf958f..de647b7e17b1 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -128,7 +128,7 @@ static inline int qdio_check_ccq(struct qdio_q *q, unsigned int ccq) static int qdio_do_eqbs(struct qdio_q *q, unsigned char *state, int start, int count, int auto_ack) { - int rc, tmp_count = count, tmp_start = start, nr = q->nr, retried = 0; + int rc, tmp_count = count, tmp_start = start, nr = q->nr; unsigned int ccq = 0; qperf_inc(q, eqbs); @@ -151,14 +151,7 @@ again: qperf_inc(q, eqbs_partial); DBF_DEV_EVENT(DBF_WARN, q->irq_ptr, "EQBS part:%02x", tmp_count); - /* - * Retry once, if that fails bail out and process the - * extracted buffers before trying again. - */ - if (!retried++) - goto again; - else - return count - tmp_count; + return count - tmp_count; } DBF_ERROR("%4x EQBS ERROR", SCH_NO(q)); -- cgit v1.2.1 From 88bf319fc2d6d971ef8692c2cae7f96708340461 Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Tue, 6 Mar 2018 17:58:49 +0100 Subject: s390/qdio: split up CCQ handling for EQBS / SQBS Get rid of the confusing two-stage translation in a hot path, and only handle CCQs that we anticipate for the respective command. Any unexpected value (such as CCQ 97 (rc == 1) for SQBS) should be considered a severe HW/driver bug, and traced as such. Signed-off-by: Julian Wiedmann Reviewed-by: Benjamin Block Signed-off-by: Martin Schwidefsky --- drivers/s390/cio/qdio_main.c | 77 +++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 44 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index de647b7e17b1..a337281337a7 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -98,22 +98,6 @@ static inline int do_siga_output(unsigned long schid, unsigned long mask, return cc; } -static inline int qdio_check_ccq(struct qdio_q *q, unsigned int ccq) -{ - /* all done or next buffer state different */ - if (ccq == 0 || ccq == 32) - return 0; - /* no buffer processed */ - if (ccq == 97) - return 1; - /* not all buffers processed */ - if (ccq == 96) - return 2; - /* notify devices immediately */ - DBF_ERROR("%4x ccq:%3d", SCH_NO(q), ccq); - return -EIO; -} - /** * qdio_do_eqbs - extract buffer states for QEBSM * @q: queue to manipulate @@ -128,7 +112,7 @@ static inline int qdio_check_ccq(struct qdio_q *q, unsigned int ccq) static int qdio_do_eqbs(struct qdio_q *q, unsigned char *state, int start, int count, int auto_ack) { - int rc, tmp_count = count, tmp_start = start, nr = q->nr; + int tmp_count = count, tmp_start = start, nr = q->nr; unsigned int ccq = 0; qperf_inc(q, eqbs); @@ -138,27 +122,30 @@ static int qdio_do_eqbs(struct qdio_q *q, unsigned char *state, again: ccq = do_eqbs(q->irq_ptr->sch_token, state, nr, &tmp_start, &tmp_count, auto_ack); - rc = qdio_check_ccq(q, ccq); - if (!rc) - return count - tmp_count; - - if (rc == 1) { - DBF_DEV_EVENT(DBF_WARN, q->irq_ptr, "EQBS again:%2d", ccq); - goto again; - } - if (rc == 2) { + switch (ccq) { + case 0: + case 32: + /* all done, or next buffer state different */ + return count - tmp_count; + case 96: + /* not all buffers processed */ qperf_inc(q, eqbs_partial); DBF_DEV_EVENT(DBF_WARN, q->irq_ptr, "EQBS part:%02x", tmp_count); return count - tmp_count; + case 97: + /* no buffer processed */ + DBF_DEV_EVENT(DBF_WARN, q->irq_ptr, "EQBS again:%2d", ccq); + goto again; + default: + DBF_ERROR("%4x ccq:%3d", SCH_NO(q), ccq); + 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); + return 0; } - - 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); - return 0; } /** @@ -178,7 +165,6 @@ static int qdio_do_sqbs(struct qdio_q *q, unsigned char state, int start, unsigned int ccq = 0; int tmp_count = count, tmp_start = start; int nr = q->nr; - int rc; if (!count) return 0; @@ -188,23 +174,26 @@ static int qdio_do_sqbs(struct qdio_q *q, unsigned char state, int start, nr += q->irq_ptr->nr_input_qs; again: ccq = do_sqbs(q->irq_ptr->sch_token, state, nr, &tmp_start, &tmp_count); - rc = qdio_check_ccq(q, ccq); - if (!rc) { + + switch (ccq) { + case 0: + case 32: + /* all done, or active buffer adapter-owned */ WARN_ON_ONCE(tmp_count); return count - tmp_count; - } - - if (rc == 1 || rc == 2) { + case 96: + /* not all buffers processed */ DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "SQBS again:%2d", ccq); qperf_inc(q, sqbs_partial); goto again; + default: + DBF_ERROR("%4x ccq:%3d", SCH_NO(q), ccq); + 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); + return 0; } - - 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); - return 0; } /* -- cgit v1.2.1 From 135a8b4ce5d737115571f08c6d0649f1aed6a48a Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Thu, 15 Mar 2018 15:03:43 +0100 Subject: s390/cio: fix unbind of io_subchannel_driver If the io_subchannel_driver is unbound from a subchannel it bluntly kills all I/O on the subchannel and sets the ccw_device state to not operable before deregistering the ccw_device. However, for online devices we should set the device offline (disband path groups etc.) which does not happen if the device is in not oper state. Simply deregister the ccw device - ccw_device_remove is smart enough to set the device offline properly. If everything fails call io_subchannel_quiesce afterwards as a safeguard. Reported-by: Shalini Chellathurai Saroja Signed-off-by: Sebastian Ott Acked-by: Peter Oberparleiter Signed-off-by: Martin Schwidefsky --- drivers/s390/cio/device.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index f50ea035aa9b..1540229a37bb 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -1073,8 +1073,7 @@ out_schedule: return 0; } -static int -io_subchannel_remove (struct subchannel *sch) +static int io_subchannel_remove(struct subchannel *sch) { struct io_subchannel_private *io_priv = to_io_private(sch); struct ccw_device *cdev; @@ -1082,14 +1081,12 @@ io_subchannel_remove (struct subchannel *sch) cdev = sch_get_cdev(sch); if (!cdev) goto out_free; - io_subchannel_quiesce(sch); - /* Set ccw device to not operational and drop reference. */ - spin_lock_irq(cdev->ccwlock); + + ccw_device_unregister(cdev); + spin_lock_irq(sch->lock); sch_set_cdev(sch, NULL); set_io_private(sch, NULL); - cdev->private->state = DEV_STATE_NOT_OPER; - spin_unlock_irq(cdev->ccwlock); - ccw_device_unregister(cdev); + spin_unlock_irq(sch->lock); out_free: kfree(io_priv); sysfs_remove_group(&sch->dev.kobj, &io_subchannel_attr_group); @@ -1721,6 +1718,7 @@ static int ccw_device_remove(struct device *dev) { struct ccw_device *cdev = to_ccwdev(dev); struct ccw_driver *cdrv = cdev->drv; + struct subchannel *sch; int ret; if (cdrv->remove) @@ -1746,7 +1744,9 @@ static int ccw_device_remove(struct device *dev) ccw_device_set_timeout(cdev, 0); cdev->drv = NULL; cdev->private->int_class = IRQIO_CIO; + sch = to_subchannel(cdev->dev.parent); spin_unlock_irq(cdev->ccwlock); + io_subchannel_quiesce(sch); __disable_cmf(cdev); return 0; -- cgit v1.2.1 From ded27d8d2eae2aefe7d03d3e7c3fbdcd1fd501fc Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Thu, 29 Jun 2017 13:27:22 +0200 Subject: s390/cio: rename struct channel_path_desc Rename struct channel_path_desc to struct channel_path_desc_fmt0 to fit the scheme. Provide a macro for the function wrappers that gather this and related data from firmware. Signed-off-by: Sebastian Ott Reviewed-by: Peter Oberparleiter Signed-off-by: Martin Schwidefsky --- drivers/s390/block/dasd_eckd.c | 2 +- drivers/s390/cio/chp.c | 10 +++---- drivers/s390/cio/chp.h | 4 +-- drivers/s390/cio/chsc.c | 56 +++++++++++++++------------------------ drivers/s390/cio/chsc.h | 4 +-- drivers/s390/cio/device_ops.c | 4 +-- drivers/s390/net/qeth_core_main.c | 2 +- 7 files changed, 34 insertions(+), 48 deletions(-) (limited to 'drivers/s390') diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 85b6a70ce334..be208e7adcb4 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -994,7 +994,7 @@ static int dasd_eckd_read_conf(struct dasd_device *device) struct dasd_eckd_private *private, path_private; struct dasd_uid *uid; char print_path_uid[60], print_device_uid[60]; - struct channel_path_desc *chp_desc; + struct channel_path_desc_fmt0 *chp_desc; struct subchannel_id sch_id; private = device->private; diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c index f95b452b8bbc..34315e65c1c8 100644 --- a/drivers/s390/cio/chp.c +++ b/drivers/s390/cio/chp.c @@ -422,7 +422,7 @@ int chp_update_desc(struct channel_path *chp) { int rc; - rc = chsc_determine_base_channel_path_desc(chp->chpid, &chp->desc); + rc = chsc_determine_fmt0_channel_path_desc(chp->chpid, &chp->desc); if (rc) return rc; @@ -506,20 +506,20 @@ out: * On success return a newly allocated copy of the channel-path description * data associated with the given channel-path ID. Return %NULL on error. */ -struct channel_path_desc *chp_get_chp_desc(struct chp_id chpid) +struct channel_path_desc_fmt0 *chp_get_chp_desc(struct chp_id chpid) { struct channel_path *chp; - struct channel_path_desc *desc; + struct channel_path_desc_fmt0 *desc; chp = chpid_to_chp(chpid); if (!chp) return NULL; - desc = kmalloc(sizeof(struct channel_path_desc), GFP_KERNEL); + desc = kmalloc(sizeof(*desc), GFP_KERNEL); if (!desc) return NULL; mutex_lock(&chp->lock); - memcpy(desc, &chp->desc, sizeof(struct channel_path_desc)); + memcpy(desc, &chp->desc, sizeof(*desc)); mutex_unlock(&chp->lock); return desc; } diff --git a/drivers/s390/cio/chp.h b/drivers/s390/cio/chp.h index 7e80323cd261..6d2bfbbead66 100644 --- a/drivers/s390/cio/chp.h +++ b/drivers/s390/cio/chp.h @@ -44,7 +44,7 @@ struct channel_path { struct chp_id chpid; struct mutex lock; /* Serialize access to below members. */ int state; - struct channel_path_desc desc; + struct channel_path_desc_fmt0 desc; struct channel_path_desc_fmt1 desc_fmt1; /* Channel-measurement related stuff: */ int cmg; @@ -61,7 +61,7 @@ static inline struct channel_path *chpid_to_chp(struct chp_id chpid) int chp_get_status(struct chp_id chpid); u8 chp_get_sch_opm(struct subchannel *sch); int chp_is_registered(struct chp_id chpid); -struct channel_path_desc *chp_get_chp_desc(struct chp_id chpid); +struct channel_path_desc_fmt0 *chp_get_chp_desc(struct chp_id chpid); void chp_remove_cmg_attr(struct channel_path *chp); int chp_add_cmg_attr(struct channel_path *chp); int chp_update_desc(struct channel_path *chp); diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c index c08fc5a8df0c..65290e5ac854 100644 --- a/drivers/s390/cio/chsc.c +++ b/drivers/s390/cio/chsc.c @@ -940,43 +940,29 @@ int chsc_determine_channel_path_desc(struct chp_id chpid, int fmt, int rfmt, } EXPORT_SYMBOL_GPL(chsc_determine_channel_path_desc); -int chsc_determine_base_channel_path_desc(struct chp_id chpid, - struct channel_path_desc *desc) -{ - struct chsc_scpd *scpd_area; - unsigned long flags; - int ret; - - spin_lock_irqsave(&chsc_page_lock, flags); - scpd_area = chsc_page; - ret = chsc_determine_channel_path_desc(chpid, 0, 0, 0, 0, scpd_area); - if (ret) - goto out; - - memcpy(desc, scpd_area->data, sizeof(*desc)); -out: - spin_unlock_irqrestore(&chsc_page_lock, flags); - return ret; +#define chsc_det_chp_desc(FMT, c) \ +int chsc_determine_fmt##FMT##_channel_path_desc( \ + struct chp_id chpid, struct channel_path_desc_fmt##FMT *desc) \ +{ \ + struct chsc_scpd *scpd_area; \ + unsigned long flags; \ + int ret; \ + \ + spin_lock_irqsave(&chsc_page_lock, flags); \ + scpd_area = chsc_page; \ + ret = chsc_determine_channel_path_desc(chpid, 0, FMT, c, 0, \ + scpd_area); \ + if (ret) \ + goto out; \ + \ + memcpy(desc, scpd_area->data, sizeof(*desc)); \ +out: \ + spin_unlock_irqrestore(&chsc_page_lock, flags); \ + return ret; \ } -int chsc_determine_fmt1_channel_path_desc(struct chp_id chpid, - struct channel_path_desc_fmt1 *desc) -{ - struct chsc_scpd *scpd_area; - unsigned long flags; - int ret; - - spin_lock_irqsave(&chsc_page_lock, flags); - scpd_area = chsc_page; - ret = chsc_determine_channel_path_desc(chpid, 0, 1, 1, 0, scpd_area); - if (ret) - goto out; - - memcpy(desc, scpd_area->data, sizeof(*desc)); -out: - spin_unlock_irqrestore(&chsc_page_lock, flags); - return ret; -} +chsc_det_chp_desc(0, 0) +chsc_det_chp_desc(1, 1) static void chsc_initialize_cmg_chars(struct channel_path *chp, u8 cmcv, diff --git a/drivers/s390/cio/chsc.h b/drivers/s390/cio/chsc.h index dda5953534b7..bdf2cc90e5ef 100644 --- a/drivers/s390/cio/chsc.h +++ b/drivers/s390/cio/chsc.h @@ -147,8 +147,8 @@ int __chsc_do_secm(struct channel_subsystem *css, int enable); int chsc_chp_vary(struct chp_id chpid, int on); int chsc_determine_channel_path_desc(struct chp_id chpid, int fmt, int rfmt, int c, int m, void *page); -int chsc_determine_base_channel_path_desc(struct chp_id chpid, - struct channel_path_desc *desc); +int chsc_determine_fmt0_channel_path_desc(struct chp_id chpid, + struct channel_path_desc_fmt0 *desc); int chsc_determine_fmt1_channel_path_desc(struct chp_id chpid, struct channel_path_desc_fmt1 *desc); void chsc_chp_online(struct chp_id chpid); diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index 75ce12a24dc2..aecfebb74157 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -460,8 +460,8 @@ __u8 ccw_device_get_path_mask(struct ccw_device *cdev) * On success return a newly allocated copy of the channel-path description * data associated with the given channel path. Return %NULL on error. */ -struct channel_path_desc *ccw_device_get_chp_desc(struct ccw_device *cdev, - int chp_idx) +struct channel_path_desc_fmt0 *ccw_device_get_chp_desc(struct ccw_device *cdev, + int chp_idx) { struct subchannel *sch; struct chp_id chpid; diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index ca72f3311004..5f10e3aee7ed 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -1361,7 +1361,7 @@ static void qeth_set_multiple_write_queues(struct qeth_card *card) static void qeth_update_from_chp_desc(struct qeth_card *card) { struct ccw_device *ccwdev; - struct channel_path_desc *chp_dsc; + struct channel_path_desc_fmt0 *chp_dsc; QETH_DBF_TEXT(SETUP, 2, "chp_desc"); -- cgit v1.2.1 From fcc6dd4b7b4dfc57ba19f988cfa5ac335de885d5 Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Wed, 22 Jun 2016 19:42:40 +0200 Subject: s390/chsc: query utility strings via fmt3 channel path descriptor Add support for format 3 channel path descriptors and use them to gather utility strings. Signed-off-by: Sebastian Ott Reviewed-by: Peter Oberparleiter Signed-off-by: Martin Schwidefsky --- drivers/s390/cio/chp.c | 1 + drivers/s390/cio/chp.h | 1 + drivers/s390/cio/chsc.c | 3 +++ drivers/s390/cio/chsc.h | 7 +++++++ 4 files changed, 12 insertions(+) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c index 34315e65c1c8..41ecc08b1cf7 100644 --- a/drivers/s390/cio/chp.c +++ b/drivers/s390/cio/chp.c @@ -431,6 +431,7 @@ int chp_update_desc(struct channel_path *chp) * hypervisors implement the required chsc commands. */ chsc_determine_fmt1_channel_path_desc(chp->chpid, &chp->desc_fmt1); + chsc_determine_fmt3_channel_path_desc(chp->chpid, &chp->desc_fmt3); chsc_get_channel_measurement_chars(chp); return 0; diff --git a/drivers/s390/cio/chp.h b/drivers/s390/cio/chp.h index 6d2bfbbead66..20259f3fbf45 100644 --- a/drivers/s390/cio/chp.h +++ b/drivers/s390/cio/chp.h @@ -46,6 +46,7 @@ struct channel_path { int state; struct channel_path_desc_fmt0 desc; struct channel_path_desc_fmt1 desc_fmt1; + struct channel_path_desc_fmt3 desc_fmt3; /* Channel-measurement related stuff: */ int cmg; int shared; diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c index 65290e5ac854..6652a49a49b1 100644 --- a/drivers/s390/cio/chsc.c +++ b/drivers/s390/cio/chsc.c @@ -915,6 +915,8 @@ int chsc_determine_channel_path_desc(struct chp_id chpid, int fmt, int rfmt, return -EINVAL; if ((rfmt == 2) && !css_general_characteristics.cib) return -EINVAL; + if ((rfmt == 3) && !css_general_characteristics.util_str) + return -EINVAL; memset(page, 0, PAGE_SIZE); scpd_area = page; @@ -963,6 +965,7 @@ out: \ chsc_det_chp_desc(0, 0) chsc_det_chp_desc(1, 1) +chsc_det_chp_desc(3, 0) static void chsc_initialize_cmg_chars(struct channel_path *chp, u8 cmcv, diff --git a/drivers/s390/cio/chsc.h b/drivers/s390/cio/chsc.h index bdf2cc90e5ef..5c9f0dd33f4e 100644 --- a/drivers/s390/cio/chsc.h +++ b/drivers/s390/cio/chsc.h @@ -40,6 +40,11 @@ struct channel_path_desc_fmt1 { u32 zeros[2]; } __attribute__ ((packed)); +struct channel_path_desc_fmt3 { + struct channel_path_desc_fmt1 fmt1_desc; + u8 util_str[64]; +}; + struct channel_path; struct css_chsc_char { @@ -151,6 +156,8 @@ int chsc_determine_fmt0_channel_path_desc(struct chp_id chpid, struct channel_path_desc_fmt0 *desc); int chsc_determine_fmt1_channel_path_desc(struct chp_id chpid, struct channel_path_desc_fmt1 *desc); +int chsc_determine_fmt3_channel_path_desc(struct chp_id chpid, + struct channel_path_desc_fmt3 *desc); void chsc_chp_online(struct chp_id chpid); void chsc_chp_offline(struct chp_id chpid); int chsc_get_channel_measurement_chars(struct channel_path *chp); -- cgit v1.2.1 From b9dd652499d645707dda2b835a905455e3718157 Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Wed, 29 Jun 2016 19:32:31 +0200 Subject: s390/cio: add util_string sysfs attribute Export utility strings as a chpid's binary sysfs attribute. Signed-off-by: Sebastian Ott Signed-off-by: Martin Schwidefsky --- drivers/s390/cio/chp.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'drivers/s390') diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c index 41ecc08b1cf7..afbdee74147d 100644 --- a/drivers/s390/cio/chp.c +++ b/drivers/s390/cio/chp.c @@ -384,6 +384,28 @@ static ssize_t chp_chid_external_show(struct device *dev, } static DEVICE_ATTR(chid_external, 0444, chp_chid_external_show, NULL); +static ssize_t util_string_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct channel_path *chp = to_channelpath(kobj_to_dev(kobj)); + ssize_t rc; + + mutex_lock(&chp->lock); + rc = memory_read_from_buffer(buf, count, &off, chp->desc_fmt3.util_str, + sizeof(chp->desc_fmt3.util_str)); + mutex_unlock(&chp->lock); + + return rc; +} +static BIN_ATTR_RO(util_string, + sizeof(((struct channel_path_desc_fmt3 *)0)->util_str)); + +static struct bin_attribute *chp_bin_attrs[] = { + &bin_attr_util_string, + NULL, +}; + static struct attribute *chp_attrs[] = { &dev_attr_status.attr, &dev_attr_configure.attr, @@ -396,6 +418,7 @@ static struct attribute *chp_attrs[] = { }; static struct attribute_group chp_attr_group = { .attrs = chp_attrs, + .bin_attrs = chp_bin_attrs, }; static const struct attribute_group *chp_attr_groups[] = { &chp_attr_group, -- cgit v1.2.1