diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/atm/cxacru.c | 236 |
1 files changed, 227 insertions, 9 deletions
diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index cdcdfed9449d..30b7bfbc985a 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c | |||
@@ -4,6 +4,7 @@ | |||
4 | * | 4 | * |
5 | * Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan | 5 | * Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan |
6 | * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) | 6 | * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) |
7 | * Copyright (C) 2007 Simon Arlott | ||
7 | * | 8 | * |
8 | * This program is free software; you can redistribute it and/or modify it | 9 | * This program is free software; you can redistribute it and/or modify it |
9 | * under the terms of the GNU General Public License as published by the Free | 10 | * under the terms of the GNU General Public License as published by the Free |
@@ -146,6 +147,13 @@ enum cxacru_info_idx { | |||
146 | CXINF_MAX = 0x1c, | 147 | CXINF_MAX = 0x1c, |
147 | }; | 148 | }; |
148 | 149 | ||
150 | enum cxacru_poll_state { | ||
151 | CXPOLL_STOPPING, | ||
152 | CXPOLL_STOPPED, | ||
153 | CXPOLL_POLLING, | ||
154 | CXPOLL_SHUTDOWN | ||
155 | }; | ||
156 | |||
149 | struct cxacru_modem_type { | 157 | struct cxacru_modem_type { |
150 | u32 pll_f_clk; | 158 | u32 pll_f_clk; |
151 | u32 pll_b_clk; | 159 | u32 pll_b_clk; |
@@ -158,8 +166,12 @@ struct cxacru_data { | |||
158 | const struct cxacru_modem_type *modem_type; | 166 | const struct cxacru_modem_type *modem_type; |
159 | 167 | ||
160 | int line_status; | 168 | int line_status; |
169 | struct mutex adsl_state_serialize; | ||
170 | int adsl_status; | ||
161 | struct delayed_work poll_work; | 171 | struct delayed_work poll_work; |
162 | u32 card_info[CXINF_MAX]; | 172 | u32 card_info[CXINF_MAX]; |
173 | struct mutex poll_state_serialize; | ||
174 | int poll_state; | ||
163 | 175 | ||
164 | /* contol handles */ | 176 | /* contol handles */ |
165 | struct mutex cm_serialize; | 177 | struct mutex cm_serialize; |
@@ -171,10 +183,18 @@ struct cxacru_data { | |||
171 | struct completion snd_done; | 183 | struct completion snd_done; |
172 | }; | 184 | }; |
173 | 185 | ||
186 | static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, | ||
187 | u8 *wdata, int wsize, u8 *rdata, int rsize); | ||
188 | static void cxacru_poll_status(struct work_struct *work); | ||
189 | |||
174 | /* Card info exported through sysfs */ | 190 | /* Card info exported through sysfs */ |
175 | #define CXACRU__ATTR_INIT(_name) \ | 191 | #define CXACRU__ATTR_INIT(_name) \ |
176 | static DEVICE_ATTR(_name, S_IRUGO, cxacru_sysfs_show_##_name, NULL) | 192 | static DEVICE_ATTR(_name, S_IRUGO, cxacru_sysfs_show_##_name, NULL) |
177 | 193 | ||
194 | #define CXACRU_CMD_INIT(_name) \ | ||
195 | static DEVICE_ATTR(_name, S_IWUSR | S_IRUGO, \ | ||
196 | cxacru_sysfs_show_##_name, cxacru_sysfs_store_##_name) | ||
197 | |||
178 | #define CXACRU_ATTR_INIT(_value, _type, _name) \ | 198 | #define CXACRU_ATTR_INIT(_value, _type, _name) \ |
179 | static ssize_t cxacru_sysfs_show_##_name(struct device *dev, \ | 199 | static ssize_t cxacru_sysfs_show_##_name(struct device *dev, \ |
180 | struct device_attribute *attr, char *buf) \ | 200 | struct device_attribute *attr, char *buf) \ |
@@ -187,9 +207,11 @@ static ssize_t cxacru_sysfs_show_##_name(struct device *dev, \ | |||
187 | CXACRU__ATTR_INIT(_name) | 207 | CXACRU__ATTR_INIT(_name) |
188 | 208 | ||
189 | #define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name) | 209 | #define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name) |
210 | #define CXACRU_CMD_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) | ||
190 | #define CXACRU__ATTR_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) | 211 | #define CXACRU__ATTR_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) |
191 | 212 | ||
192 | #define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name) | 213 | #define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name) |
214 | #define CXACRU_CMD_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) | ||
193 | #define CXACRU__ATTR_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) | 215 | #define CXACRU__ATTR_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) |
194 | 216 | ||
195 | static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf) | 217 | static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf) |
@@ -278,6 +300,119 @@ static ssize_t cxacru_sysfs_show_mac_address(struct device *dev, | |||
278 | atm_dev->esi[3], atm_dev->esi[4], atm_dev->esi[5]); | 300 | atm_dev->esi[3], atm_dev->esi[4], atm_dev->esi[5]); |
279 | } | 301 | } |
280 | 302 | ||
303 | static ssize_t cxacru_sysfs_show_adsl_state(struct device *dev, | ||
304 | struct device_attribute *attr, char *buf) | ||
305 | { | ||
306 | struct usb_interface *intf = to_usb_interface(dev); | ||
307 | struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); | ||
308 | struct cxacru_data *instance = usbatm_instance->driver_data; | ||
309 | u32 value = instance->card_info[CXINF_LINE_STARTABLE]; | ||
310 | |||
311 | switch (value) { | ||
312 | case 0: return snprintf(buf, PAGE_SIZE, "running\n"); | ||
313 | case 1: return snprintf(buf, PAGE_SIZE, "stopped\n"); | ||
314 | default: return snprintf(buf, PAGE_SIZE, "unknown (%u)\n", value); | ||
315 | } | ||
316 | } | ||
317 | |||
318 | static ssize_t cxacru_sysfs_store_adsl_state(struct device *dev, | ||
319 | struct device_attribute *attr, const char *buf, size_t count) | ||
320 | { | ||
321 | struct usb_interface *intf = to_usb_interface(dev); | ||
322 | struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); | ||
323 | struct cxacru_data *instance = usbatm_instance->driver_data; | ||
324 | int ret; | ||
325 | int poll = -1; | ||
326 | char str_cmd[8]; | ||
327 | int len = strlen(buf); | ||
328 | |||
329 | if (!capable(CAP_NET_ADMIN)) | ||
330 | return -EACCES; | ||
331 | |||
332 | ret = sscanf(buf, "%7s", str_cmd); | ||
333 | if (ret != 1) | ||
334 | return -EINVAL; | ||
335 | ret = 0; | ||
336 | |||
337 | if (mutex_lock_interruptible(&instance->adsl_state_serialize)) | ||
338 | return -ERESTARTSYS; | ||
339 | |||
340 | if (!strcmp(str_cmd, "stop") || !strcmp(str_cmd, "restart")) { | ||
341 | ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0, NULL, 0); | ||
342 | if (ret < 0) { | ||
343 | atm_err(usbatm_instance, "change adsl state:" | ||
344 | " CHIP_ADSL_LINE_STOP returned %d\n", ret); | ||
345 | |||
346 | ret = -EIO; | ||
347 | } else { | ||
348 | ret = len; | ||
349 | poll = CXPOLL_STOPPED; | ||
350 | } | ||
351 | } | ||
352 | |||
353 | /* Line status is only updated every second | ||
354 | * and the device appears to only react to | ||
355 | * START/STOP every second too. Wait 1.5s to | ||
356 | * be sure that restart will have an effect. */ | ||
357 | if (!strcmp(str_cmd, "restart")) | ||
358 | msleep(1500); | ||
359 | |||
360 | if (!strcmp(str_cmd, "start") || !strcmp(str_cmd, "restart")) { | ||
361 | ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); | ||
362 | if (ret < 0) { | ||
363 | atm_err(usbatm_instance, "change adsl state:" | ||
364 | " CHIP_ADSL_LINE_START returned %d\n", ret); | ||
365 | |||
366 | ret = -EIO; | ||
367 | } else { | ||
368 | ret = len; | ||
369 | poll = CXPOLL_POLLING; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | if (!strcmp(str_cmd, "poll")) { | ||
374 | ret = len; | ||
375 | poll = CXPOLL_POLLING; | ||
376 | } | ||
377 | |||
378 | if (ret == 0) { | ||
379 | ret = -EINVAL; | ||
380 | poll = -1; | ||
381 | } | ||
382 | |||
383 | if (poll == CXPOLL_POLLING) { | ||
384 | mutex_lock(&instance->poll_state_serialize); | ||
385 | switch (instance->poll_state) { | ||
386 | case CXPOLL_STOPPED: | ||
387 | /* start polling */ | ||
388 | instance->poll_state = CXPOLL_POLLING; | ||
389 | break; | ||
390 | |||
391 | case CXPOLL_STOPPING: | ||
392 | /* abort stop request */ | ||
393 | instance->poll_state = CXPOLL_POLLING; | ||
394 | case CXPOLL_POLLING: | ||
395 | case CXPOLL_SHUTDOWN: | ||
396 | /* don't start polling */ | ||
397 | poll = -1; | ||
398 | } | ||
399 | mutex_unlock(&instance->poll_state_serialize); | ||
400 | } else if (poll == CXPOLL_STOPPED) { | ||
401 | mutex_lock(&instance->poll_state_serialize); | ||
402 | /* request stop */ | ||
403 | if (instance->poll_state == CXPOLL_POLLING) | ||
404 | instance->poll_state = CXPOLL_STOPPING; | ||
405 | mutex_unlock(&instance->poll_state_serialize); | ||
406 | } | ||
407 | |||
408 | mutex_unlock(&instance->adsl_state_serialize); | ||
409 | |||
410 | if (poll == CXPOLL_POLLING) | ||
411 | cxacru_poll_status(&instance->poll_work.work); | ||
412 | |||
413 | return ret; | ||
414 | } | ||
415 | |||
281 | /* | 416 | /* |
282 | * All device attributes are included in CXACRU_ALL_FILES | 417 | * All device attributes are included in CXACRU_ALL_FILES |
283 | * so that the same list can be used multiple times: | 418 | * so that the same list can be used multiple times: |
@@ -312,7 +447,8 @@ CXACRU_ATTR_##_action(CXINF_LINE_STARTABLE, bool, line_startable); \ | |||
312 | CXACRU_ATTR_##_action(CXINF_MODULATION, MODU, modulation); \ | 447 | CXACRU_ATTR_##_action(CXINF_MODULATION, MODU, modulation); \ |
313 | CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND, u32, adsl_headend); \ | 448 | CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND, u32, adsl_headend); \ |
314 | CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT, u32, adsl_headend_environment); \ | 449 | CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT, u32, adsl_headend_environment); \ |
315 | CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION, u32, adsl_controller_version); | 450 | CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION, u32, adsl_controller_version); \ |
451 | CXACRU_CMD_##_action( adsl_state); | ||
316 | 452 | ||
317 | CXACRU_ALL_FILES(INIT); | 453 | CXACRU_ALL_FILES(INIT); |
318 | 454 | ||
@@ -493,8 +629,6 @@ static int cxacru_card_status(struct cxacru_data *instance) | |||
493 | return 0; | 629 | return 0; |
494 | } | 630 | } |
495 | 631 | ||
496 | static void cxacru_poll_status(struct work_struct *work); | ||
497 | |||
498 | static int cxacru_atm_start(struct usbatm_data *usbatm_instance, | 632 | static int cxacru_atm_start(struct usbatm_data *usbatm_instance, |
499 | struct atm_dev *atm_dev) | 633 | struct atm_dev *atm_dev) |
500 | { | 634 | { |
@@ -503,6 +637,7 @@ static int cxacru_atm_start(struct usbatm_data *usbatm_instance, | |||
503 | struct atm_dev *atm_dev = usbatm_instance->atm_dev; | 637 | struct atm_dev *atm_dev = usbatm_instance->atm_dev; |
504 | */ | 638 | */ |
505 | int ret; | 639 | int ret; |
640 | int start_polling = 1; | ||
506 | 641 | ||
507 | dbg("cxacru_atm_start"); | 642 | dbg("cxacru_atm_start"); |
508 | 643 | ||
@@ -515,14 +650,35 @@ static int cxacru_atm_start(struct usbatm_data *usbatm_instance, | |||
515 | } | 650 | } |
516 | 651 | ||
517 | /* start ADSL */ | 652 | /* start ADSL */ |
653 | mutex_lock(&instance->adsl_state_serialize); | ||
518 | ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); | 654 | ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); |
519 | if (ret < 0) { | 655 | if (ret < 0) { |
520 | atm_err(usbatm_instance, "cxacru_atm_start: CHIP_ADSL_LINE_START returned %d\n", ret); | 656 | atm_err(usbatm_instance, "cxacru_atm_start: CHIP_ADSL_LINE_START returned %d\n", ret); |
657 | mutex_unlock(&instance->adsl_state_serialize); | ||
521 | return ret; | 658 | return ret; |
522 | } | 659 | } |
523 | 660 | ||
524 | /* Start status polling */ | 661 | /* Start status polling */ |
525 | cxacru_poll_status(&instance->poll_work.work); | 662 | mutex_lock(&instance->poll_state_serialize); |
663 | switch (instance->poll_state) { | ||
664 | case CXPOLL_STOPPED: | ||
665 | /* start polling */ | ||
666 | instance->poll_state = CXPOLL_POLLING; | ||
667 | break; | ||
668 | |||
669 | case CXPOLL_STOPPING: | ||
670 | /* abort stop request */ | ||
671 | instance->poll_state = CXPOLL_POLLING; | ||
672 | case CXPOLL_POLLING: | ||
673 | case CXPOLL_SHUTDOWN: | ||
674 | /* don't start polling */ | ||
675 | start_polling = 0; | ||
676 | } | ||
677 | mutex_unlock(&instance->poll_state_serialize); | ||
678 | mutex_unlock(&instance->adsl_state_serialize); | ||
679 | |||
680 | if (start_polling) | ||
681 | cxacru_poll_status(&instance->poll_work.work); | ||
526 | return 0; | 682 | return 0; |
527 | } | 683 | } |
528 | 684 | ||
@@ -533,16 +689,46 @@ static void cxacru_poll_status(struct work_struct *work) | |||
533 | u32 buf[CXINF_MAX] = {}; | 689 | u32 buf[CXINF_MAX] = {}; |
534 | struct usbatm_data *usbatm = instance->usbatm; | 690 | struct usbatm_data *usbatm = instance->usbatm; |
535 | struct atm_dev *atm_dev = usbatm->atm_dev; | 691 | struct atm_dev *atm_dev = usbatm->atm_dev; |
692 | int keep_polling = 1; | ||
536 | int ret; | 693 | int ret; |
537 | 694 | ||
538 | ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX); | 695 | ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX); |
539 | if (ret < 0) { | 696 | if (ret < 0) { |
540 | atm_warn(usbatm, "poll status: error %d\n", ret); | 697 | if (ret != -ESHUTDOWN) |
698 | atm_warn(usbatm, "poll status: error %d\n", ret); | ||
699 | |||
700 | mutex_lock(&instance->poll_state_serialize); | ||
701 | if (instance->poll_state != CXPOLL_SHUTDOWN) { | ||
702 | instance->poll_state = CXPOLL_STOPPED; | ||
703 | |||
704 | if (ret != -ESHUTDOWN) | ||
705 | atm_warn(usbatm, "polling disabled, set adsl_state" | ||
706 | " to 'start' or 'poll' to resume\n"); | ||
707 | } | ||
708 | mutex_unlock(&instance->poll_state_serialize); | ||
541 | goto reschedule; | 709 | goto reschedule; |
542 | } | 710 | } |
543 | 711 | ||
544 | memcpy(instance->card_info, buf, sizeof(instance->card_info)); | 712 | memcpy(instance->card_info, buf, sizeof(instance->card_info)); |
545 | 713 | ||
714 | if (instance->adsl_status != buf[CXINF_LINE_STARTABLE]) { | ||
715 | instance->adsl_status = buf[CXINF_LINE_STARTABLE]; | ||
716 | |||
717 | switch (instance->adsl_status) { | ||
718 | case 0: | ||
719 | atm_printk(KERN_INFO, usbatm, "ADSL state: running\n"); | ||
720 | break; | ||
721 | |||
722 | case 1: | ||
723 | atm_printk(KERN_INFO, usbatm, "ADSL state: stopped\n"); | ||
724 | break; | ||
725 | |||
726 | default: | ||
727 | atm_printk(KERN_INFO, usbatm, "Unknown adsl status %02x\n", instance->adsl_status); | ||
728 | break; | ||
729 | } | ||
730 | } | ||
731 | |||
546 | if (instance->line_status == buf[CXINF_LINE_STATUS]) | 732 | if (instance->line_status == buf[CXINF_LINE_STATUS]) |
547 | goto reschedule; | 733 | goto reschedule; |
548 | 734 | ||
@@ -597,8 +783,20 @@ static void cxacru_poll_status(struct work_struct *work) | |||
597 | break; | 783 | break; |
598 | } | 784 | } |
599 | reschedule: | 785 | reschedule: |
600 | schedule_delayed_work(&instance->poll_work, | 786 | |
601 | round_jiffies_relative(msecs_to_jiffies(POLL_INTERVAL*1000))); | 787 | mutex_lock(&instance->poll_state_serialize); |
788 | if (instance->poll_state == CXPOLL_STOPPING && | ||
789 | instance->adsl_status == 1 && /* stopped */ | ||
790 | instance->line_status == 0) /* down */ | ||
791 | instance->poll_state = CXPOLL_STOPPED; | ||
792 | |||
793 | if (instance->poll_state == CXPOLL_STOPPED) | ||
794 | keep_polling = 0; | ||
795 | mutex_unlock(&instance->poll_state_serialize); | ||
796 | |||
797 | if (keep_polling) | ||
798 | schedule_delayed_work(&instance->poll_work, | ||
799 | round_jiffies_relative(POLL_INTERVAL*HZ)); | ||
602 | } | 800 | } |
603 | 801 | ||
604 | static int cxacru_fw(struct usb_device *usb_dev, enum cxacru_fw_request fw, | 802 | static int cxacru_fw(struct usb_device *usb_dev, enum cxacru_fw_request fw, |
@@ -835,6 +1033,13 @@ static int cxacru_bind(struct usbatm_data *usbatm_instance, | |||
835 | instance->modem_type = (struct cxacru_modem_type *) id->driver_info; | 1033 | instance->modem_type = (struct cxacru_modem_type *) id->driver_info; |
836 | memset(instance->card_info, 0, sizeof(instance->card_info)); | 1034 | memset(instance->card_info, 0, sizeof(instance->card_info)); |
837 | 1035 | ||
1036 | mutex_init(&instance->poll_state_serialize); | ||
1037 | instance->poll_state = CXPOLL_STOPPED; | ||
1038 | instance->line_status = -1; | ||
1039 | instance->adsl_status = -1; | ||
1040 | |||
1041 | mutex_init(&instance->adsl_state_serialize); | ||
1042 | |||
838 | instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL); | 1043 | instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL); |
839 | if (!instance->rcv_buf) { | 1044 | if (!instance->rcv_buf) { |
840 | dbg("cxacru_bind: no memory for rcv_buf"); | 1045 | dbg("cxacru_bind: no memory for rcv_buf"); |
@@ -909,6 +1114,7 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, | |||
909 | struct usb_interface *intf) | 1114 | struct usb_interface *intf) |
910 | { | 1115 | { |
911 | struct cxacru_data *instance = usbatm_instance->driver_data; | 1116 | struct cxacru_data *instance = usbatm_instance->driver_data; |
1117 | int is_polling = 1; | ||
912 | 1118 | ||
913 | dbg("cxacru_unbind entered"); | 1119 | dbg("cxacru_unbind entered"); |
914 | 1120 | ||
@@ -917,8 +1123,20 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, | |||
917 | return; | 1123 | return; |
918 | } | 1124 | } |
919 | 1125 | ||
920 | while (!cancel_delayed_work(&instance->poll_work)) | 1126 | mutex_lock(&instance->poll_state_serialize); |
921 | flush_scheduled_work(); | 1127 | BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN); |
1128 | |||
1129 | /* ensure that status polling continues unless | ||
1130 | * it has already stopped */ | ||
1131 | if (instance->poll_state == CXPOLL_STOPPED) | ||
1132 | is_polling = 0; | ||
1133 | |||
1134 | /* stop polling from being stopped or started */ | ||
1135 | instance->poll_state = CXPOLL_SHUTDOWN; | ||
1136 | mutex_unlock(&instance->poll_state_serialize); | ||
1137 | |||
1138 | if (is_polling) | ||
1139 | cancel_rearming_delayed_work(&instance->poll_work); | ||
922 | 1140 | ||
923 | usb_kill_urb(instance->snd_urb); | 1141 | usb_kill_urb(instance->snd_urb); |
924 | usb_kill_urb(instance->rcv_urb); | 1142 | usb_kill_urb(instance->rcv_urb); |