diff options
Diffstat (limited to 'drivers/s390/char/sclp_config.c')
-rw-r--r-- | drivers/s390/char/sclp_config.c | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/drivers/s390/char/sclp_config.c b/drivers/s390/char/sclp_config.c new file mode 100644 index 000000000..10383e936 --- /dev/null +++ b/drivers/s390/char/sclp_config.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright IBM Corp. 2007 + */ + +#define KMSG_COMPONENT "sclp_config" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/cpu.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <asm/smp.h> + +#include "sclp.h" + +struct conf_mgm_data { + u8 reserved; + u8 ev_qualifier; +} __attribute__((packed)); + +#define OFB_DATA_MAX 64 + +struct sclp_ofb_evbuf { + struct evbuf_header header; + struct conf_mgm_data cm_data; + char ev_data[OFB_DATA_MAX]; +} __packed; + +struct sclp_ofb_sccb { + struct sccb_header header; + struct sclp_ofb_evbuf ofb_evbuf; +} __packed; + +#define EV_QUAL_CPU_CHANGE 1 +#define EV_QUAL_CAP_CHANGE 3 +#define EV_QUAL_OPEN4BUSINESS 5 + +static struct work_struct sclp_cpu_capability_work; +static struct work_struct sclp_cpu_change_work; + +static void sclp_cpu_capability_notify(struct work_struct *work) +{ + int cpu; + struct device *dev; + + s390_update_cpu_mhz(); + pr_info("CPU capability may have changed\n"); + cpus_read_lock(); + for_each_online_cpu(cpu) { + dev = get_cpu_device(cpu); + kobject_uevent(&dev->kobj, KOBJ_CHANGE); + } + cpus_read_unlock(); +} + +static void __ref sclp_cpu_change_notify(struct work_struct *work) +{ + lock_device_hotplug(); + smp_rescan_cpus(); + unlock_device_hotplug(); +} + +static void sclp_conf_receiver_fn(struct evbuf_header *evbuf) +{ + struct conf_mgm_data *cdata; + + cdata = (struct conf_mgm_data *)(evbuf + 1); + switch (cdata->ev_qualifier) { + case EV_QUAL_CPU_CHANGE: + schedule_work(&sclp_cpu_change_work); + break; + case EV_QUAL_CAP_CHANGE: + schedule_work(&sclp_cpu_capability_work); + break; + } +} + +static struct sclp_register sclp_conf_register = +{ +#ifdef CONFIG_SCLP_OFB + .send_mask = EVTYP_CONFMGMDATA_MASK, +#endif + .receive_mask = EVTYP_CONFMGMDATA_MASK, + .receiver_fn = sclp_conf_receiver_fn, +}; + +#ifdef CONFIG_SCLP_OFB +static int sclp_ofb_send_req(char *ev_data, size_t len) +{ + static DEFINE_MUTEX(send_mutex); + struct sclp_ofb_sccb *sccb; + int rc, response; + + if (len > OFB_DATA_MAX) + return -EINVAL; + sccb = (struct sclp_ofb_sccb *) get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!sccb) + return -ENOMEM; + /* Setup SCCB for Control-Program Identification */ + sccb->header.length = sizeof(struct sclp_ofb_sccb); + sccb->ofb_evbuf.header.length = sizeof(struct sclp_ofb_evbuf); + sccb->ofb_evbuf.header.type = EVTYP_CONFMGMDATA; + sccb->ofb_evbuf.cm_data.ev_qualifier = EV_QUAL_OPEN4BUSINESS; + memcpy(sccb->ofb_evbuf.ev_data, ev_data, len); + + if (!(sclp_conf_register.sclp_receive_mask & EVTYP_CONFMGMDATA_MASK)) + pr_warn("SCLP receiver did not register to receive " + "Configuration Management Data Events.\n"); + + mutex_lock(&send_mutex); + rc = sclp_sync_request(SCLP_CMDW_WRITE_EVENT_DATA, sccb); + mutex_unlock(&send_mutex); + if (rc) + goto out; + response = sccb->header.response_code; + if (response != 0x0020) { + pr_err("Open for Business request failed with response code " + "0x%04x\n", response); + rc = -EIO; + } +out: + free_page((unsigned long)sccb); + return rc; +} + +static ssize_t sysfs_ofb_data_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + int rc; + + rc = sclp_ofb_send_req(buf, count); + return rc ?: count; +} + +static const struct bin_attribute ofb_bin_attr = { + .attr = { + .name = "event_data", + .mode = S_IWUSR, + }, + .write = sysfs_ofb_data_write, +}; +#endif + +static int __init sclp_ofb_setup(void) +{ +#ifdef CONFIG_SCLP_OFB + struct kset *ofb_kset; + int rc; + + ofb_kset = kset_create_and_add("ofb", NULL, firmware_kobj); + if (!ofb_kset) + return -ENOMEM; + rc = sysfs_create_bin_file(&ofb_kset->kobj, &ofb_bin_attr); + if (rc) { + kset_unregister(ofb_kset); + return rc; + } +#endif + return 0; +} + +static int __init sclp_conf_init(void) +{ + int rc; + + INIT_WORK(&sclp_cpu_capability_work, sclp_cpu_capability_notify); + INIT_WORK(&sclp_cpu_change_work, sclp_cpu_change_notify); + rc = sclp_register(&sclp_conf_register); + if (rc) + return rc; + return sclp_ofb_setup(); +} + +__initcall(sclp_conf_init); |