1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Request memory topology information via diag0x310.
4 *
5 * Copyright IBM Corp. 2025
6 */
7
8#include <linux/kernel.h>
9#include <linux/types.h>
10#include <linux/uaccess.h>
11#include <linux/vmalloc.h>
12#include <asm/diag.h>
13#include <asm/sclp.h>
14#include <uapi/asm/diag.h>
15#include "diag_ioctl.h"
16
17#define DIAG310_LEVELMIN 1
18#define DIAG310_LEVELMAX 6
19
20enum diag310_sc {
21 DIAG310_SUBC_0 = 0,
22 DIAG310_SUBC_1 = 1,
23 DIAG310_SUBC_4 = 4,
24 DIAG310_SUBC_5 = 5
25};
26
27enum diag310_retcode {
28 DIAG310_RET_SUCCESS = 0x0001,
29 DIAG310_RET_BUSY = 0x0101,
30 DIAG310_RET_OPNOTSUPP = 0x0102,
31 DIAG310_RET_SC4_INVAL = 0x0401,
32 DIAG310_RET_SC4_NODATA = 0x0402,
33 DIAG310_RET_SC5_INVAL = 0x0501,
34 DIAG310_RET_SC5_NODATA = 0x0502,
35 DIAG310_RET_SC5_ESIZE = 0x0503
36};
37
38union diag310_response {
39 u64 response;
40 struct {
41 u64 result : 32;
42 u64 : 16;
43 u64 rc : 16;
44 };
45};
46
47union diag310_req_subcode {
48 u64 subcode;
49 struct {
50 u64 : 48;
51 u64 st : 8;
52 u64 sc : 8;
53 };
54};
55
56union diag310_req_size {
57 u64 size;
58 struct {
59 u64 page_count : 32;
60 u64 : 32;
61 };
62};
63
64static inline unsigned long diag310(unsigned long subcode, unsigned long size, void *addr)
65{
66 union register_pair rp = { .even = (unsigned long)addr, .odd = size };
67
68 diag_stat_inc(DIAG_STAT_X310);
69 asm volatile("diag %[rp],%[subcode],0x310"
70 : [rp] "+d" (rp.pair)
71 : [subcode] "d" (subcode)
72 : "memory");
73 return rp.odd;
74}
75
76static int diag310_result_to_errno(unsigned int result)
77{
78 switch (result) {
79 case DIAG310_RET_BUSY:
80 return -EBUSY;
81 case DIAG310_RET_OPNOTSUPP:
82 return -EOPNOTSUPP;
83 default:
84 return -EINVAL;
85 }
86}
87
88static int diag310_get_subcode_mask(unsigned long *mask)
89{
90 union diag310_response res;
91
92 res.response = diag310(subcode: DIAG310_SUBC_0, size: 0, NULL);
93 if (res.rc != DIAG310_RET_SUCCESS)
94 return diag310_result_to_errno(result: res.rc);
95 *mask = res.response;
96 return 0;
97}
98
99static int diag310_get_memtop_stride(unsigned long *stride)
100{
101 union diag310_response res;
102
103 res.response = diag310(subcode: DIAG310_SUBC_1, size: 0, NULL);
104 if (res.rc != DIAG310_RET_SUCCESS)
105 return diag310_result_to_errno(result: res.rc);
106 *stride = res.result;
107 return 0;
108}
109
110static int diag310_get_memtop_size(unsigned long *pages, unsigned long level)
111{
112 union diag310_req_subcode req = { .sc = DIAG310_SUBC_4, .st = level };
113 union diag310_response res;
114
115 res.response = diag310(subcode: req.subcode, size: 0, NULL);
116 switch (res.rc) {
117 case DIAG310_RET_SUCCESS:
118 *pages = res.result;
119 return 0;
120 case DIAG310_RET_SC4_NODATA:
121 return -ENODATA;
122 case DIAG310_RET_SC4_INVAL:
123 return -EINVAL;
124 default:
125 return diag310_result_to_errno(result: res.rc);
126 }
127}
128
129static int diag310_store_topology_map(void *buf, unsigned long pages, unsigned long level)
130{
131 union diag310_req_subcode req_sc = { .sc = DIAG310_SUBC_5, .st = level };
132 union diag310_req_size req_size = { .page_count = pages };
133 union diag310_response res;
134
135 res.response = diag310(subcode: req_sc.subcode, size: req_size.size, addr: buf);
136 switch (res.rc) {
137 case DIAG310_RET_SUCCESS:
138 return 0;
139 case DIAG310_RET_SC5_NODATA:
140 return -ENODATA;
141 case DIAG310_RET_SC5_ESIZE:
142 return -EOVERFLOW;
143 case DIAG310_RET_SC5_INVAL:
144 return -EINVAL;
145 default:
146 return diag310_result_to_errno(result: res.rc);
147 }
148}
149
150static int diag310_check_features(void)
151{
152 static int features_available;
153 unsigned long mask;
154 int rc;
155
156 if (READ_ONCE(features_available))
157 return 0;
158 if (!sclp.has_diag310)
159 return -EOPNOTSUPP;
160 rc = diag310_get_subcode_mask(mask: &mask);
161 if (rc)
162 return rc;
163 if (!test_bit_inv(DIAG310_SUBC_1, &mask))
164 return -EOPNOTSUPP;
165 if (!test_bit_inv(DIAG310_SUBC_4, &mask))
166 return -EOPNOTSUPP;
167 if (!test_bit_inv(DIAG310_SUBC_5, &mask))
168 return -EOPNOTSUPP;
169 WRITE_ONCE(features_available, 1);
170 return 0;
171}
172
173static int memtop_get_stride_len(unsigned long *res)
174{
175 static unsigned long memtop_stride;
176 unsigned long stride;
177 int rc;
178
179 stride = READ_ONCE(memtop_stride);
180 if (!stride) {
181 rc = diag310_get_memtop_stride(stride: &stride);
182 if (rc)
183 return rc;
184 WRITE_ONCE(memtop_stride, stride);
185 }
186 *res = stride;
187 return 0;
188}
189
190static int memtop_get_page_count(unsigned long *res, unsigned long level)
191{
192 static unsigned long memtop_pages[DIAG310_LEVELMAX];
193 unsigned long pages;
194 int rc;
195
196 if (level > DIAG310_LEVELMAX || level < DIAG310_LEVELMIN)
197 return -EINVAL;
198 pages = READ_ONCE(memtop_pages[level - 1]);
199 if (!pages) {
200 rc = diag310_get_memtop_size(pages: &pages, level);
201 if (rc)
202 return rc;
203 WRITE_ONCE(memtop_pages[level - 1], pages);
204 }
205 *res = pages;
206 return 0;
207}
208
209long diag310_memtop_stride(unsigned long arg)
210{
211 size_t __user *argp = (void __user *)arg;
212 unsigned long stride;
213 int rc;
214
215 rc = diag310_check_features();
216 if (rc)
217 return rc;
218 rc = memtop_get_stride_len(res: &stride);
219 if (rc)
220 return rc;
221 if (put_user(stride, argp))
222 return -EFAULT;
223 return 0;
224}
225
226long diag310_memtop_len(unsigned long arg)
227{
228 size_t __user *argp = (void __user *)arg;
229 unsigned long pages, level;
230 int rc;
231
232 rc = diag310_check_features();
233 if (rc)
234 return rc;
235 if (get_user(level, argp))
236 return -EFAULT;
237 rc = memtop_get_page_count(res: &pages, level);
238 if (rc)
239 return rc;
240 if (put_user(pages * PAGE_SIZE, argp))
241 return -EFAULT;
242 return 0;
243}
244
245long diag310_memtop_buf(unsigned long arg)
246{
247 struct diag310_memtop __user *udata = (struct diag310_memtop __user *)arg;
248 unsigned long level, pages, data_size;
249 u64 address;
250 void *buf;
251 int rc;
252
253 rc = diag310_check_features();
254 if (rc)
255 return rc;
256 if (get_user(level, &udata->nesting_lvl))
257 return -EFAULT;
258 if (get_user(address, &udata->address))
259 return -EFAULT;
260 rc = memtop_get_page_count(res: &pages, level);
261 if (rc)
262 return rc;
263 data_size = pages * PAGE_SIZE;
264 buf = __vmalloc_node(data_size, PAGE_SIZE, GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT,
265 NUMA_NO_NODE, __builtin_return_address(0));
266 if (!buf)
267 return -ENOMEM;
268 rc = diag310_store_topology_map(buf, pages, level);
269 if (rc)
270 goto out;
271 if (copy_to_user(to: (void __user *)address, from: buf, n: data_size))
272 rc = -EFAULT;
273out:
274 vfree(addr: buf);
275 return rc;
276}
277

source code of linux/arch/s390/kernel/diag/diag310.c