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 DBFS_D204_HDR_VERSION 0 |
25 | |
26 | static enum diag204_sc diag204_store_sc; /* used subcode for store */ |
27 | static enum diag204_format diag204_info_type; /* used diag 204 data format */ |
28 | |
29 | static void *diag204_buf; /* 4K aligned buffer for diag204 data */ |
30 | static int diag204_buf_pages; /* number of pages for diag204 data */ |
31 | |
32 | static struct dentry *dbfs_d204_file; |
33 | |
34 | enum diag204_format diag204_get_info_type(void) |
35 | { |
36 | return diag204_info_type; |
37 | } |
38 | |
39 | static void diag204_set_info_type(enum diag204_format type) |
40 | { |
41 | diag204_info_type = type; |
42 | } |
43 | |
44 | /* Diagnose 204 functions */ |
45 | /* |
46 | * For the old diag subcode 4 with simple data format we have to use real |
47 | * memory. If we use subcode 6 or 7 with extended data format, we can (and |
48 | * should) use vmalloc, since we need a lot of memory in that case. Currently |
49 | * up to 93 pages! |
50 | */ |
51 | |
52 | static void diag204_free_buffer(void) |
53 | { |
54 | vfree(addr: diag204_buf); |
55 | diag204_buf = NULL; |
56 | } |
57 | |
58 | void *diag204_get_buffer(enum diag204_format fmt, int *pages) |
59 | { |
60 | if (diag204_buf) { |
61 | *pages = diag204_buf_pages; |
62 | return diag204_buf; |
63 | } |
64 | if (fmt == DIAG204_INFO_SIMPLE) { |
65 | *pages = 1; |
66 | } else {/* DIAG204_INFO_EXT */ |
67 | *pages = diag204((unsigned long)DIAG204_SUBC_RSI | |
68 | (unsigned long)DIAG204_INFO_EXT, 0, NULL); |
69 | if (*pages <= 0) |
70 | return ERR_PTR(error: -EOPNOTSUPP); |
71 | } |
72 | diag204_buf = __vmalloc_node(array_size(*pages, PAGE_SIZE), |
73 | PAGE_SIZE, GFP_KERNEL, NUMA_NO_NODE, |
74 | caller: __builtin_return_address(0)); |
75 | if (!diag204_buf) |
76 | return ERR_PTR(error: -ENOMEM); |
77 | diag204_buf_pages = *pages; |
78 | return diag204_buf; |
79 | } |
80 | |
81 | /* |
82 | * diag204_probe() has to find out, which type of diagnose 204 implementation |
83 | * we have on our machine. Currently there are three possible scanarios: |
84 | * - subcode 4 + simple data format (only one page) |
85 | * - subcode 4-6 + extended data format |
86 | * - subcode 4-7 + extended data format |
87 | * |
88 | * Subcode 5 is used to retrieve the size of the data, provided by subcodes |
89 | * 6 and 7. Subcode 7 basically has the same function as subcode 6. In addition |
90 | * to subcode 6 it provides also information about secondary cpus. |
91 | * In order to get as much information as possible, we first try |
92 | * subcode 7, then 6 and if both fail, we use subcode 4. |
93 | */ |
94 | |
95 | static int diag204_probe(void) |
96 | { |
97 | void *buf; |
98 | int pages, rc; |
99 | |
100 | buf = diag204_get_buffer(fmt: DIAG204_INFO_EXT, pages: &pages); |
101 | if (!IS_ERR(ptr: buf)) { |
102 | if (diag204((unsigned long)DIAG204_SUBC_STIB7 | |
103 | (unsigned long)DIAG204_INFO_EXT, pages, buf) >= 0) { |
104 | diag204_store_sc = DIAG204_SUBC_STIB7; |
105 | diag204_set_info_type(type: DIAG204_INFO_EXT); |
106 | goto out; |
107 | } |
108 | if (diag204((unsigned long)DIAG204_SUBC_STIB6 | |
109 | (unsigned long)DIAG204_INFO_EXT, pages, buf) >= 0) { |
110 | diag204_store_sc = DIAG204_SUBC_STIB6; |
111 | diag204_set_info_type(type: DIAG204_INFO_EXT); |
112 | goto out; |
113 | } |
114 | diag204_free_buffer(); |
115 | } |
116 | |
117 | /* subcodes 6 and 7 failed, now try subcode 4 */ |
118 | |
119 | buf = diag204_get_buffer(fmt: DIAG204_INFO_SIMPLE, pages: &pages); |
120 | if (IS_ERR(ptr: buf)) { |
121 | rc = PTR_ERR(ptr: buf); |
122 | goto fail_alloc; |
123 | } |
124 | if (diag204((unsigned long)DIAG204_SUBC_STIB4 | |
125 | (unsigned long)DIAG204_INFO_SIMPLE, pages, buf) >= 0) { |
126 | diag204_store_sc = DIAG204_SUBC_STIB4; |
127 | diag204_set_info_type(type: DIAG204_INFO_SIMPLE); |
128 | goto out; |
129 | } else { |
130 | rc = -EOPNOTSUPP; |
131 | goto fail_store; |
132 | } |
133 | out: |
134 | rc = 0; |
135 | fail_store: |
136 | diag204_free_buffer(); |
137 | fail_alloc: |
138 | return rc; |
139 | } |
140 | |
141 | int diag204_store(void *buf, int pages) |
142 | { |
143 | int rc; |
144 | |
145 | rc = diag204((unsigned long)diag204_store_sc | |
146 | (unsigned long)diag204_get_info_type(), pages, buf); |
147 | return rc < 0 ? -EOPNOTSUPP : 0; |
148 | } |
149 | |
150 | struct dbfs_d204_hdr { |
151 | u64 len; /* Length of d204 buffer without header */ |
152 | u16 version; /* Version of header */ |
153 | u8 sc; /* Used subcode */ |
154 | char reserved[53]; |
155 | } __attribute__ ((packed)); |
156 | |
157 | struct dbfs_d204 { |
158 | struct dbfs_d204_hdr hdr; /* 64 byte header */ |
159 | char buf[]; /* d204 buffer */ |
160 | } __attribute__ ((packed)); |
161 | |
162 | static int dbfs_d204_create(void **data, void **data_free_ptr, size_t *size) |
163 | { |
164 | struct dbfs_d204 *d204; |
165 | int rc, buf_size; |
166 | void *base; |
167 | |
168 | buf_size = PAGE_SIZE * (diag204_buf_pages + 1) + sizeof(d204->hdr); |
169 | base = vzalloc(size: buf_size); |
170 | if (!base) |
171 | return -ENOMEM; |
172 | d204 = PTR_ALIGN(base + sizeof(d204->hdr), PAGE_SIZE) - sizeof(d204->hdr); |
173 | rc = diag204_store(buf: d204->buf, pages: diag204_buf_pages); |
174 | if (rc) { |
175 | vfree(addr: base); |
176 | return rc; |
177 | } |
178 | d204->hdr.version = DBFS_D204_HDR_VERSION; |
179 | d204->hdr.len = PAGE_SIZE * diag204_buf_pages; |
180 | d204->hdr.sc = diag204_store_sc; |
181 | *data = d204; |
182 | *data_free_ptr = base; |
183 | *size = d204->hdr.len + sizeof(struct dbfs_d204_hdr); |
184 | return 0; |
185 | } |
186 | |
187 | static struct hypfs_dbfs_file dbfs_file_d204 = { |
188 | .name = "diag_204" , |
189 | .data_create = dbfs_d204_create, |
190 | .data_free = vfree, |
191 | }; |
192 | |
193 | __init int hypfs_diag_init(void) |
194 | { |
195 | int rc; |
196 | |
197 | if (diag204_probe()) { |
198 | pr_info("The hardware system does not support hypfs\n" ); |
199 | return -ENODATA; |
200 | } |
201 | |
202 | if (diag204_get_info_type() == DIAG204_INFO_EXT) |
203 | hypfs_dbfs_create_file(df: &dbfs_file_d204); |
204 | |
205 | rc = hypfs_diag_fs_init(); |
206 | if (rc) { |
207 | pr_err("The hardware system does not provide all functions required by hypfs\n" ); |
208 | debugfs_remove(dentry: dbfs_d204_file); |
209 | } |
210 | return rc; |
211 | } |
212 | |
213 | void hypfs_diag_exit(void) |
214 | { |
215 | debugfs_remove(dentry: dbfs_d204_file); |
216 | hypfs_diag_fs_exit(); |
217 | diag204_free_buffer(); |
218 | hypfs_dbfs_remove_file(df: &dbfs_file_d204); |
219 | } |
220 | |