| // 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); |