1/*
2 * Copyright 2011 Tilera Corporation. All Rights Reserved.
3 *
4 *   This program is free software; you can redistribute it and/or
5 *   modify it under the terms of the GNU General Public License
6 *   as published by the Free Software Foundation, version 2.
7 *
8 *   This program is distributed in the hope that it will be useful, but
9 *   WITHOUT ANY WARRANTY; without even the implied warranty of
10 *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
11 *   NON INFRINGEMENT.  See the GNU General Public License for
12 *   more details.
13 *
14 * /sys entry support.
15 */
16
17#include <linux/device.h>
18#include <linux/cpu.h>
19#include <linux/slab.h>
20#include <linux/smp.h>
21#include <linux/stat.h>
22#include <hv/hypervisor.h>
23
24/* Return a string queried from the hypervisor, truncated to page size. */
25static ssize_t get_hv_confstr(char *page, int query)
26{
27	ssize_t n = hv_confstr(query, (unsigned long)page, PAGE_SIZE - 1);
28	n = n < 0 ? 0 : min(n, (ssize_t)PAGE_SIZE - 1) - 1;
29	if (n)
30		page[n++] = '\n';
31	page[n] = '\0';
32	return n;
33}
34
35static ssize_t chip_width_show(struct device *dev,
36			       struct device_attribute *attr,
37			       char *page)
38{
39	return sprintf(page, "%u\n", smp_width);
40}
41static DEVICE_ATTR(chip_width, 0444, chip_width_show, NULL);
42
43static ssize_t chip_height_show(struct device *dev,
44				struct device_attribute *attr,
45				char *page)
46{
47	return sprintf(page, "%u\n", smp_height);
48}
49static DEVICE_ATTR(chip_height, 0444, chip_height_show, NULL);
50
51static ssize_t chip_serial_show(struct device *dev,
52				struct device_attribute *attr,
53				char *page)
54{
55	return get_hv_confstr(page, HV_CONFSTR_CHIP_SERIAL_NUM);
56}
57static DEVICE_ATTR(chip_serial, 0444, chip_serial_show, NULL);
58
59static ssize_t chip_revision_show(struct device *dev,
60				  struct device_attribute *attr,
61				  char *page)
62{
63	return get_hv_confstr(page, HV_CONFSTR_CHIP_REV);
64}
65static DEVICE_ATTR(chip_revision, 0444, chip_revision_show, NULL);
66
67
68static ssize_t type_show(struct device *dev,
69			    struct device_attribute *attr,
70			    char *page)
71{
72	return sprintf(page, "tilera\n");
73}
74static DEVICE_ATTR(type, 0444, type_show, NULL);
75
76#define HV_CONF_ATTR(name, conf)					\
77	static ssize_t name ## _show(struct device *dev,		\
78				     struct device_attribute *attr, \
79				     char *page)			\
80	{								\
81		return get_hv_confstr(page, conf);			\
82	}								\
83	static DEVICE_ATTR(name, 0444, name ## _show, NULL);
84
85HV_CONF_ATTR(version,		HV_CONFSTR_HV_SW_VER)
86HV_CONF_ATTR(config_version,	HV_CONFSTR_HV_CONFIG_VER)
87
88HV_CONF_ATTR(board_part,	HV_CONFSTR_BOARD_PART_NUM)
89HV_CONF_ATTR(board_serial,	HV_CONFSTR_BOARD_SERIAL_NUM)
90HV_CONF_ATTR(board_revision,	HV_CONFSTR_BOARD_REV)
91HV_CONF_ATTR(board_description,	HV_CONFSTR_BOARD_DESC)
92HV_CONF_ATTR(mezz_part,		HV_CONFSTR_MEZZ_PART_NUM)
93HV_CONF_ATTR(mezz_serial,	HV_CONFSTR_MEZZ_SERIAL_NUM)
94HV_CONF_ATTR(mezz_revision,	HV_CONFSTR_MEZZ_REV)
95HV_CONF_ATTR(mezz_description,	HV_CONFSTR_MEZZ_DESC)
96HV_CONF_ATTR(cpumod_part,	HV_CONFSTR_CPUMOD_PART_NUM)
97HV_CONF_ATTR(cpumod_serial,	HV_CONFSTR_CPUMOD_SERIAL_NUM)
98HV_CONF_ATTR(cpumod_revision,	HV_CONFSTR_CPUMOD_REV)
99HV_CONF_ATTR(cpumod_description,HV_CONFSTR_CPUMOD_DESC)
100HV_CONF_ATTR(switch_control,	HV_CONFSTR_SWITCH_CONTROL)
101
102static struct attribute *board_attrs[] = {
103	&dev_attr_board_part.attr,
104	&dev_attr_board_serial.attr,
105	&dev_attr_board_revision.attr,
106	&dev_attr_board_description.attr,
107	&dev_attr_mezz_part.attr,
108	&dev_attr_mezz_serial.attr,
109	&dev_attr_mezz_revision.attr,
110	&dev_attr_mezz_description.attr,
111	&dev_attr_cpumod_part.attr,
112	&dev_attr_cpumod_serial.attr,
113	&dev_attr_cpumod_revision.attr,
114	&dev_attr_cpumod_description.attr,
115	&dev_attr_switch_control.attr,
116	NULL
117};
118
119static struct attribute_group board_attr_group = {
120	.name   = "board",
121	.attrs  = board_attrs,
122};
123
124
125static struct bin_attribute hvconfig_bin;
126
127static ssize_t
128hvconfig_bin_read(struct file *filp, struct kobject *kobj,
129		  struct bin_attribute *bin_attr,
130		  char *buf, loff_t off, size_t count)
131{
132	static size_t size;
133
134	/* Lazily learn the true size (minus the trailing NUL). */
135	if (size == 0)
136		size = hv_confstr(HV_CONFSTR_HV_CONFIG, 0, 0) - 1;
137
138	/* Check and adjust input parameters. */
139	if (off > size)
140		return -EINVAL;
141	if (count > size - off)
142		count = size - off;
143
144	if (count) {
145		/* Get a copy of the hvc and copy out the relevant portion. */
146		char *hvc;
147
148		size = off + count;
149		hvc = kmalloc(size, GFP_KERNEL);
150		if (hvc == NULL)
151			return -ENOMEM;
152		hv_confstr(HV_CONFSTR_HV_CONFIG, (unsigned long)hvc, size);
153		memcpy(buf, hvc + off, count);
154		kfree(hvc);
155	}
156
157	return count;
158}
159
160static ssize_t hv_stats_show(struct device *dev,
161			     struct device_attribute *attr,
162			     char *page)
163{
164	int cpu = dev->id;
165	long lotar = HV_XY_TO_LOTAR(cpu_x(cpu), cpu_y(cpu));
166
167	ssize_t n = hv_confstr(HV_CONFSTR_HV_STATS,
168			       (unsigned long)page, PAGE_SIZE - 1,
169			       lotar, 0);
170	n = n < 0 ? 0 : min(n, (ssize_t)PAGE_SIZE - 1);
171	page[n] = '\0';
172	return n;
173}
174
175static ssize_t hv_stats_store(struct device *dev,
176			      struct device_attribute *attr,
177			      const char *page,
178			      size_t count)
179{
180	int cpu = dev->id;
181	long lotar = HV_XY_TO_LOTAR(cpu_x(cpu), cpu_y(cpu));
182
183	ssize_t n = hv_confstr(HV_CONFSTR_HV_STATS, 0, 0, lotar, 1);
184	return n < 0 ? n : count;
185}
186
187static DEVICE_ATTR(hv_stats, 0644, hv_stats_show, hv_stats_store);
188
189static int hv_stats_device_add(struct device *dev, struct subsys_interface *sif)
190{
191	int err, cpu = dev->id;
192
193	if (!cpu_online(cpu))
194		return 0;
195
196	err = sysfs_create_file(&dev->kobj, &dev_attr_hv_stats.attr);
197
198	return err;
199}
200
201static void hv_stats_device_remove(struct device *dev,
202				   struct subsys_interface *sif)
203{
204	int cpu = dev->id;
205
206	if (cpu_online(cpu))
207		sysfs_remove_file(&dev->kobj, &dev_attr_hv_stats.attr);
208}
209
210
211static struct subsys_interface hv_stats_interface = {
212	.name			= "hv_stats",
213	.subsys			= &cpu_subsys,
214	.add_dev		= hv_stats_device_add,
215	.remove_dev		= hv_stats_device_remove,
216};
217
218static int __init create_sysfs_entries(void)
219{
220	int err = 0;
221
222#define create_cpu_attr(name)						\
223	if (!err)							\
224		err = device_create_file(cpu_subsys.dev_root, &dev_attr_##name);
225	create_cpu_attr(chip_width);
226	create_cpu_attr(chip_height);
227	create_cpu_attr(chip_serial);
228	create_cpu_attr(chip_revision);
229
230#define create_hv_attr(name)						\
231	if (!err)							\
232		err = sysfs_create_file(hypervisor_kobj, &dev_attr_##name.attr);
233	create_hv_attr(type);
234	create_hv_attr(version);
235	create_hv_attr(config_version);
236
237	if (!err)
238		err = sysfs_create_group(hypervisor_kobj, &board_attr_group);
239
240	if (!err) {
241		sysfs_bin_attr_init(&hvconfig_bin);
242		hvconfig_bin.attr.name = "hvconfig";
243		hvconfig_bin.attr.mode = S_IRUGO;
244		hvconfig_bin.read = hvconfig_bin_read;
245		hvconfig_bin.size = PAGE_SIZE;
246		err = sysfs_create_bin_file(hypervisor_kobj, &hvconfig_bin);
247	}
248
249	if (!err) {
250		/*
251		 * Don't bother adding the hv_stats files on each CPU if
252		 * our hypervisor doesn't supply statistics.
253		 */
254		int cpu = raw_smp_processor_id();
255		long lotar = HV_XY_TO_LOTAR(cpu_x(cpu), cpu_y(cpu));
256		char dummy;
257		ssize_t n = hv_confstr(HV_CONFSTR_HV_STATS,
258				       (unsigned long) &dummy, 1,
259				       lotar, 0);
260		if (n >= 0)
261			err = subsys_interface_register(&hv_stats_interface);
262	}
263
264	return err;
265}
266subsys_initcall(create_sysfs_entries);
267