1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com> |
4 | * Horst Hummel <Horst.Hummel@de.ibm.com> |
5 | * Carsten Otte <Cotte@de.ibm.com> |
6 | * Martin Schwidefsky <schwidefsky@de.ibm.com> |
7 | * Bugreports.to..: <Linux390@de.ibm.com> |
8 | * Copyright IBM Corp. 1999, 2002 |
9 | * |
10 | * /proc interface for the dasd driver. |
11 | * |
12 | */ |
13 | |
14 | #define KMSG_COMPONENT "dasd" |
15 | |
16 | #include <linux/ctype.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/string.h> |
19 | #include <linux/seq_file.h> |
20 | #include <linux/vmalloc.h> |
21 | #include <linux/proc_fs.h> |
22 | |
23 | #include <asm/debug.h> |
24 | #include <linux/uaccess.h> |
25 | |
26 | /* This is ugly... */ |
27 | #define "dasd_proc:" |
28 | |
29 | #include "dasd_int.h" |
30 | |
31 | static struct proc_dir_entry *dasd_proc_root_entry = NULL; |
32 | static struct proc_dir_entry *dasd_devices_entry = NULL; |
33 | static struct proc_dir_entry *dasd_statistics_entry = NULL; |
34 | |
35 | static int |
36 | dasd_devices_show(struct seq_file *m, void *v) |
37 | { |
38 | struct dasd_device *device; |
39 | struct dasd_block *block; |
40 | char *substr; |
41 | |
42 | device = dasd_device_from_devindex((unsigned long) v - 1); |
43 | if (IS_ERR(ptr: device)) |
44 | return 0; |
45 | if (device->block) |
46 | block = device->block; |
47 | else { |
48 | dasd_put_device(device); |
49 | return 0; |
50 | } |
51 | /* Print device number. */ |
52 | seq_printf(m, fmt: "%s" , dev_name(dev: &device->cdev->dev)); |
53 | /* Print discipline string. */ |
54 | if (device->discipline != NULL) |
55 | seq_printf(m, fmt: "(%s)" , device->discipline->name); |
56 | else |
57 | seq_printf(m, fmt: "(none)" ); |
58 | /* Print kdev. */ |
59 | if (block->gdp) |
60 | seq_printf(m, fmt: " at (%3d:%6d)" , |
61 | MAJOR(disk_devt(block->gdp)), |
62 | MINOR(disk_devt(block->gdp))); |
63 | else |
64 | seq_printf(m, fmt: " at (???:??????)" ); |
65 | /* Print device name. */ |
66 | if (block->gdp) |
67 | seq_printf(m, fmt: " is %-8s" , block->gdp->disk_name); |
68 | else |
69 | seq_printf(m, fmt: " is ????????" ); |
70 | /* Print devices features. */ |
71 | substr = (device->features & DASD_FEATURE_READONLY) ? "(ro)" : " " ; |
72 | seq_printf(m, fmt: "%4s: " , substr); |
73 | /* Print device status information. */ |
74 | switch (device->state) { |
75 | case DASD_STATE_NEW: |
76 | seq_printf(m, fmt: "new" ); |
77 | break; |
78 | case DASD_STATE_KNOWN: |
79 | seq_printf(m, fmt: "detected" ); |
80 | break; |
81 | case DASD_STATE_BASIC: |
82 | seq_printf(m, fmt: "basic" ); |
83 | break; |
84 | case DASD_STATE_UNFMT: |
85 | seq_printf(m, fmt: "unformatted" ); |
86 | break; |
87 | case DASD_STATE_READY: |
88 | case DASD_STATE_ONLINE: |
89 | seq_printf(m, fmt: "active " ); |
90 | if (dasd_check_blocksize(bsize: block->bp_block)) |
91 | seq_printf(m, fmt: "n/f " ); |
92 | else |
93 | seq_printf(m, |
94 | fmt: "at blocksize: %u, %lu blocks, %lu MB" , |
95 | block->bp_block, block->blocks, |
96 | ((block->bp_block >> 9) * |
97 | block->blocks) >> 11); |
98 | break; |
99 | default: |
100 | seq_printf(m, fmt: "no stat" ); |
101 | break; |
102 | } |
103 | dasd_put_device(device); |
104 | if (dasd_probeonly) |
105 | seq_printf(m, fmt: "(probeonly)" ); |
106 | seq_printf(m, fmt: "\n" ); |
107 | return 0; |
108 | } |
109 | |
110 | static void *dasd_devices_start(struct seq_file *m, loff_t *pos) |
111 | { |
112 | if (*pos >= dasd_max_devindex) |
113 | return NULL; |
114 | return (void *)((unsigned long) *pos + 1); |
115 | } |
116 | |
117 | static void *dasd_devices_next(struct seq_file *m, void *v, loff_t *pos) |
118 | { |
119 | ++*pos; |
120 | return dasd_devices_start(m, pos); |
121 | } |
122 | |
123 | static void dasd_devices_stop(struct seq_file *m, void *v) |
124 | { |
125 | } |
126 | |
127 | static const struct seq_operations dasd_devices_seq_ops = { |
128 | .start = dasd_devices_start, |
129 | .next = dasd_devices_next, |
130 | .stop = dasd_devices_stop, |
131 | .show = dasd_devices_show, |
132 | }; |
133 | |
134 | #ifdef CONFIG_DASD_PROFILE |
135 | static int dasd_stats_all_block_on(void) |
136 | { |
137 | int i, rc; |
138 | struct dasd_device *device; |
139 | |
140 | rc = 0; |
141 | for (i = 0; i < dasd_max_devindex; ++i) { |
142 | device = dasd_device_from_devindex(i); |
143 | if (IS_ERR(device)) |
144 | continue; |
145 | if (device->block) |
146 | rc = dasd_profile_on(&device->block->profile); |
147 | dasd_put_device(device); |
148 | if (rc) |
149 | return rc; |
150 | } |
151 | return 0; |
152 | } |
153 | |
154 | static void dasd_stats_all_block_off(void) |
155 | { |
156 | int i; |
157 | struct dasd_device *device; |
158 | |
159 | for (i = 0; i < dasd_max_devindex; ++i) { |
160 | device = dasd_device_from_devindex(i); |
161 | if (IS_ERR(device)) |
162 | continue; |
163 | if (device->block) |
164 | dasd_profile_off(&device->block->profile); |
165 | dasd_put_device(device); |
166 | } |
167 | } |
168 | |
169 | static void dasd_stats_all_block_reset(void) |
170 | { |
171 | int i; |
172 | struct dasd_device *device; |
173 | |
174 | for (i = 0; i < dasd_max_devindex; ++i) { |
175 | device = dasd_device_from_devindex(i); |
176 | if (IS_ERR(device)) |
177 | continue; |
178 | if (device->block) |
179 | dasd_profile_reset(&device->block->profile); |
180 | dasd_put_device(device); |
181 | } |
182 | } |
183 | |
184 | static void dasd_statistics_array(struct seq_file *m, unsigned int *array, int factor) |
185 | { |
186 | int i; |
187 | |
188 | for (i = 0; i < 32; i++) { |
189 | seq_printf(m, "%7d " , array[i] / factor); |
190 | if (i == 15) |
191 | seq_putc(m, '\n'); |
192 | } |
193 | seq_putc(m, '\n'); |
194 | } |
195 | #endif /* CONFIG_DASD_PROFILE */ |
196 | |
197 | static int dasd_stats_proc_show(struct seq_file *m, void *v) |
198 | { |
199 | #ifdef CONFIG_DASD_PROFILE |
200 | struct dasd_profile_info *prof; |
201 | int factor; |
202 | |
203 | spin_lock_bh(&dasd_global_profile.lock); |
204 | prof = dasd_global_profile.data; |
205 | if (!prof) { |
206 | spin_unlock_bh(&dasd_global_profile.lock); |
207 | seq_printf(m, "Statistics are off - they might be " |
208 | "switched on using 'echo set on > " |
209 | "/proc/dasd/statistics'\n" ); |
210 | return 0; |
211 | } |
212 | |
213 | /* prevent counter 'overflow' on output */ |
214 | for (factor = 1; (prof->dasd_io_reqs / factor) > 9999999; |
215 | factor *= 10); |
216 | |
217 | seq_printf(m, "%d dasd I/O requests\n" , prof->dasd_io_reqs); |
218 | seq_printf(m, "with %u sectors(512B each)\n" , |
219 | prof->dasd_io_sects); |
220 | seq_printf(m, "Scale Factor is %d\n" , factor); |
221 | seq_printf(m, |
222 | " __<4 ___8 __16 __32 __64 _128 " |
223 | " _256 _512 __1k __2k __4k __8k " |
224 | " _16k _32k _64k 128k\n" ); |
225 | seq_printf(m, |
226 | " _256 _512 __1M __2M __4M __8M " |
227 | " _16M _32M _64M 128M 256M 512M " |
228 | " __1G __2G __4G " " _>4G\n" ); |
229 | |
230 | seq_printf(m, "Histogram of sizes (512B secs)\n" ); |
231 | dasd_statistics_array(m, prof->dasd_io_secs, factor); |
232 | seq_printf(m, "Histogram of I/O times (microseconds)\n" ); |
233 | dasd_statistics_array(m, prof->dasd_io_times, factor); |
234 | seq_printf(m, "Histogram of I/O times per sector\n" ); |
235 | dasd_statistics_array(m, prof->dasd_io_timps, factor); |
236 | seq_printf(m, "Histogram of I/O time till ssch\n" ); |
237 | dasd_statistics_array(m, prof->dasd_io_time1, factor); |
238 | seq_printf(m, "Histogram of I/O time between ssch and irq\n" ); |
239 | dasd_statistics_array(m, prof->dasd_io_time2, factor); |
240 | seq_printf(m, "Histogram of I/O time between ssch " |
241 | "and irq per sector\n" ); |
242 | dasd_statistics_array(m, prof->dasd_io_time2ps, factor); |
243 | seq_printf(m, "Histogram of I/O time between irq and end\n" ); |
244 | dasd_statistics_array(m, prof->dasd_io_time3, factor); |
245 | seq_printf(m, "# of req in chanq at enqueuing (1..32) \n" ); |
246 | dasd_statistics_array(m, prof->dasd_io_nr_req, factor); |
247 | spin_unlock_bh(&dasd_global_profile.lock); |
248 | #else |
249 | seq_printf(m, fmt: "Statistics are not activated in this kernel\n" ); |
250 | #endif |
251 | return 0; |
252 | } |
253 | |
254 | static int dasd_stats_proc_open(struct inode *inode, struct file *file) |
255 | { |
256 | return single_open(file, dasd_stats_proc_show, NULL); |
257 | } |
258 | |
259 | static ssize_t dasd_stats_proc_write(struct file *file, |
260 | const char __user *user_buf, size_t user_len, loff_t *pos) |
261 | { |
262 | #ifdef CONFIG_DASD_PROFILE |
263 | char *buffer, *str; |
264 | int rc; |
265 | |
266 | if (user_len > 65536) |
267 | user_len = 65536; |
268 | buffer = dasd_get_user_string(user_buf, user_len); |
269 | if (IS_ERR(buffer)) |
270 | return PTR_ERR(buffer); |
271 | |
272 | /* check for valid verbs */ |
273 | str = skip_spaces(buffer); |
274 | if (strncmp(str, "set" , 3) == 0 && isspace(str[3])) { |
275 | /* 'set xxx' was given */ |
276 | str = skip_spaces(str + 4); |
277 | if (strcmp(str, "on" ) == 0) { |
278 | /* switch on statistics profiling */ |
279 | rc = dasd_stats_all_block_on(); |
280 | if (rc) { |
281 | dasd_stats_all_block_off(); |
282 | goto out_error; |
283 | } |
284 | rc = dasd_profile_on(&dasd_global_profile); |
285 | if (rc) { |
286 | dasd_stats_all_block_off(); |
287 | goto out_error; |
288 | } |
289 | dasd_profile_reset(&dasd_global_profile); |
290 | dasd_global_profile_level = DASD_PROFILE_ON; |
291 | pr_info("The statistics feature has been switched " |
292 | "on\n" ); |
293 | } else if (strcmp(str, "off" ) == 0) { |
294 | /* switch off statistics profiling */ |
295 | dasd_global_profile_level = DASD_PROFILE_OFF; |
296 | dasd_profile_off(&dasd_global_profile); |
297 | dasd_stats_all_block_off(); |
298 | pr_info("The statistics feature has been switched " |
299 | "off\n" ); |
300 | } else |
301 | goto out_parse_error; |
302 | } else if (strncmp(str, "reset" , 5) == 0) { |
303 | /* reset the statistics */ |
304 | dasd_profile_reset(&dasd_global_profile); |
305 | dasd_stats_all_block_reset(); |
306 | pr_info("The statistics have been reset\n" ); |
307 | } else |
308 | goto out_parse_error; |
309 | vfree(buffer); |
310 | return user_len; |
311 | out_parse_error: |
312 | rc = -EINVAL; |
313 | pr_warn("%s is not a supported value for /proc/dasd/statistics\n" , str); |
314 | out_error: |
315 | vfree(buffer); |
316 | return rc; |
317 | #else |
318 | pr_warn("/proc/dasd/statistics: is not activated in this kernel\n" ); |
319 | return user_len; |
320 | #endif /* CONFIG_DASD_PROFILE */ |
321 | } |
322 | |
323 | static const struct proc_ops dasd_stats_proc_ops = { |
324 | .proc_open = dasd_stats_proc_open, |
325 | .proc_read = seq_read, |
326 | .proc_lseek = seq_lseek, |
327 | .proc_release = single_release, |
328 | .proc_write = dasd_stats_proc_write, |
329 | }; |
330 | |
331 | /* |
332 | * Create dasd proc-fs entries. |
333 | * In case creation failed, cleanup and return -ENOENT. |
334 | */ |
335 | int |
336 | dasd_proc_init(void) |
337 | { |
338 | dasd_proc_root_entry = proc_mkdir("dasd" , NULL); |
339 | if (!dasd_proc_root_entry) |
340 | goto out_nodasd; |
341 | dasd_devices_entry = proc_create_seq("devices" , 0444, |
342 | dasd_proc_root_entry, |
343 | &dasd_devices_seq_ops); |
344 | if (!dasd_devices_entry) |
345 | goto out_nodevices; |
346 | dasd_statistics_entry = proc_create(name: "statistics" , |
347 | S_IFREG | S_IRUGO | S_IWUSR, |
348 | parent: dasd_proc_root_entry, |
349 | proc_ops: &dasd_stats_proc_ops); |
350 | if (!dasd_statistics_entry) |
351 | goto out_nostatistics; |
352 | return 0; |
353 | |
354 | out_nostatistics: |
355 | remove_proc_entry("devices" , dasd_proc_root_entry); |
356 | out_nodevices: |
357 | remove_proc_entry("dasd" , NULL); |
358 | out_nodasd: |
359 | return -ENOENT; |
360 | } |
361 | |
362 | void |
363 | dasd_proc_exit(void) |
364 | { |
365 | remove_proc_entry("devices" , dasd_proc_root_entry); |
366 | remove_proc_entry("statistics" , dasd_proc_root_entry); |
367 | remove_proc_entry("dasd" , NULL); |
368 | } |
369 | |