1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Hypervisor filesystem for Linux on s390. Diag 204 and 224 |
4 | * implementation. |
5 | * |
6 | * Copyright IBM Corp. 2006, 2008 |
7 | * Author(s): Michael Holzheu <holzheu@de.ibm.com> |
8 | */ |
9 | |
10 | #define KMSG_COMPONENT "hypfs" |
11 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
12 | |
13 | #include <linux/types.h> |
14 | #include <linux/errno.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/string.h> |
17 | #include <linux/vmalloc.h> |
18 | #include <linux/mm.h> |
19 | #include <asm/diag.h> |
20 | #include <asm/ebcdic.h> |
21 | #include "hypfs_diag.h" |
22 | #include "hypfs.h" |
23 | |
24 | #define TMP_SIZE 64 /* size of temporary buffers */ |
25 | |
26 | static char *diag224_cpu_names; /* diag 224 name table */ |
27 | static int diag224_idx2name(int index, char *name); |
28 | |
29 | /* |
30 | * DIAG 204 member access functions. |
31 | * |
32 | * Since we have two different diag 204 data formats for old and new s390 |
33 | * machines, we do not access the structs directly, but use getter functions for |
34 | * each struct member instead. This should make the code more readable. |
35 | */ |
36 | |
37 | /* Time information block */ |
38 | |
39 | static inline int info_blk_hdr__size(enum diag204_format type) |
40 | { |
41 | if (type == DIAG204_INFO_SIMPLE) |
42 | return sizeof(struct diag204_info_blk_hdr); |
43 | else /* DIAG204_INFO_EXT */ |
44 | return sizeof(struct diag204_x_info_blk_hdr); |
45 | } |
46 | |
47 | static inline __u8 info_blk_hdr__npar(enum diag204_format type, void *hdr) |
48 | { |
49 | if (type == DIAG204_INFO_SIMPLE) |
50 | return ((struct diag204_info_blk_hdr *)hdr)->npar; |
51 | else /* DIAG204_INFO_EXT */ |
52 | return ((struct diag204_x_info_blk_hdr *)hdr)->npar; |
53 | } |
54 | |
55 | static inline __u8 info_blk_hdr__flags(enum diag204_format type, void *hdr) |
56 | { |
57 | if (type == DIAG204_INFO_SIMPLE) |
58 | return ((struct diag204_info_blk_hdr *)hdr)->flags; |
59 | else /* DIAG204_INFO_EXT */ |
60 | return ((struct diag204_x_info_blk_hdr *)hdr)->flags; |
61 | } |
62 | |
63 | /* Partition header */ |
64 | |
65 | static inline int part_hdr__size(enum diag204_format type) |
66 | { |
67 | if (type == DIAG204_INFO_SIMPLE) |
68 | return sizeof(struct diag204_part_hdr); |
69 | else /* DIAG204_INFO_EXT */ |
70 | return sizeof(struct diag204_x_part_hdr); |
71 | } |
72 | |
73 | static inline __u8 part_hdr__rcpus(enum diag204_format type, void *hdr) |
74 | { |
75 | if (type == DIAG204_INFO_SIMPLE) |
76 | return ((struct diag204_part_hdr *)hdr)->cpus; |
77 | else /* DIAG204_INFO_EXT */ |
78 | return ((struct diag204_x_part_hdr *)hdr)->rcpus; |
79 | } |
80 | |
81 | static inline void part_hdr__part_name(enum diag204_format type, void *hdr, |
82 | char *name) |
83 | { |
84 | if (type == DIAG204_INFO_SIMPLE) |
85 | memcpy(name, ((struct diag204_part_hdr *)hdr)->part_name, |
86 | DIAG204_LPAR_NAME_LEN); |
87 | else /* DIAG204_INFO_EXT */ |
88 | memcpy(name, ((struct diag204_x_part_hdr *)hdr)->part_name, |
89 | DIAG204_LPAR_NAME_LEN); |
90 | EBCASC(name, DIAG204_LPAR_NAME_LEN); |
91 | name[DIAG204_LPAR_NAME_LEN] = 0; |
92 | strim(name); |
93 | } |
94 | |
95 | /* CPU info block */ |
96 | |
97 | static inline int cpu_info__size(enum diag204_format type) |
98 | { |
99 | if (type == DIAG204_INFO_SIMPLE) |
100 | return sizeof(struct diag204_cpu_info); |
101 | else /* DIAG204_INFO_EXT */ |
102 | return sizeof(struct diag204_x_cpu_info); |
103 | } |
104 | |
105 | static inline __u8 cpu_info__ctidx(enum diag204_format type, void *hdr) |
106 | { |
107 | if (type == DIAG204_INFO_SIMPLE) |
108 | return ((struct diag204_cpu_info *)hdr)->ctidx; |
109 | else /* DIAG204_INFO_EXT */ |
110 | return ((struct diag204_x_cpu_info *)hdr)->ctidx; |
111 | } |
112 | |
113 | static inline __u16 cpu_info__cpu_addr(enum diag204_format type, void *hdr) |
114 | { |
115 | if (type == DIAG204_INFO_SIMPLE) |
116 | return ((struct diag204_cpu_info *)hdr)->cpu_addr; |
117 | else /* DIAG204_INFO_EXT */ |
118 | return ((struct diag204_x_cpu_info *)hdr)->cpu_addr; |
119 | } |
120 | |
121 | static inline __u64 cpu_info__acc_time(enum diag204_format type, void *hdr) |
122 | { |
123 | if (type == DIAG204_INFO_SIMPLE) |
124 | return ((struct diag204_cpu_info *)hdr)->acc_time; |
125 | else /* DIAG204_INFO_EXT */ |
126 | return ((struct diag204_x_cpu_info *)hdr)->acc_time; |
127 | } |
128 | |
129 | static inline __u64 cpu_info__lp_time(enum diag204_format type, void *hdr) |
130 | { |
131 | if (type == DIAG204_INFO_SIMPLE) |
132 | return ((struct diag204_cpu_info *)hdr)->lp_time; |
133 | else /* DIAG204_INFO_EXT */ |
134 | return ((struct diag204_x_cpu_info *)hdr)->lp_time; |
135 | } |
136 | |
137 | static inline __u64 cpu_info__online_time(enum diag204_format type, void *hdr) |
138 | { |
139 | if (type == DIAG204_INFO_SIMPLE) |
140 | return 0; /* online_time not available in simple info */ |
141 | else /* DIAG204_INFO_EXT */ |
142 | return ((struct diag204_x_cpu_info *)hdr)->online_time; |
143 | } |
144 | |
145 | /* Physical header */ |
146 | |
147 | static inline int phys_hdr__size(enum diag204_format type) |
148 | { |
149 | if (type == DIAG204_INFO_SIMPLE) |
150 | return sizeof(struct diag204_phys_hdr); |
151 | else /* DIAG204_INFO_EXT */ |
152 | return sizeof(struct diag204_x_phys_hdr); |
153 | } |
154 | |
155 | static inline __u8 phys_hdr__cpus(enum diag204_format type, void *hdr) |
156 | { |
157 | if (type == DIAG204_INFO_SIMPLE) |
158 | return ((struct diag204_phys_hdr *)hdr)->cpus; |
159 | else /* DIAG204_INFO_EXT */ |
160 | return ((struct diag204_x_phys_hdr *)hdr)->cpus; |
161 | } |
162 | |
163 | /* Physical CPU info block */ |
164 | |
165 | static inline int phys_cpu__size(enum diag204_format type) |
166 | { |
167 | if (type == DIAG204_INFO_SIMPLE) |
168 | return sizeof(struct diag204_phys_cpu); |
169 | else /* DIAG204_INFO_EXT */ |
170 | return sizeof(struct diag204_x_phys_cpu); |
171 | } |
172 | |
173 | static inline __u16 phys_cpu__cpu_addr(enum diag204_format type, void *hdr) |
174 | { |
175 | if (type == DIAG204_INFO_SIMPLE) |
176 | return ((struct diag204_phys_cpu *)hdr)->cpu_addr; |
177 | else /* DIAG204_INFO_EXT */ |
178 | return ((struct diag204_x_phys_cpu *)hdr)->cpu_addr; |
179 | } |
180 | |
181 | static inline __u64 phys_cpu__mgm_time(enum diag204_format type, void *hdr) |
182 | { |
183 | if (type == DIAG204_INFO_SIMPLE) |
184 | return ((struct diag204_phys_cpu *)hdr)->mgm_time; |
185 | else /* DIAG204_INFO_EXT */ |
186 | return ((struct diag204_x_phys_cpu *)hdr)->mgm_time; |
187 | } |
188 | |
189 | static inline __u64 phys_cpu__ctidx(enum diag204_format type, void *hdr) |
190 | { |
191 | if (type == DIAG204_INFO_SIMPLE) |
192 | return ((struct diag204_phys_cpu *)hdr)->ctidx; |
193 | else /* DIAG204_INFO_EXT */ |
194 | return ((struct diag204_x_phys_cpu *)hdr)->ctidx; |
195 | } |
196 | |
197 | /* |
198 | * Functions to create the directory structure |
199 | * ******************************************* |
200 | */ |
201 | |
202 | static int hypfs_create_cpu_files(struct dentry *cpus_dir, void *cpu_info) |
203 | { |
204 | struct dentry *cpu_dir; |
205 | char buffer[TMP_SIZE]; |
206 | void *rc; |
207 | |
208 | snprintf(buf: buffer, TMP_SIZE, fmt: "%d" , cpu_info__cpu_addr(type: diag204_get_info_type(), |
209 | hdr: cpu_info)); |
210 | cpu_dir = hypfs_mkdir(parent: cpus_dir, name: buffer); |
211 | rc = hypfs_create_u64(dir: cpu_dir, name: "mgmtime" , |
212 | value: cpu_info__acc_time(type: diag204_get_info_type(), hdr: cpu_info) - |
213 | cpu_info__lp_time(type: diag204_get_info_type(), hdr: cpu_info)); |
214 | if (IS_ERR(ptr: rc)) |
215 | return PTR_ERR(ptr: rc); |
216 | rc = hypfs_create_u64(dir: cpu_dir, name: "cputime" , |
217 | value: cpu_info__lp_time(type: diag204_get_info_type(), hdr: cpu_info)); |
218 | if (IS_ERR(ptr: rc)) |
219 | return PTR_ERR(ptr: rc); |
220 | if (diag204_get_info_type() == DIAG204_INFO_EXT) { |
221 | rc = hypfs_create_u64(dir: cpu_dir, name: "onlinetime" , |
222 | value: cpu_info__online_time(type: diag204_get_info_type(), |
223 | hdr: cpu_info)); |
224 | if (IS_ERR(ptr: rc)) |
225 | return PTR_ERR(ptr: rc); |
226 | } |
227 | diag224_idx2name(index: cpu_info__ctidx(type: diag204_get_info_type(), hdr: cpu_info), name: buffer); |
228 | rc = hypfs_create_str(dir: cpu_dir, name: "type" , string: buffer); |
229 | return PTR_ERR_OR_ZERO(ptr: rc); |
230 | } |
231 | |
232 | static void *hypfs_create_lpar_files(struct dentry *systems_dir, void *part_hdr) |
233 | { |
234 | struct dentry *cpus_dir; |
235 | struct dentry *lpar_dir; |
236 | char lpar_name[DIAG204_LPAR_NAME_LEN + 1]; |
237 | void *cpu_info; |
238 | int i; |
239 | |
240 | part_hdr__part_name(type: diag204_get_info_type(), hdr: part_hdr, name: lpar_name); |
241 | lpar_name[DIAG204_LPAR_NAME_LEN] = 0; |
242 | lpar_dir = hypfs_mkdir(parent: systems_dir, name: lpar_name); |
243 | if (IS_ERR(ptr: lpar_dir)) |
244 | return lpar_dir; |
245 | cpus_dir = hypfs_mkdir(parent: lpar_dir, name: "cpus" ); |
246 | if (IS_ERR(ptr: cpus_dir)) |
247 | return cpus_dir; |
248 | cpu_info = part_hdr + part_hdr__size(type: diag204_get_info_type()); |
249 | for (i = 0; i < part_hdr__rcpus(type: diag204_get_info_type(), hdr: part_hdr); i++) { |
250 | int rc; |
251 | |
252 | rc = hypfs_create_cpu_files(cpus_dir, cpu_info); |
253 | if (rc) |
254 | return ERR_PTR(error: rc); |
255 | cpu_info += cpu_info__size(type: diag204_get_info_type()); |
256 | } |
257 | return cpu_info; |
258 | } |
259 | |
260 | static int hypfs_create_phys_cpu_files(struct dentry *cpus_dir, void *cpu_info) |
261 | { |
262 | struct dentry *cpu_dir; |
263 | char buffer[TMP_SIZE]; |
264 | void *rc; |
265 | |
266 | snprintf(buf: buffer, TMP_SIZE, fmt: "%i" , phys_cpu__cpu_addr(type: diag204_get_info_type(), |
267 | hdr: cpu_info)); |
268 | cpu_dir = hypfs_mkdir(parent: cpus_dir, name: buffer); |
269 | if (IS_ERR(ptr: cpu_dir)) |
270 | return PTR_ERR(ptr: cpu_dir); |
271 | rc = hypfs_create_u64(dir: cpu_dir, name: "mgmtime" , |
272 | value: phys_cpu__mgm_time(type: diag204_get_info_type(), hdr: cpu_info)); |
273 | if (IS_ERR(ptr: rc)) |
274 | return PTR_ERR(ptr: rc); |
275 | diag224_idx2name(index: phys_cpu__ctidx(type: diag204_get_info_type(), hdr: cpu_info), name: buffer); |
276 | rc = hypfs_create_str(dir: cpu_dir, name: "type" , string: buffer); |
277 | return PTR_ERR_OR_ZERO(ptr: rc); |
278 | } |
279 | |
280 | static void *hypfs_create_phys_files(struct dentry *parent_dir, void *phys_hdr) |
281 | { |
282 | int i; |
283 | void *cpu_info; |
284 | struct dentry *cpus_dir; |
285 | |
286 | cpus_dir = hypfs_mkdir(parent: parent_dir, name: "cpus" ); |
287 | if (IS_ERR(ptr: cpus_dir)) |
288 | return cpus_dir; |
289 | cpu_info = phys_hdr + phys_hdr__size(type: diag204_get_info_type()); |
290 | for (i = 0; i < phys_hdr__cpus(type: diag204_get_info_type(), hdr: phys_hdr); i++) { |
291 | int rc; |
292 | |
293 | rc = hypfs_create_phys_cpu_files(cpus_dir, cpu_info); |
294 | if (rc) |
295 | return ERR_PTR(error: rc); |
296 | cpu_info += phys_cpu__size(type: diag204_get_info_type()); |
297 | } |
298 | return cpu_info; |
299 | } |
300 | |
301 | int hypfs_diag_create_files(struct dentry *root) |
302 | { |
303 | struct dentry *systems_dir, *hyp_dir; |
304 | void *time_hdr, *part_hdr; |
305 | void *buffer, *ptr; |
306 | int i, rc, pages; |
307 | |
308 | buffer = diag204_get_buffer(fmt: diag204_get_info_type(), pages: &pages); |
309 | if (IS_ERR(ptr: buffer)) |
310 | return PTR_ERR(ptr: buffer); |
311 | rc = diag204_store(buf: buffer, pages); |
312 | if (rc) |
313 | return rc; |
314 | |
315 | systems_dir = hypfs_mkdir(parent: root, name: "systems" ); |
316 | if (IS_ERR(ptr: systems_dir)) { |
317 | rc = PTR_ERR(ptr: systems_dir); |
318 | goto err_out; |
319 | } |
320 | time_hdr = (struct x_info_blk_hdr *)buffer; |
321 | part_hdr = time_hdr + info_blk_hdr__size(type: diag204_get_info_type()); |
322 | for (i = 0; i < info_blk_hdr__npar(type: diag204_get_info_type(), hdr: time_hdr); i++) { |
323 | part_hdr = hypfs_create_lpar_files(systems_dir, part_hdr); |
324 | if (IS_ERR(ptr: part_hdr)) { |
325 | rc = PTR_ERR(ptr: part_hdr); |
326 | goto err_out; |
327 | } |
328 | } |
329 | if (info_blk_hdr__flags(type: diag204_get_info_type(), hdr: time_hdr) & |
330 | DIAG204_LPAR_PHYS_FLG) { |
331 | ptr = hypfs_create_phys_files(parent_dir: root, phys_hdr: part_hdr); |
332 | if (IS_ERR(ptr)) { |
333 | rc = PTR_ERR(ptr); |
334 | goto err_out; |
335 | } |
336 | } |
337 | hyp_dir = hypfs_mkdir(parent: root, name: "hyp" ); |
338 | if (IS_ERR(ptr: hyp_dir)) { |
339 | rc = PTR_ERR(ptr: hyp_dir); |
340 | goto err_out; |
341 | } |
342 | ptr = hypfs_create_str(dir: hyp_dir, name: "type" , string: "LPAR Hypervisor" ); |
343 | if (IS_ERR(ptr)) { |
344 | rc = PTR_ERR(ptr); |
345 | goto err_out; |
346 | } |
347 | rc = 0; |
348 | |
349 | err_out: |
350 | return rc; |
351 | } |
352 | |
353 | /* Diagnose 224 functions */ |
354 | |
355 | static int diag224_idx2name(int index, char *name) |
356 | { |
357 | memcpy(name, diag224_cpu_names + ((index + 1) * DIAG204_CPU_NAME_LEN), |
358 | DIAG204_CPU_NAME_LEN); |
359 | name[DIAG204_CPU_NAME_LEN] = 0; |
360 | strim(name); |
361 | return 0; |
362 | } |
363 | |
364 | static int diag224_get_name_table(void) |
365 | { |
366 | /* memory must be below 2GB */ |
367 | diag224_cpu_names = (char *)__get_free_page(GFP_KERNEL | GFP_DMA); |
368 | if (!diag224_cpu_names) |
369 | return -ENOMEM; |
370 | if (diag224(diag224_cpu_names)) { |
371 | free_page((unsigned long)diag224_cpu_names); |
372 | return -EOPNOTSUPP; |
373 | } |
374 | EBCASC(diag224_cpu_names + 16, (*diag224_cpu_names + 1) * 16); |
375 | return 0; |
376 | } |
377 | |
378 | static void diag224_delete_name_table(void) |
379 | { |
380 | free_page((unsigned long)diag224_cpu_names); |
381 | } |
382 | |
383 | int __init __hypfs_diag_fs_init(void) |
384 | { |
385 | if (MACHINE_IS_LPAR) |
386 | return diag224_get_name_table(); |
387 | return 0; |
388 | } |
389 | |
390 | void __hypfs_diag_fs_exit(void) |
391 | { |
392 | diag224_delete_name_table(); |
393 | } |
394 | |