1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for s390 chsc subchannels |
4 | * |
5 | * Copyright IBM Corp. 2008, 2011 |
6 | * |
7 | * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com> |
8 | * |
9 | */ |
10 | |
11 | #include <linux/slab.h> |
12 | #include <linux/compat.h> |
13 | #include <linux/device.h> |
14 | #include <linux/io.h> |
15 | #include <linux/module.h> |
16 | #include <linux/uaccess.h> |
17 | #include <linux/miscdevice.h> |
18 | #include <linux/kernel_stat.h> |
19 | |
20 | #include <asm/cio.h> |
21 | #include <asm/chsc.h> |
22 | #include <asm/isc.h> |
23 | |
24 | #include "cio.h" |
25 | #include "cio_debug.h" |
26 | #include "css.h" |
27 | #include "chsc_sch.h" |
28 | #include "ioasm.h" |
29 | |
30 | static debug_info_t *chsc_debug_msg_id; |
31 | static debug_info_t *chsc_debug_log_id; |
32 | |
33 | static struct chsc_request *on_close_request; |
34 | static struct chsc_async_area *on_close_chsc_area; |
35 | static DEFINE_MUTEX(on_close_mutex); |
36 | |
37 | #define CHSC_MSG(imp, args...) do { \ |
38 | debug_sprintf_event(chsc_debug_msg_id, imp , ##args); \ |
39 | } while (0) |
40 | |
41 | #define CHSC_LOG(imp, txt) do { \ |
42 | debug_text_event(chsc_debug_log_id, imp , txt); \ |
43 | } while (0) |
44 | |
45 | static void CHSC_LOG_HEX(int level, void *data, int length) |
46 | { |
47 | debug_event(chsc_debug_log_id, level, data, length); |
48 | } |
49 | |
50 | MODULE_AUTHOR("IBM Corporation" ); |
51 | MODULE_DESCRIPTION("driver for s390 chsc subchannels" ); |
52 | MODULE_LICENSE("GPL" ); |
53 | |
54 | static void chsc_subchannel_irq(struct subchannel *sch) |
55 | { |
56 | struct chsc_private *private = dev_get_drvdata(dev: &sch->dev); |
57 | struct chsc_request *request = private->request; |
58 | struct irb *irb = this_cpu_ptr(&cio_irb); |
59 | |
60 | CHSC_LOG(4, "irb" ); |
61 | CHSC_LOG_HEX(4, irb, sizeof(*irb)); |
62 | inc_irq_stat(IRQIO_CSC); |
63 | |
64 | /* Copy irb to provided request and set done. */ |
65 | if (!request) { |
66 | CHSC_MSG(0, "Interrupt on sch 0.%x.%04x with no request\n" , |
67 | sch->schid.ssid, sch->schid.sch_no); |
68 | return; |
69 | } |
70 | private->request = NULL; |
71 | memcpy(&request->irb, irb, sizeof(*irb)); |
72 | cio_update_schib(sch); |
73 | complete(&request->completion); |
74 | put_device(dev: &sch->dev); |
75 | } |
76 | |
77 | static int chsc_subchannel_probe(struct subchannel *sch) |
78 | { |
79 | struct chsc_private *private; |
80 | int ret; |
81 | |
82 | CHSC_MSG(6, "Detected chsc subchannel 0.%x.%04x\n" , |
83 | sch->schid.ssid, sch->schid.sch_no); |
84 | sch->isc = CHSC_SCH_ISC; |
85 | private = kzalloc(size: sizeof(*private), GFP_KERNEL); |
86 | if (!private) |
87 | return -ENOMEM; |
88 | dev_set_drvdata(dev: &sch->dev, data: private); |
89 | ret = cio_enable_subchannel(sch, (u32)virt_to_phys(address: sch)); |
90 | if (ret) { |
91 | CHSC_MSG(0, "Failed to enable 0.%x.%04x: %d\n" , |
92 | sch->schid.ssid, sch->schid.sch_no, ret); |
93 | dev_set_drvdata(dev: &sch->dev, NULL); |
94 | kfree(objp: private); |
95 | } |
96 | return ret; |
97 | } |
98 | |
99 | static void chsc_subchannel_remove(struct subchannel *sch) |
100 | { |
101 | struct chsc_private *private; |
102 | |
103 | cio_disable_subchannel(sch); |
104 | private = dev_get_drvdata(dev: &sch->dev); |
105 | dev_set_drvdata(dev: &sch->dev, NULL); |
106 | if (private->request) { |
107 | complete(&private->request->completion); |
108 | put_device(dev: &sch->dev); |
109 | } |
110 | kfree(objp: private); |
111 | } |
112 | |
113 | static void chsc_subchannel_shutdown(struct subchannel *sch) |
114 | { |
115 | cio_disable_subchannel(sch); |
116 | } |
117 | |
118 | static struct css_device_id chsc_subchannel_ids[] = { |
119 | { .match_flags = 0x1, .type =SUBCHANNEL_TYPE_CHSC, }, |
120 | { /* end of list */ }, |
121 | }; |
122 | MODULE_DEVICE_TABLE(css, chsc_subchannel_ids); |
123 | |
124 | static struct css_driver chsc_subchannel_driver = { |
125 | .drv = { |
126 | .owner = THIS_MODULE, |
127 | .name = "chsc_subchannel" , |
128 | }, |
129 | .subchannel_type = chsc_subchannel_ids, |
130 | .irq = chsc_subchannel_irq, |
131 | .probe = chsc_subchannel_probe, |
132 | .remove = chsc_subchannel_remove, |
133 | .shutdown = chsc_subchannel_shutdown, |
134 | }; |
135 | |
136 | static int __init chsc_init_dbfs(void) |
137 | { |
138 | chsc_debug_msg_id = debug_register("chsc_msg" , 8, 1, 4 * sizeof(long)); |
139 | if (!chsc_debug_msg_id) |
140 | goto out; |
141 | debug_register_view(chsc_debug_msg_id, &debug_sprintf_view); |
142 | debug_set_level(chsc_debug_msg_id, 2); |
143 | chsc_debug_log_id = debug_register("chsc_log" , 16, 1, 16); |
144 | if (!chsc_debug_log_id) |
145 | goto out; |
146 | debug_register_view(chsc_debug_log_id, &debug_hex_ascii_view); |
147 | debug_set_level(chsc_debug_log_id, 2); |
148 | return 0; |
149 | out: |
150 | debug_unregister(chsc_debug_msg_id); |
151 | return -ENOMEM; |
152 | } |
153 | |
154 | static void chsc_remove_dbfs(void) |
155 | { |
156 | debug_unregister(chsc_debug_log_id); |
157 | debug_unregister(chsc_debug_msg_id); |
158 | } |
159 | |
160 | static int __init chsc_init_sch_driver(void) |
161 | { |
162 | return css_driver_register(&chsc_subchannel_driver); |
163 | } |
164 | |
165 | static void chsc_cleanup_sch_driver(void) |
166 | { |
167 | css_driver_unregister(&chsc_subchannel_driver); |
168 | } |
169 | |
170 | static DEFINE_SPINLOCK(chsc_lock); |
171 | |
172 | static int chsc_subchannel_match_next_free(struct device *dev, const void *data) |
173 | { |
174 | struct subchannel *sch = to_subchannel(dev); |
175 | |
176 | return sch->schib.pmcw.ena && !scsw_fctl(&sch->schib.scsw); |
177 | } |
178 | |
179 | static struct subchannel *chsc_get_next_subchannel(struct subchannel *sch) |
180 | { |
181 | struct device *dev; |
182 | |
183 | dev = driver_find_device(drv: &chsc_subchannel_driver.drv, |
184 | start: sch ? &sch->dev : NULL, NULL, |
185 | match: chsc_subchannel_match_next_free); |
186 | return dev ? to_subchannel(dev) : NULL; |
187 | } |
188 | |
189 | /** |
190 | * chsc_async() - try to start a chsc request asynchronously |
191 | * @chsc_area: request to be started |
192 | * @request: request structure to associate |
193 | * |
194 | * Tries to start a chsc request on one of the existing chsc subchannels. |
195 | * Returns: |
196 | * %0 if the request was performed synchronously |
197 | * %-EINPROGRESS if the request was successfully started |
198 | * %-EBUSY if all chsc subchannels are busy |
199 | * %-ENODEV if no chsc subchannels are available |
200 | * Context: |
201 | * interrupts disabled, chsc_lock held |
202 | */ |
203 | static int chsc_async(struct chsc_async_area *chsc_area, |
204 | struct chsc_request *request) |
205 | { |
206 | int cc; |
207 | struct chsc_private *private; |
208 | struct subchannel *sch = NULL; |
209 | int ret = -ENODEV; |
210 | char dbf[10]; |
211 | |
212 | chsc_area->header.key = PAGE_DEFAULT_KEY >> 4; |
213 | while ((sch = chsc_get_next_subchannel(sch))) { |
214 | spin_lock(lock: sch->lock); |
215 | private = dev_get_drvdata(dev: &sch->dev); |
216 | if (private->request) { |
217 | spin_unlock(lock: sch->lock); |
218 | ret = -EBUSY; |
219 | continue; |
220 | } |
221 | chsc_area->header.sid = sch->schid; |
222 | CHSC_LOG(2, "schid" ); |
223 | CHSC_LOG_HEX(level: 2, data: &sch->schid, length: sizeof(sch->schid)); |
224 | cc = chsc(chsc_area); |
225 | snprintf(buf: dbf, size: sizeof(dbf), fmt: "cc:%d" , cc); |
226 | CHSC_LOG(2, dbf); |
227 | switch (cc) { |
228 | case 0: |
229 | ret = 0; |
230 | break; |
231 | case 1: |
232 | sch->schib.scsw.cmd.fctl |= SCSW_FCTL_START_FUNC; |
233 | ret = -EINPROGRESS; |
234 | private->request = request; |
235 | break; |
236 | case 2: |
237 | ret = -EBUSY; |
238 | break; |
239 | default: |
240 | ret = -ENODEV; |
241 | } |
242 | spin_unlock(lock: sch->lock); |
243 | CHSC_MSG(2, "chsc on 0.%x.%04x returned cc=%d\n" , |
244 | sch->schid.ssid, sch->schid.sch_no, cc); |
245 | if (ret == -EINPROGRESS) |
246 | return -EINPROGRESS; |
247 | put_device(dev: &sch->dev); |
248 | if (ret == 0) |
249 | return 0; |
250 | } |
251 | return ret; |
252 | } |
253 | |
254 | static void chsc_log_command(void *chsc_area) |
255 | { |
256 | char dbf[10]; |
257 | |
258 | snprintf(buf: dbf, size: sizeof(dbf), fmt: "CHSC:%x" , ((uint16_t *)chsc_area)[1]); |
259 | CHSC_LOG(0, dbf); |
260 | CHSC_LOG_HEX(level: 0, data: chsc_area, length: 32); |
261 | } |
262 | |
263 | static int chsc_examine_irb(struct chsc_request *request) |
264 | { |
265 | int backed_up; |
266 | |
267 | if (!(scsw_stctl(&request->irb.scsw) & SCSW_STCTL_STATUS_PEND)) |
268 | return -EIO; |
269 | backed_up = scsw_cstat(&request->irb.scsw) & SCHN_STAT_CHAIN_CHECK; |
270 | request->irb.scsw.cmd.cstat &= ~SCHN_STAT_CHAIN_CHECK; |
271 | if (scsw_cstat(&request->irb.scsw) == 0) |
272 | return 0; |
273 | if (!backed_up) |
274 | return 0; |
275 | if (scsw_cstat(&request->irb.scsw) & SCHN_STAT_PROG_CHECK) |
276 | return -EIO; |
277 | if (scsw_cstat(&request->irb.scsw) & SCHN_STAT_PROT_CHECK) |
278 | return -EPERM; |
279 | if (scsw_cstat(&request->irb.scsw) & SCHN_STAT_CHN_DATA_CHK) |
280 | return -EAGAIN; |
281 | if (scsw_cstat(&request->irb.scsw) & SCHN_STAT_CHN_CTRL_CHK) |
282 | return -EAGAIN; |
283 | return -EIO; |
284 | } |
285 | |
286 | static int chsc_ioctl_start(void __user *user_area) |
287 | { |
288 | struct chsc_request *request; |
289 | struct chsc_async_area *chsc_area; |
290 | int ret; |
291 | char dbf[10]; |
292 | |
293 | if (!css_general_characteristics.dynio) |
294 | /* It makes no sense to try. */ |
295 | return -EOPNOTSUPP; |
296 | chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL); |
297 | if (!chsc_area) |
298 | return -ENOMEM; |
299 | request = kzalloc(size: sizeof(*request), GFP_KERNEL); |
300 | if (!request) { |
301 | ret = -ENOMEM; |
302 | goto out_free; |
303 | } |
304 | init_completion(x: &request->completion); |
305 | if (copy_from_user(to: chsc_area, from: user_area, PAGE_SIZE)) { |
306 | ret = -EFAULT; |
307 | goto out_free; |
308 | } |
309 | chsc_log_command(chsc_area); |
310 | spin_lock_irq(lock: &chsc_lock); |
311 | ret = chsc_async(chsc_area, request); |
312 | spin_unlock_irq(lock: &chsc_lock); |
313 | if (ret == -EINPROGRESS) { |
314 | wait_for_completion(&request->completion); |
315 | ret = chsc_examine_irb(request); |
316 | } |
317 | /* copy area back to user */ |
318 | if (!ret) |
319 | if (copy_to_user(to: user_area, from: chsc_area, PAGE_SIZE)) |
320 | ret = -EFAULT; |
321 | out_free: |
322 | snprintf(buf: dbf, size: sizeof(dbf), fmt: "ret:%d" , ret); |
323 | CHSC_LOG(0, dbf); |
324 | kfree(objp: request); |
325 | free_page((unsigned long)chsc_area); |
326 | return ret; |
327 | } |
328 | |
329 | static int chsc_ioctl_on_close_set(void __user *user_area) |
330 | { |
331 | char dbf[13]; |
332 | int ret; |
333 | |
334 | mutex_lock(&on_close_mutex); |
335 | if (on_close_chsc_area) { |
336 | ret = -EBUSY; |
337 | goto out_unlock; |
338 | } |
339 | on_close_request = kzalloc(size: sizeof(*on_close_request), GFP_KERNEL); |
340 | if (!on_close_request) { |
341 | ret = -ENOMEM; |
342 | goto out_unlock; |
343 | } |
344 | on_close_chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL); |
345 | if (!on_close_chsc_area) { |
346 | ret = -ENOMEM; |
347 | goto out_free_request; |
348 | } |
349 | if (copy_from_user(to: on_close_chsc_area, from: user_area, PAGE_SIZE)) { |
350 | ret = -EFAULT; |
351 | goto out_free_chsc; |
352 | } |
353 | ret = 0; |
354 | goto out_unlock; |
355 | |
356 | out_free_chsc: |
357 | free_page((unsigned long)on_close_chsc_area); |
358 | on_close_chsc_area = NULL; |
359 | out_free_request: |
360 | kfree(objp: on_close_request); |
361 | on_close_request = NULL; |
362 | out_unlock: |
363 | mutex_unlock(lock: &on_close_mutex); |
364 | snprintf(buf: dbf, size: sizeof(dbf), fmt: "ocsret:%d" , ret); |
365 | CHSC_LOG(0, dbf); |
366 | return ret; |
367 | } |
368 | |
369 | static int chsc_ioctl_on_close_remove(void) |
370 | { |
371 | char dbf[13]; |
372 | int ret; |
373 | |
374 | mutex_lock(&on_close_mutex); |
375 | if (!on_close_chsc_area) { |
376 | ret = -ENOENT; |
377 | goto out_unlock; |
378 | } |
379 | free_page((unsigned long)on_close_chsc_area); |
380 | on_close_chsc_area = NULL; |
381 | kfree(objp: on_close_request); |
382 | on_close_request = NULL; |
383 | ret = 0; |
384 | out_unlock: |
385 | mutex_unlock(lock: &on_close_mutex); |
386 | snprintf(buf: dbf, size: sizeof(dbf), fmt: "ocrret:%d" , ret); |
387 | CHSC_LOG(0, dbf); |
388 | return ret; |
389 | } |
390 | |
391 | static int chsc_ioctl_start_sync(void __user *user_area) |
392 | { |
393 | struct chsc_sync_area *chsc_area; |
394 | int ret, ccode; |
395 | |
396 | chsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
397 | if (!chsc_area) |
398 | return -ENOMEM; |
399 | if (copy_from_user(to: chsc_area, from: user_area, PAGE_SIZE)) { |
400 | ret = -EFAULT; |
401 | goto out_free; |
402 | } |
403 | if (chsc_area->header.code & 0x4000) { |
404 | ret = -EINVAL; |
405 | goto out_free; |
406 | } |
407 | chsc_log_command(chsc_area); |
408 | ccode = chsc(chsc_area); |
409 | if (ccode != 0) { |
410 | ret = -EIO; |
411 | goto out_free; |
412 | } |
413 | if (copy_to_user(to: user_area, from: chsc_area, PAGE_SIZE)) |
414 | ret = -EFAULT; |
415 | else |
416 | ret = 0; |
417 | out_free: |
418 | free_page((unsigned long)chsc_area); |
419 | return ret; |
420 | } |
421 | |
422 | static int chsc_ioctl_info_channel_path(void __user *user_cd) |
423 | { |
424 | struct chsc_chp_cd *cd; |
425 | int ret, ccode; |
426 | struct { |
427 | struct chsc_header request; |
428 | u32 : 2; |
429 | u32 m : 1; |
430 | u32 : 1; |
431 | u32 fmt1 : 4; |
432 | u32 cssid : 8; |
433 | u32 : 8; |
434 | u32 first_chpid : 8; |
435 | u32 : 24; |
436 | u32 last_chpid : 8; |
437 | u32 : 32; |
438 | struct chsc_header response; |
439 | u8 data[PAGE_SIZE - 20]; |
440 | } __attribute__ ((packed)) *scpcd_area; |
441 | |
442 | scpcd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
443 | if (!scpcd_area) |
444 | return -ENOMEM; |
445 | cd = kzalloc(sizeof(*cd), GFP_KERNEL); |
446 | if (!cd) { |
447 | ret = -ENOMEM; |
448 | goto out_free; |
449 | } |
450 | if (copy_from_user(cd, user_cd, sizeof(*cd))) { |
451 | ret = -EFAULT; |
452 | goto out_free; |
453 | } |
454 | scpcd_area->request.length = 0x0010; |
455 | scpcd_area->request.code = 0x0028; |
456 | scpcd_area->m = cd->m; |
457 | scpcd_area->fmt1 = cd->fmt; |
458 | scpcd_area->cssid = cd->chpid.cssid; |
459 | scpcd_area->first_chpid = cd->chpid.id; |
460 | scpcd_area->last_chpid = cd->chpid.id; |
461 | |
462 | ccode = chsc(chsc_area: scpcd_area); |
463 | if (ccode != 0) { |
464 | ret = -EIO; |
465 | goto out_free; |
466 | } |
467 | if (scpcd_area->response.code != 0x0001) { |
468 | ret = -EIO; |
469 | CHSC_MSG(0, "scpcd: response code=%x\n" , |
470 | scpcd_area->response.code); |
471 | goto out_free; |
472 | } |
473 | memcpy(&cd->cpcb, &scpcd_area->response, scpcd_area->response.length); |
474 | if (copy_to_user(user_cd, cd, sizeof(*cd))) |
475 | ret = -EFAULT; |
476 | else |
477 | ret = 0; |
478 | out_free: |
479 | kfree(objp: cd); |
480 | free_page((unsigned long)scpcd_area); |
481 | return ret; |
482 | } |
483 | |
484 | static int chsc_ioctl_info_cu(void __user *user_cd) |
485 | { |
486 | struct chsc_cu_cd *cd; |
487 | int ret, ccode; |
488 | struct { |
489 | struct chsc_header request; |
490 | u32 : 2; |
491 | u32 m : 1; |
492 | u32 : 1; |
493 | u32 fmt1 : 4; |
494 | u32 cssid : 8; |
495 | u32 : 8; |
496 | u32 first_cun : 8; |
497 | u32 : 24; |
498 | u32 last_cun : 8; |
499 | u32 : 32; |
500 | struct chsc_header response; |
501 | u8 data[PAGE_SIZE - 20]; |
502 | } __attribute__ ((packed)) *scucd_area; |
503 | |
504 | scucd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
505 | if (!scucd_area) |
506 | return -ENOMEM; |
507 | cd = kzalloc(sizeof(*cd), GFP_KERNEL); |
508 | if (!cd) { |
509 | ret = -ENOMEM; |
510 | goto out_free; |
511 | } |
512 | if (copy_from_user(cd, user_cd, sizeof(*cd))) { |
513 | ret = -EFAULT; |
514 | goto out_free; |
515 | } |
516 | scucd_area->request.length = 0x0010; |
517 | scucd_area->request.code = 0x0026; |
518 | scucd_area->m = cd->m; |
519 | scucd_area->fmt1 = cd->fmt; |
520 | scucd_area->cssid = cd->cssid; |
521 | scucd_area->first_cun = cd->cun; |
522 | scucd_area->last_cun = cd->cun; |
523 | |
524 | ccode = chsc(chsc_area: scucd_area); |
525 | if (ccode != 0) { |
526 | ret = -EIO; |
527 | goto out_free; |
528 | } |
529 | if (scucd_area->response.code != 0x0001) { |
530 | ret = -EIO; |
531 | CHSC_MSG(0, "scucd: response code=%x\n" , |
532 | scucd_area->response.code); |
533 | goto out_free; |
534 | } |
535 | memcpy(&cd->cucb, &scucd_area->response, scucd_area->response.length); |
536 | if (copy_to_user(user_cd, cd, sizeof(*cd))) |
537 | ret = -EFAULT; |
538 | else |
539 | ret = 0; |
540 | out_free: |
541 | kfree(objp: cd); |
542 | free_page((unsigned long)scucd_area); |
543 | return ret; |
544 | } |
545 | |
546 | static int chsc_ioctl_info_sch_cu(void __user *user_cud) |
547 | { |
548 | struct chsc_sch_cud *cud; |
549 | int ret, ccode; |
550 | struct { |
551 | struct chsc_header request; |
552 | u32 : 2; |
553 | u32 m : 1; |
554 | u32 : 5; |
555 | u32 fmt1 : 4; |
556 | u32 : 2; |
557 | u32 ssid : 2; |
558 | u32 first_sch : 16; |
559 | u32 : 8; |
560 | u32 cssid : 8; |
561 | u32 last_sch : 16; |
562 | u32 : 32; |
563 | struct chsc_header response; |
564 | u8 data[PAGE_SIZE - 20]; |
565 | } __attribute__ ((packed)) *sscud_area; |
566 | |
567 | sscud_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
568 | if (!sscud_area) |
569 | return -ENOMEM; |
570 | cud = kzalloc(sizeof(*cud), GFP_KERNEL); |
571 | if (!cud) { |
572 | ret = -ENOMEM; |
573 | goto out_free; |
574 | } |
575 | if (copy_from_user(cud, user_cud, sizeof(*cud))) { |
576 | ret = -EFAULT; |
577 | goto out_free; |
578 | } |
579 | sscud_area->request.length = 0x0010; |
580 | sscud_area->request.code = 0x0006; |
581 | sscud_area->m = cud->schid.m; |
582 | sscud_area->fmt1 = cud->fmt; |
583 | sscud_area->ssid = cud->schid.ssid; |
584 | sscud_area->first_sch = cud->schid.sch_no; |
585 | sscud_area->cssid = cud->schid.cssid; |
586 | sscud_area->last_sch = cud->schid.sch_no; |
587 | |
588 | ccode = chsc(chsc_area: sscud_area); |
589 | if (ccode != 0) { |
590 | ret = -EIO; |
591 | goto out_free; |
592 | } |
593 | if (sscud_area->response.code != 0x0001) { |
594 | ret = -EIO; |
595 | CHSC_MSG(0, "sscud: response code=%x\n" , |
596 | sscud_area->response.code); |
597 | goto out_free; |
598 | } |
599 | memcpy(&cud->scub, &sscud_area->response, sscud_area->response.length); |
600 | if (copy_to_user(user_cud, cud, sizeof(*cud))) |
601 | ret = -EFAULT; |
602 | else |
603 | ret = 0; |
604 | out_free: |
605 | kfree(objp: cud); |
606 | free_page((unsigned long)sscud_area); |
607 | return ret; |
608 | } |
609 | |
610 | static int chsc_ioctl_conf_info(void __user *user_ci) |
611 | { |
612 | struct chsc_conf_info *ci; |
613 | int ret, ccode; |
614 | struct { |
615 | struct chsc_header request; |
616 | u32 : 2; |
617 | u32 m : 1; |
618 | u32 : 1; |
619 | u32 fmt1 : 4; |
620 | u32 cssid : 8; |
621 | u32 : 6; |
622 | u32 ssid : 2; |
623 | u32 : 8; |
624 | u64 : 64; |
625 | struct chsc_header response; |
626 | u8 data[PAGE_SIZE - 20]; |
627 | } __attribute__ ((packed)) *sci_area; |
628 | |
629 | sci_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
630 | if (!sci_area) |
631 | return -ENOMEM; |
632 | ci = kzalloc(sizeof(*ci), GFP_KERNEL); |
633 | if (!ci) { |
634 | ret = -ENOMEM; |
635 | goto out_free; |
636 | } |
637 | if (copy_from_user(ci, user_ci, sizeof(*ci))) { |
638 | ret = -EFAULT; |
639 | goto out_free; |
640 | } |
641 | sci_area->request.length = 0x0010; |
642 | sci_area->request.code = 0x0012; |
643 | sci_area->m = ci->id.m; |
644 | sci_area->fmt1 = ci->fmt; |
645 | sci_area->cssid = ci->id.cssid; |
646 | sci_area->ssid = ci->id.ssid; |
647 | |
648 | ccode = chsc(chsc_area: sci_area); |
649 | if (ccode != 0) { |
650 | ret = -EIO; |
651 | goto out_free; |
652 | } |
653 | if (sci_area->response.code != 0x0001) { |
654 | ret = -EIO; |
655 | CHSC_MSG(0, "sci: response code=%x\n" , |
656 | sci_area->response.code); |
657 | goto out_free; |
658 | } |
659 | memcpy(&ci->scid, &sci_area->response, sci_area->response.length); |
660 | if (copy_to_user(user_ci, ci, sizeof(*ci))) |
661 | ret = -EFAULT; |
662 | else |
663 | ret = 0; |
664 | out_free: |
665 | kfree(objp: ci); |
666 | free_page((unsigned long)sci_area); |
667 | return ret; |
668 | } |
669 | |
670 | static int chsc_ioctl_conf_comp_list(void __user *user_ccl) |
671 | { |
672 | struct chsc_comp_list *ccl; |
673 | int ret, ccode; |
674 | struct { |
675 | struct chsc_header request; |
676 | u32 ctype : 8; |
677 | u32 : 4; |
678 | u32 fmt : 4; |
679 | u32 : 16; |
680 | u64 : 64; |
681 | u32 list_parm[2]; |
682 | u64 : 64; |
683 | struct chsc_header response; |
684 | u8 data[PAGE_SIZE - 36]; |
685 | } __attribute__ ((packed)) *sccl_area; |
686 | struct { |
687 | u32 m : 1; |
688 | u32 : 31; |
689 | u32 cssid : 8; |
690 | u32 : 16; |
691 | u32 chpid : 8; |
692 | } __attribute__ ((packed)) *chpid_parm; |
693 | struct { |
694 | u32 f_cssid : 8; |
695 | u32 l_cssid : 8; |
696 | u32 : 16; |
697 | u32 res; |
698 | } __attribute__ ((packed)) *cssids_parm; |
699 | |
700 | sccl_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
701 | if (!sccl_area) |
702 | return -ENOMEM; |
703 | ccl = kzalloc(sizeof(*ccl), GFP_KERNEL); |
704 | if (!ccl) { |
705 | ret = -ENOMEM; |
706 | goto out_free; |
707 | } |
708 | if (copy_from_user(ccl, user_ccl, sizeof(*ccl))) { |
709 | ret = -EFAULT; |
710 | goto out_free; |
711 | } |
712 | sccl_area->request.length = 0x0020; |
713 | sccl_area->request.code = 0x0030; |
714 | sccl_area->fmt = ccl->req.fmt; |
715 | sccl_area->ctype = ccl->req.ctype; |
716 | switch (sccl_area->ctype) { |
717 | case CCL_CU_ON_CHP: |
718 | case CCL_IOP_CHP: |
719 | chpid_parm = (void *)&sccl_area->list_parm; |
720 | chpid_parm->m = ccl->req.chpid.m; |
721 | chpid_parm->cssid = ccl->req.chpid.chp.cssid; |
722 | chpid_parm->chpid = ccl->req.chpid.chp.id; |
723 | break; |
724 | case CCL_CSS_IMG: |
725 | case CCL_CSS_IMG_CONF_CHAR: |
726 | cssids_parm = (void *)&sccl_area->list_parm; |
727 | cssids_parm->f_cssid = ccl->req.cssids.f_cssid; |
728 | cssids_parm->l_cssid = ccl->req.cssids.l_cssid; |
729 | break; |
730 | } |
731 | ccode = chsc(chsc_area: sccl_area); |
732 | if (ccode != 0) { |
733 | ret = -EIO; |
734 | goto out_free; |
735 | } |
736 | if (sccl_area->response.code != 0x0001) { |
737 | ret = -EIO; |
738 | CHSC_MSG(0, "sccl: response code=%x\n" , |
739 | sccl_area->response.code); |
740 | goto out_free; |
741 | } |
742 | memcpy(&ccl->sccl, &sccl_area->response, sccl_area->response.length); |
743 | if (copy_to_user(user_ccl, ccl, sizeof(*ccl))) |
744 | ret = -EFAULT; |
745 | else |
746 | ret = 0; |
747 | out_free: |
748 | kfree(objp: ccl); |
749 | free_page((unsigned long)sccl_area); |
750 | return ret; |
751 | } |
752 | |
753 | static int chsc_ioctl_chpd(void __user *user_chpd) |
754 | { |
755 | struct chsc_scpd *scpd_area; |
756 | struct chsc_cpd_info *chpd; |
757 | int ret; |
758 | |
759 | chpd = kzalloc(sizeof(*chpd), GFP_KERNEL); |
760 | scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
761 | if (!scpd_area || !chpd) { |
762 | ret = -ENOMEM; |
763 | goto out_free; |
764 | } |
765 | if (copy_from_user(chpd, user_chpd, sizeof(*chpd))) { |
766 | ret = -EFAULT; |
767 | goto out_free; |
768 | } |
769 | ret = chsc_determine_channel_path_desc(chpid: chpd->chpid, fmt: chpd->fmt, |
770 | rfmt: chpd->rfmt, c: chpd->c, m: chpd->m, |
771 | page: scpd_area); |
772 | if (ret) |
773 | goto out_free; |
774 | memcpy(&chpd->chpdb, &scpd_area->response, scpd_area->response.length); |
775 | if (copy_to_user(user_chpd, chpd, sizeof(*chpd))) |
776 | ret = -EFAULT; |
777 | out_free: |
778 | kfree(objp: chpd); |
779 | free_page((unsigned long)scpd_area); |
780 | return ret; |
781 | } |
782 | |
783 | static int chsc_ioctl_dcal(void __user *user_dcal) |
784 | { |
785 | struct chsc_dcal *dcal; |
786 | int ret, ccode; |
787 | struct { |
788 | struct chsc_header request; |
789 | u32 atype : 8; |
790 | u32 : 4; |
791 | u32 fmt : 4; |
792 | u32 : 16; |
793 | u32 res0[2]; |
794 | u32 list_parm[2]; |
795 | u32 res1[2]; |
796 | struct chsc_header response; |
797 | u8 data[PAGE_SIZE - 36]; |
798 | } __attribute__ ((packed)) *sdcal_area; |
799 | |
800 | sdcal_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
801 | if (!sdcal_area) |
802 | return -ENOMEM; |
803 | dcal = kzalloc(sizeof(*dcal), GFP_KERNEL); |
804 | if (!dcal) { |
805 | ret = -ENOMEM; |
806 | goto out_free; |
807 | } |
808 | if (copy_from_user(dcal, user_dcal, sizeof(*dcal))) { |
809 | ret = -EFAULT; |
810 | goto out_free; |
811 | } |
812 | sdcal_area->request.length = 0x0020; |
813 | sdcal_area->request.code = 0x0034; |
814 | sdcal_area->atype = dcal->req.atype; |
815 | sdcal_area->fmt = dcal->req.fmt; |
816 | memcpy(&sdcal_area->list_parm, &dcal->req.list_parm, |
817 | sizeof(sdcal_area->list_parm)); |
818 | |
819 | ccode = chsc(chsc_area: sdcal_area); |
820 | if (ccode != 0) { |
821 | ret = -EIO; |
822 | goto out_free; |
823 | } |
824 | if (sdcal_area->response.code != 0x0001) { |
825 | ret = -EIO; |
826 | CHSC_MSG(0, "sdcal: response code=%x\n" , |
827 | sdcal_area->response.code); |
828 | goto out_free; |
829 | } |
830 | memcpy(&dcal->sdcal, &sdcal_area->response, |
831 | sdcal_area->response.length); |
832 | if (copy_to_user(user_dcal, dcal, sizeof(*dcal))) |
833 | ret = -EFAULT; |
834 | else |
835 | ret = 0; |
836 | out_free: |
837 | kfree(objp: dcal); |
838 | free_page((unsigned long)sdcal_area); |
839 | return ret; |
840 | } |
841 | |
842 | static long chsc_ioctl(struct file *filp, unsigned int cmd, |
843 | unsigned long arg) |
844 | { |
845 | void __user *argp; |
846 | |
847 | CHSC_MSG(2, "chsc_ioctl called, cmd=%x\n" , cmd); |
848 | if (is_compat_task()) |
849 | argp = compat_ptr(uptr: arg); |
850 | else |
851 | argp = (void __user *)arg; |
852 | switch (cmd) { |
853 | case CHSC_START: |
854 | return chsc_ioctl_start(user_area: argp); |
855 | case CHSC_START_SYNC: |
856 | return chsc_ioctl_start_sync(user_area: argp); |
857 | case CHSC_INFO_CHANNEL_PATH: |
858 | return chsc_ioctl_info_channel_path(user_cd: argp); |
859 | case CHSC_INFO_CU: |
860 | return chsc_ioctl_info_cu(user_cd: argp); |
861 | case CHSC_INFO_SCH_CU: |
862 | return chsc_ioctl_info_sch_cu(user_cud: argp); |
863 | case CHSC_INFO_CI: |
864 | return chsc_ioctl_conf_info(user_ci: argp); |
865 | case CHSC_INFO_CCL: |
866 | return chsc_ioctl_conf_comp_list(user_ccl: argp); |
867 | case CHSC_INFO_CPD: |
868 | return chsc_ioctl_chpd(user_chpd: argp); |
869 | case CHSC_INFO_DCAL: |
870 | return chsc_ioctl_dcal(user_dcal: argp); |
871 | case CHSC_ON_CLOSE_SET: |
872 | return chsc_ioctl_on_close_set(user_area: argp); |
873 | case CHSC_ON_CLOSE_REMOVE: |
874 | return chsc_ioctl_on_close_remove(); |
875 | default: /* unknown ioctl number */ |
876 | return -ENOIOCTLCMD; |
877 | } |
878 | } |
879 | |
880 | static atomic_t chsc_ready_for_use = ATOMIC_INIT(1); |
881 | |
882 | static int chsc_open(struct inode *inode, struct file *file) |
883 | { |
884 | if (!atomic_dec_and_test(v: &chsc_ready_for_use)) { |
885 | atomic_inc(v: &chsc_ready_for_use); |
886 | return -EBUSY; |
887 | } |
888 | return nonseekable_open(inode, filp: file); |
889 | } |
890 | |
891 | static int chsc_release(struct inode *inode, struct file *filp) |
892 | { |
893 | char dbf[13]; |
894 | int ret; |
895 | |
896 | mutex_lock(&on_close_mutex); |
897 | if (!on_close_chsc_area) |
898 | goto out_unlock; |
899 | init_completion(x: &on_close_request->completion); |
900 | CHSC_LOG(0, "on_close" ); |
901 | chsc_log_command(chsc_area: on_close_chsc_area); |
902 | spin_lock_irq(lock: &chsc_lock); |
903 | ret = chsc_async(chsc_area: on_close_chsc_area, request: on_close_request); |
904 | spin_unlock_irq(lock: &chsc_lock); |
905 | if (ret == -EINPROGRESS) { |
906 | wait_for_completion(&on_close_request->completion); |
907 | ret = chsc_examine_irb(request: on_close_request); |
908 | } |
909 | snprintf(buf: dbf, size: sizeof(dbf), fmt: "relret:%d" , ret); |
910 | CHSC_LOG(0, dbf); |
911 | free_page((unsigned long)on_close_chsc_area); |
912 | on_close_chsc_area = NULL; |
913 | kfree(objp: on_close_request); |
914 | on_close_request = NULL; |
915 | out_unlock: |
916 | mutex_unlock(lock: &on_close_mutex); |
917 | atomic_inc(v: &chsc_ready_for_use); |
918 | return 0; |
919 | } |
920 | |
921 | static const struct file_operations chsc_fops = { |
922 | .owner = THIS_MODULE, |
923 | .open = chsc_open, |
924 | .release = chsc_release, |
925 | .unlocked_ioctl = chsc_ioctl, |
926 | .compat_ioctl = chsc_ioctl, |
927 | .llseek = no_llseek, |
928 | }; |
929 | |
930 | static struct miscdevice chsc_misc_device = { |
931 | .minor = MISC_DYNAMIC_MINOR, |
932 | .name = "chsc" , |
933 | .fops = &chsc_fops, |
934 | }; |
935 | |
936 | static int __init chsc_misc_init(void) |
937 | { |
938 | return misc_register(misc: &chsc_misc_device); |
939 | } |
940 | |
941 | static void chsc_misc_cleanup(void) |
942 | { |
943 | misc_deregister(misc: &chsc_misc_device); |
944 | } |
945 | |
946 | static int __init chsc_sch_init(void) |
947 | { |
948 | int ret; |
949 | |
950 | ret = chsc_init_dbfs(); |
951 | if (ret) |
952 | return ret; |
953 | isc_register(CHSC_SCH_ISC); |
954 | ret = chsc_init_sch_driver(); |
955 | if (ret) |
956 | goto out_dbf; |
957 | ret = chsc_misc_init(); |
958 | if (ret) |
959 | goto out_driver; |
960 | return ret; |
961 | out_driver: |
962 | chsc_cleanup_sch_driver(); |
963 | out_dbf: |
964 | isc_unregister(CHSC_SCH_ISC); |
965 | chsc_remove_dbfs(); |
966 | return ret; |
967 | } |
968 | |
969 | static void __exit chsc_sch_exit(void) |
970 | { |
971 | chsc_misc_cleanup(); |
972 | chsc_cleanup_sch_driver(); |
973 | isc_unregister(CHSC_SCH_ISC); |
974 | chsc_remove_dbfs(); |
975 | } |
976 | |
977 | module_init(chsc_sch_init); |
978 | module_exit(chsc_sch_exit); |
979 | |