summaryrefslogtreecommitdiffstats
path: root/drivers/s390/char/sclp_config.c
blob: 039b2074db7e5d39a88aeed516630e62dfc05e4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// SPDX-License-Identifier: GPL-2.0
/*
 *    Copyright IBM Corp. 2007
 *    Author(s): Heiko Carstens <heiko.carstens@de.ibm.com>
 */

#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");
	get_online_cpus();
	for_each_online_cpu(cpu) {
		dev = get_cpu_device(cpu);
		kobject_uevent(&dev->kobj, KOBJ_CHANGE);
	}
	put_online_cpus();
}

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