Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
a755a45d JG |
2 | /* |
3 | * Copyright IBM Corp. 2012 | |
4 | * | |
5 | * Author(s): | |
6 | * Jan Glauber <jang@linux.vnet.ibm.com> | |
7 | */ | |
8 | ||
896cb7e6 GS |
9 | #define KMSG_COMPONENT "zpci" |
10 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | |
a755a45d | 11 | |
988b86e6 | 12 | #include <linux/compat.h> |
a755a45d | 13 | #include <linux/kernel.h> |
988b86e6 | 14 | #include <linux/miscdevice.h> |
a755a45d JG |
15 | #include <linux/slab.h> |
16 | #include <linux/err.h> | |
17 | #include <linux/delay.h> | |
18 | #include <linux/pci.h> | |
988b86e6 | 19 | #include <linux/uaccess.h> |
a2ab8333 | 20 | #include <asm/pci_debug.h> |
a755a45d | 21 | #include <asm/pci_clp.h> |
988b86e6 MS |
22 | #include <asm/clp.h> |
23 | #include <uapi/asm/clp.h> | |
a755a45d | 24 | |
5c5afd02 SO |
25 | bool zpci_unique_uid; |
26 | ||
5db23179 SO |
27 | static void update_uid_checking(bool new) |
28 | { | |
29 | if (zpci_unique_uid != new) | |
30 | zpci_dbg(1, "uid checking:%d\n", new); | |
31 | ||
32 | zpci_unique_uid = new; | |
33 | } | |
34 | ||
1f1dcbd4 SO |
35 | static inline void zpci_err_clp(unsigned int rsp, int rc) |
36 | { | |
37 | struct { | |
38 | unsigned int rsp; | |
39 | int rc; | |
40 | } __packed data = {rsp, rc}; | |
41 | ||
42 | zpci_err_hex(&data, sizeof(data)); | |
43 | } | |
44 | ||
a755a45d | 45 | /* |
988b86e6 MS |
46 | * Call Logical Processor with c=1, lps=0 and command 1 |
47 | * to get the bit mask of installed logical processors | |
a755a45d | 48 | */ |
988b86e6 MS |
49 | static inline int clp_get_ilp(unsigned long *ilp) |
50 | { | |
51 | unsigned long mask; | |
52 | int cc = 3; | |
53 | ||
54 | asm volatile ( | |
55 | " .insn rrf,0xb9a00000,%[mask],%[cmd],8,0\n" | |
56 | "0: ipm %[cc]\n" | |
57 | " srl %[cc],28\n" | |
58 | "1:\n" | |
59 | EX_TABLE(0b, 1b) | |
60 | : [cc] "+d" (cc), [mask] "=d" (mask) : [cmd] "a" (1) | |
61 | : "cc"); | |
62 | *ilp = mask; | |
63 | return cc; | |
64 | } | |
65 | ||
66 | /* | |
67 | * Call Logical Processor with c=0, the give constant lps and an lpcb request. | |
68 | */ | |
771c24f6 | 69 | static __always_inline int clp_req(void *data, unsigned int lps) |
a755a45d | 70 | { |
bf4ec24f SO |
71 | struct { u8 _[CLP_BLK_SIZE]; } *req = data; |
72 | u64 ignored; | |
988b86e6 | 73 | int cc = 3; |
a755a45d JG |
74 | |
75 | asm volatile ( | |
988b86e6 MS |
76 | " .insn rrf,0xb9a00000,%[ign],%[req],0,%[lps]\n" |
77 | "0: ipm %[cc]\n" | |
a755a45d | 78 | " srl %[cc],28\n" |
988b86e6 MS |
79 | "1:\n" |
80 | EX_TABLE(0b, 1b) | |
81 | : [cc] "+d" (cc), [ign] "=d" (ignored), "+m" (*req) | |
82 | : [req] "a" (req), [lps] "i" (lps) | |
bf4ec24f | 83 | : "cc"); |
a755a45d JG |
84 | return cc; |
85 | } | |
86 | ||
1d578966 | 87 | static void *clp_alloc_block(gfp_t gfp_mask) |
a755a45d | 88 | { |
1d578966 | 89 | return (void *) __get_free_pages(gfp_mask, get_order(CLP_BLK_SIZE)); |
a755a45d JG |
90 | } |
91 | ||
92 | static void clp_free_block(void *ptr) | |
93 | { | |
94 | free_pages((unsigned long) ptr, get_order(CLP_BLK_SIZE)); | |
95 | } | |
96 | ||
97 | static void clp_store_query_pci_fngrp(struct zpci_dev *zdev, | |
98 | struct clp_rsp_query_pci_grp *response) | |
99 | { | |
828b35f6 JG |
100 | zdev->tlb_refresh = response->refresh; |
101 | zdev->dma_mask = response->dasm; | |
9a4da8a5 | 102 | zdev->msi_addr = response->msia; |
b19148f6 | 103 | zdev->max_msi = response->noi; |
d0b08853 | 104 | zdev->fmb_update = response->mui; |
9a4da8a5 | 105 | |
a755a45d JG |
106 | switch (response->version) { |
107 | case 1: | |
108 | zdev->max_bus_speed = PCIE_SPEED_5_0GT; | |
109 | break; | |
110 | default: | |
111 | zdev->max_bus_speed = PCI_SPEED_UNKNOWN; | |
112 | break; | |
113 | } | |
114 | } | |
115 | ||
116 | static int clp_query_pci_fngrp(struct zpci_dev *zdev, u8 pfgid) | |
117 | { | |
118 | struct clp_req_rsp_query_pci_grp *rrb; | |
119 | int rc; | |
120 | ||
1d578966 | 121 | rrb = clp_alloc_block(GFP_KERNEL); |
a755a45d JG |
122 | if (!rrb) |
123 | return -ENOMEM; | |
124 | ||
125 | memset(rrb, 0, sizeof(*rrb)); | |
126 | rrb->request.hdr.len = sizeof(rrb->request); | |
127 | rrb->request.hdr.cmd = CLP_QUERY_PCI_FNGRP; | |
128 | rrb->response.hdr.len = sizeof(rrb->response); | |
129 | rrb->request.pfgid = pfgid; | |
130 | ||
988b86e6 | 131 | rc = clp_req(rrb, CLP_LPS_PCI); |
a755a45d JG |
132 | if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) |
133 | clp_store_query_pci_fngrp(zdev, &rrb->response); | |
134 | else { | |
1f1dcbd4 SO |
135 | zpci_err("Q PCI FGRP:\n"); |
136 | zpci_err_clp(rrb->response.hdr.rsp, rc); | |
a755a45d JG |
137 | rc = -EIO; |
138 | } | |
139 | clp_free_block(rrb); | |
140 | return rc; | |
141 | } | |
142 | ||
143 | static int clp_store_query_pci_fn(struct zpci_dev *zdev, | |
144 | struct clp_rsp_query_pci *response) | |
145 | { | |
146 | int i; | |
147 | ||
148 | for (i = 0; i < PCI_BAR_COUNT; i++) { | |
149 | zdev->bars[i].val = le32_to_cpu(response->bar[i]); | |
150 | zdev->bars[i].size = response->bar_size[i]; | |
151 | } | |
828b35f6 JG |
152 | zdev->start_dma = response->sdma; |
153 | zdev->end_dma = response->edma; | |
a755a45d JG |
154 | zdev->pchid = response->pchid; |
155 | zdev->pfgid = response->pfgid; | |
ac4995b9 SO |
156 | zdev->pft = response->pft; |
157 | zdev->vfn = response->vfn; | |
158 | zdev->uid = response->uid; | |
0b7589ec | 159 | zdev->fmb_length = sizeof(u32) * response->fmb_len; |
ac4995b9 SO |
160 | |
161 | memcpy(zdev->pfip, response->pfip, sizeof(zdev->pfip)); | |
162 | if (response->util_str_avail) { | |
163 | memcpy(zdev->util_str, response->util_str, | |
164 | sizeof(zdev->util_str)); | |
165 | } | |
71ba41c9 SO |
166 | zdev->mio_capable = response->mio_addr_avail; |
167 | for (i = 0; i < PCI_BAR_COUNT; i++) { | |
1354b38b | 168 | if (!(response->mio.valid & (1 << (PCI_BAR_COUNT - i - 1)))) |
71ba41c9 | 169 | continue; |
ac4995b9 | 170 | |
1354b38b SO |
171 | zdev->bars[i].mio_wb = (void __iomem *) response->mio.addr[i].wb; |
172 | zdev->bars[i].mio_wt = (void __iomem *) response->mio.addr[i].wt; | |
71ba41c9 | 173 | } |
a755a45d JG |
174 | return 0; |
175 | } | |
176 | ||
177 | static int clp_query_pci_fn(struct zpci_dev *zdev, u32 fh) | |
178 | { | |
179 | struct clp_req_rsp_query_pci *rrb; | |
180 | int rc; | |
181 | ||
1d578966 | 182 | rrb = clp_alloc_block(GFP_KERNEL); |
a755a45d JG |
183 | if (!rrb) |
184 | return -ENOMEM; | |
185 | ||
186 | memset(rrb, 0, sizeof(*rrb)); | |
187 | rrb->request.hdr.len = sizeof(rrb->request); | |
188 | rrb->request.hdr.cmd = CLP_QUERY_PCI_FN; | |
189 | rrb->response.hdr.len = sizeof(rrb->response); | |
190 | rrb->request.fh = fh; | |
191 | ||
988b86e6 | 192 | rc = clp_req(rrb, CLP_LPS_PCI); |
a755a45d JG |
193 | if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) { |
194 | rc = clp_store_query_pci_fn(zdev, &rrb->response); | |
195 | if (rc) | |
196 | goto out; | |
aa624886 | 197 | rc = clp_query_pci_fngrp(zdev, rrb->response.pfgid); |
a755a45d | 198 | } else { |
1f1dcbd4 SO |
199 | zpci_err("Q PCI FN:\n"); |
200 | zpci_err_clp(rrb->response.hdr.rsp, rc); | |
a755a45d JG |
201 | rc = -EIO; |
202 | } | |
203 | out: | |
204 | clp_free_block(rrb); | |
205 | return rc; | |
206 | } | |
207 | ||
208 | int clp_add_pci_device(u32 fid, u32 fh, int configured) | |
209 | { | |
210 | struct zpci_dev *zdev; | |
be2c3676 | 211 | int rc = -ENOMEM; |
a755a45d | 212 | |
a2ab8333 | 213 | zpci_dbg(3, "add fid:%x, fh:%x, c:%d\n", fid, fh, configured); |
7d594322 SO |
214 | zdev = kzalloc(sizeof(*zdev), GFP_KERNEL); |
215 | if (!zdev) | |
be2c3676 | 216 | goto error; |
a755a45d JG |
217 | |
218 | zdev->fh = fh; | |
219 | zdev->fid = fid; | |
220 | ||
221 | /* Query function properties and update zdev */ | |
222 | rc = clp_query_pci_fn(zdev, fh); | |
223 | if (rc) | |
224 | goto error; | |
225 | ||
226 | if (configured) | |
227 | zdev->state = ZPCI_FN_STATE_CONFIGURED; | |
228 | else | |
229 | zdev->state = ZPCI_FN_STATE_STANDBY; | |
230 | ||
231 | rc = zpci_create_device(zdev); | |
232 | if (rc) | |
233 | goto error; | |
234 | return 0; | |
235 | ||
236 | error: | |
be2c3676 | 237 | zpci_dbg(0, "add fid:%x, rc:%d\n", fid, rc); |
7d594322 | 238 | kfree(zdev); |
a755a45d JG |
239 | return rc; |
240 | } | |
241 | ||
242 | /* | |
243 | * Enable/Disable a given PCI function defined by its function handle. | |
244 | */ | |
245 | static int clp_set_pci_fn(u32 *fh, u8 nr_dma_as, u8 command) | |
246 | { | |
247 | struct clp_req_rsp_set_pci *rrb; | |
d03abe58 | 248 | int rc, retries = 100; |
a755a45d | 249 | |
1d578966 | 250 | rrb = clp_alloc_block(GFP_KERNEL); |
a755a45d JG |
251 | if (!rrb) |
252 | return -ENOMEM; | |
253 | ||
254 | do { | |
255 | memset(rrb, 0, sizeof(*rrb)); | |
256 | rrb->request.hdr.len = sizeof(rrb->request); | |
257 | rrb->request.hdr.cmd = CLP_SET_PCI_FN; | |
258 | rrb->response.hdr.len = sizeof(rrb->response); | |
259 | rrb->request.fh = *fh; | |
260 | rrb->request.oc = command; | |
261 | rrb->request.ndas = nr_dma_as; | |
262 | ||
988b86e6 | 263 | rc = clp_req(rrb, CLP_LPS_PCI); |
a755a45d JG |
264 | if (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY) { |
265 | retries--; | |
266 | if (retries < 0) | |
267 | break; | |
d03abe58 | 268 | msleep(20); |
a755a45d JG |
269 | } |
270 | } while (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY); | |
271 | ||
272 | if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) | |
273 | *fh = rrb->response.fh; | |
274 | else { | |
1f1dcbd4 SO |
275 | zpci_err("Set PCI FN:\n"); |
276 | zpci_err_clp(rrb->response.hdr.rsp, rc); | |
a755a45d JG |
277 | rc = -EIO; |
278 | } | |
279 | clp_free_block(rrb); | |
280 | return rc; | |
281 | } | |
282 | ||
283 | int clp_enable_fh(struct zpci_dev *zdev, u8 nr_dma_as) | |
284 | { | |
285 | u32 fh = zdev->fh; | |
286 | int rc; | |
287 | ||
288 | rc = clp_set_pci_fn(&fh, nr_dma_as, CLP_SET_ENABLE_PCI_FN); | |
71ba41c9 SO |
289 | zpci_dbg(3, "ena fid:%x, fh:%x, rc:%d\n", zdev->fid, fh, rc); |
290 | if (rc) | |
291 | goto out; | |
a2ab8333 | 292 | |
71ba41c9 | 293 | zdev->fh = fh; |
6ae3483d | 294 | if (zpci_use_mio(zdev)) { |
71ba41c9 SO |
295 | rc = clp_set_pci_fn(&fh, nr_dma_as, CLP_SET_ENABLE_MIO); |
296 | zpci_dbg(3, "ena mio fid:%x, fh:%x, rc:%d\n", zdev->fid, fh, rc); | |
297 | if (rc) | |
298 | clp_disable_fh(zdev); | |
299 | } | |
300 | out: | |
a755a45d JG |
301 | return rc; |
302 | } | |
303 | ||
304 | int clp_disable_fh(struct zpci_dev *zdev) | |
305 | { | |
306 | u32 fh = zdev->fh; | |
307 | int rc; | |
308 | ||
309 | if (!zdev_enabled(zdev)) | |
310 | return 0; | |
311 | ||
a755a45d | 312 | rc = clp_set_pci_fn(&fh, 0, CLP_SET_DISABLE_PCI_FN); |
71ba41c9 | 313 | zpci_dbg(3, "dis fid:%x, fh:%x, rc:%d\n", zdev->fid, fh, rc); |
a755a45d | 314 | if (!rc) |
a755a45d | 315 | zdev->fh = fh; |
a2ab8333 | 316 | |
a755a45d JG |
317 | return rc; |
318 | } | |
319 | ||
783684f1 SO |
320 | static int clp_list_pci(struct clp_req_rsp_list_pci *rrb, void *data, |
321 | void (*cb)(struct clp_fh_list_entry *, void *)) | |
a755a45d | 322 | { |
a755a45d JG |
323 | u64 resume_token = 0; |
324 | int entries, i, rc; | |
325 | ||
a755a45d JG |
326 | do { |
327 | memset(rrb, 0, sizeof(*rrb)); | |
328 | rrb->request.hdr.len = sizeof(rrb->request); | |
329 | rrb->request.hdr.cmd = CLP_LIST_PCI; | |
330 | /* store as many entries as possible */ | |
331 | rrb->response.hdr.len = CLP_BLK_SIZE - LIST_PCI_HDR_LEN; | |
332 | rrb->request.resume_token = resume_token; | |
333 | ||
334 | /* Get PCI function handle list */ | |
988b86e6 | 335 | rc = clp_req(rrb, CLP_LPS_PCI); |
a755a45d | 336 | if (rc || rrb->response.hdr.rsp != CLP_RC_OK) { |
1f1dcbd4 SO |
337 | zpci_err("List PCI FN:\n"); |
338 | zpci_err_clp(rrb->response.hdr.rsp, rc); | |
a755a45d JG |
339 | rc = -EIO; |
340 | goto out; | |
341 | } | |
342 | ||
5db23179 | 343 | update_uid_checking(rrb->response.uid_checking); |
a755a45d JG |
344 | WARN_ON_ONCE(rrb->response.entry_size != |
345 | sizeof(struct clp_fh_list_entry)); | |
346 | ||
347 | entries = (rrb->response.hdr.len - LIST_PCI_HDR_LEN) / | |
348 | rrb->response.entry_size; | |
a755a45d | 349 | |
a755a45d | 350 | resume_token = rrb->response.resume_token; |
a755a45d | 351 | for (i = 0; i < entries; i++) |
783684f1 | 352 | cb(&rrb->response.fh_list[i], data); |
a755a45d | 353 | } while (resume_token); |
a755a45d | 354 | out: |
1d578966 SO |
355 | return rc; |
356 | } | |
357 | ||
783684f1 | 358 | static void __clp_add(struct clp_fh_list_entry *entry, void *data) |
1d578966 SO |
359 | { |
360 | struct zpci_dev *zdev; | |
361 | ||
362 | if (!entry->vendor_id) | |
363 | return; | |
364 | ||
365 | zdev = get_zdev_by_fid(entry->fid); | |
01553d9a | 366 | if (!zdev) |
1d578966 | 367 | clp_add_pci_device(entry->fid, entry->fh, entry->config_state); |
1d578966 SO |
368 | } |
369 | ||
783684f1 | 370 | static void __clp_update(struct clp_fh_list_entry *entry, void *data) |
57b5918c SO |
371 | { |
372 | struct zpci_dev *zdev; | |
373 | ||
374 | if (!entry->vendor_id) | |
375 | return; | |
376 | ||
377 | zdev = get_zdev_by_fid(entry->fid); | |
378 | if (!zdev) | |
379 | return; | |
380 | ||
381 | zdev->fh = entry->fh; | |
382 | } | |
383 | ||
1d578966 SO |
384 | int clp_scan_pci_devices(void) |
385 | { | |
386 | struct clp_req_rsp_list_pci *rrb; | |
387 | int rc; | |
388 | ||
389 | rrb = clp_alloc_block(GFP_KERNEL); | |
390 | if (!rrb) | |
391 | return -ENOMEM; | |
392 | ||
783684f1 | 393 | rc = clp_list_pci(rrb, NULL, __clp_add); |
1d578966 SO |
394 | |
395 | clp_free_block(rrb); | |
396 | return rc; | |
397 | } | |
398 | ||
399 | int clp_rescan_pci_devices(void) | |
400 | { | |
401 | struct clp_req_rsp_list_pci *rrb; | |
402 | int rc; | |
403 | ||
01553d9a SO |
404 | zpci_remove_reserved_devices(); |
405 | ||
1d578966 SO |
406 | rrb = clp_alloc_block(GFP_KERNEL); |
407 | if (!rrb) | |
408 | return -ENOMEM; | |
409 | ||
01553d9a | 410 | rc = clp_list_pci(rrb, NULL, __clp_add); |
1d578966 | 411 | |
a755a45d JG |
412 | clp_free_block(rrb); |
413 | return rc; | |
414 | } | |
57b5918c SO |
415 | |
416 | int clp_rescan_pci_devices_simple(void) | |
417 | { | |
418 | struct clp_req_rsp_list_pci *rrb; | |
419 | int rc; | |
420 | ||
421 | rrb = clp_alloc_block(GFP_NOWAIT); | |
422 | if (!rrb) | |
423 | return -ENOMEM; | |
424 | ||
783684f1 SO |
425 | rc = clp_list_pci(rrb, NULL, __clp_update); |
426 | ||
427 | clp_free_block(rrb); | |
428 | return rc; | |
429 | } | |
430 | ||
431 | struct clp_state_data { | |
432 | u32 fid; | |
433 | enum zpci_state state; | |
434 | }; | |
435 | ||
436 | static void __clp_get_state(struct clp_fh_list_entry *entry, void *data) | |
437 | { | |
438 | struct clp_state_data *sd = data; | |
439 | ||
440 | if (entry->fid != sd->fid) | |
441 | return; | |
442 | ||
443 | sd->state = entry->config_state; | |
444 | } | |
445 | ||
446 | int clp_get_state(u32 fid, enum zpci_state *state) | |
447 | { | |
448 | struct clp_req_rsp_list_pci *rrb; | |
449 | struct clp_state_data sd = {fid, ZPCI_FN_STATE_RESERVED}; | |
450 | int rc; | |
451 | ||
98dfd326 | 452 | rrb = clp_alloc_block(GFP_ATOMIC); |
783684f1 SO |
453 | if (!rrb) |
454 | return -ENOMEM; | |
455 | ||
456 | rc = clp_list_pci(rrb, &sd, __clp_get_state); | |
457 | if (!rc) | |
458 | *state = sd.state; | |
57b5918c SO |
459 | |
460 | clp_free_block(rrb); | |
461 | return rc; | |
462 | } | |
988b86e6 MS |
463 | |
464 | static int clp_base_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb) | |
465 | { | |
466 | unsigned long limit = PAGE_SIZE - sizeof(lpcb->request); | |
467 | ||
468 | if (lpcb->request.hdr.len != sizeof(lpcb->request) || | |
469 | lpcb->response.hdr.len > limit) | |
470 | return -EINVAL; | |
471 | return clp_req(lpcb, CLP_LPS_BASE) ? -EOPNOTSUPP : 0; | |
472 | } | |
473 | ||
474 | static int clp_base_command(struct clp_req *req, struct clp_req_hdr *lpcb) | |
475 | { | |
476 | switch (lpcb->cmd) { | |
477 | case 0x0001: /* store logical-processor characteristics */ | |
478 | return clp_base_slpc(req, (void *) lpcb); | |
479 | default: | |
480 | return -EINVAL; | |
481 | } | |
482 | } | |
483 | ||
484 | static int clp_pci_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb) | |
485 | { | |
486 | unsigned long limit = PAGE_SIZE - sizeof(lpcb->request); | |
487 | ||
488 | if (lpcb->request.hdr.len != sizeof(lpcb->request) || | |
489 | lpcb->response.hdr.len > limit) | |
490 | return -EINVAL; | |
491 | return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0; | |
492 | } | |
493 | ||
494 | static int clp_pci_list(struct clp_req *req, struct clp_req_rsp_list_pci *lpcb) | |
495 | { | |
496 | unsigned long limit = PAGE_SIZE - sizeof(lpcb->request); | |
497 | ||
498 | if (lpcb->request.hdr.len != sizeof(lpcb->request) || | |
499 | lpcb->response.hdr.len > limit) | |
500 | return -EINVAL; | |
501 | if (lpcb->request.reserved2 != 0) | |
502 | return -EINVAL; | |
503 | return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0; | |
504 | } | |
505 | ||
506 | static int clp_pci_query(struct clp_req *req, | |
507 | struct clp_req_rsp_query_pci *lpcb) | |
508 | { | |
509 | unsigned long limit = PAGE_SIZE - sizeof(lpcb->request); | |
510 | ||
511 | if (lpcb->request.hdr.len != sizeof(lpcb->request) || | |
512 | lpcb->response.hdr.len > limit) | |
513 | return -EINVAL; | |
514 | if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0) | |
515 | return -EINVAL; | |
516 | return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0; | |
517 | } | |
518 | ||
519 | static int clp_pci_query_grp(struct clp_req *req, | |
520 | struct clp_req_rsp_query_pci_grp *lpcb) | |
521 | { | |
522 | unsigned long limit = PAGE_SIZE - sizeof(lpcb->request); | |
523 | ||
524 | if (lpcb->request.hdr.len != sizeof(lpcb->request) || | |
525 | lpcb->response.hdr.len > limit) | |
526 | return -EINVAL; | |
527 | if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0 || | |
528 | lpcb->request.reserved4 != 0) | |
529 | return -EINVAL; | |
530 | return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0; | |
531 | } | |
532 | ||
533 | static int clp_pci_command(struct clp_req *req, struct clp_req_hdr *lpcb) | |
534 | { | |
535 | switch (lpcb->cmd) { | |
536 | case 0x0001: /* store logical-processor characteristics */ | |
537 | return clp_pci_slpc(req, (void *) lpcb); | |
538 | case 0x0002: /* list PCI functions */ | |
539 | return clp_pci_list(req, (void *) lpcb); | |
540 | case 0x0003: /* query PCI function */ | |
541 | return clp_pci_query(req, (void *) lpcb); | |
542 | case 0x0004: /* query PCI function group */ | |
543 | return clp_pci_query_grp(req, (void *) lpcb); | |
544 | default: | |
545 | return -EINVAL; | |
546 | } | |
547 | } | |
548 | ||
549 | static int clp_normal_command(struct clp_req *req) | |
550 | { | |
551 | struct clp_req_hdr *lpcb; | |
552 | void __user *uptr; | |
553 | int rc; | |
554 | ||
555 | rc = -EINVAL; | |
556 | if (req->lps != 0 && req->lps != 2) | |
557 | goto out; | |
558 | ||
559 | rc = -ENOMEM; | |
560 | lpcb = clp_alloc_block(GFP_KERNEL); | |
561 | if (!lpcb) | |
562 | goto out; | |
563 | ||
564 | rc = -EFAULT; | |
565 | uptr = (void __force __user *)(unsigned long) req->data_p; | |
566 | if (copy_from_user(lpcb, uptr, PAGE_SIZE) != 0) | |
567 | goto out_free; | |
568 | ||
569 | rc = -EINVAL; | |
570 | if (lpcb->fmt != 0 || lpcb->reserved1 != 0 || lpcb->reserved2 != 0) | |
571 | goto out_free; | |
572 | ||
573 | switch (req->lps) { | |
574 | case 0: | |
575 | rc = clp_base_command(req, lpcb); | |
576 | break; | |
577 | case 2: | |
578 | rc = clp_pci_command(req, lpcb); | |
579 | break; | |
580 | } | |
581 | if (rc) | |
582 | goto out_free; | |
583 | ||
584 | rc = -EFAULT; | |
585 | if (copy_to_user(uptr, lpcb, PAGE_SIZE) != 0) | |
586 | goto out_free; | |
587 | ||
588 | rc = 0; | |
589 | ||
590 | out_free: | |
591 | clp_free_block(lpcb); | |
592 | out: | |
593 | return rc; | |
594 | } | |
595 | ||
596 | static int clp_immediate_command(struct clp_req *req) | |
597 | { | |
598 | void __user *uptr; | |
599 | unsigned long ilp; | |
600 | int exists; | |
601 | ||
602 | if (req->cmd > 1 || clp_get_ilp(&ilp) != 0) | |
603 | return -EINVAL; | |
604 | ||
605 | uptr = (void __force __user *)(unsigned long) req->data_p; | |
606 | if (req->cmd == 0) { | |
607 | /* Command code 0: test for a specific processor */ | |
608 | exists = test_bit_inv(req->lps, &ilp); | |
609 | return put_user(exists, (int __user *) uptr); | |
610 | } | |
611 | /* Command code 1: return bit mask of installed processors */ | |
612 | return put_user(ilp, (unsigned long __user *) uptr); | |
613 | } | |
614 | ||
615 | static long clp_misc_ioctl(struct file *filp, unsigned int cmd, | |
616 | unsigned long arg) | |
617 | { | |
618 | struct clp_req req; | |
619 | void __user *argp; | |
620 | ||
621 | if (cmd != CLP_SYNC) | |
622 | return -EINVAL; | |
623 | ||
624 | argp = is_compat_task() ? compat_ptr(arg) : (void __user *) arg; | |
625 | if (copy_from_user(&req, argp, sizeof(req))) | |
626 | return -EFAULT; | |
627 | if (req.r != 0) | |
628 | return -EINVAL; | |
629 | return req.c ? clp_immediate_command(&req) : clp_normal_command(&req); | |
630 | } | |
631 | ||
632 | static int clp_misc_release(struct inode *inode, struct file *filp) | |
633 | { | |
634 | return 0; | |
635 | } | |
636 | ||
637 | static const struct file_operations clp_misc_fops = { | |
638 | .owner = THIS_MODULE, | |
639 | .open = nonseekable_open, | |
640 | .release = clp_misc_release, | |
641 | .unlocked_ioctl = clp_misc_ioctl, | |
642 | .compat_ioctl = clp_misc_ioctl, | |
643 | .llseek = no_llseek, | |
644 | }; | |
645 | ||
646 | static struct miscdevice clp_misc_device = { | |
647 | .minor = MISC_DYNAMIC_MINOR, | |
648 | .name = "clp", | |
649 | .fops = &clp_misc_fops, | |
650 | }; | |
651 | ||
652 | static int __init clp_misc_init(void) | |
653 | { | |
654 | return misc_register(&clp_misc_device); | |
655 | } | |
656 | ||
657 | device_initcall(clp_misc_init); |