diff options
Diffstat (limited to 'drivers/usb/atm/cxacru.c')
| -rw-r--r-- | drivers/usb/atm/cxacru.c | 411 |
1 files changed, 400 insertions, 11 deletions
diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index 3dfa3e40e148..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 |
| @@ -34,14 +35,14 @@ | |||
| 34 | #include <linux/errno.h> | 35 | #include <linux/errno.h> |
| 35 | #include <linux/slab.h> | 36 | #include <linux/slab.h> |
| 36 | #include <linux/init.h> | 37 | #include <linux/init.h> |
| 37 | #include <linux/device.h> /* FIXME: linux/firmware.h should include it itself */ | 38 | #include <linux/device.h> |
| 38 | #include <linux/firmware.h> | 39 | #include <linux/firmware.h> |
| 39 | #include <linux/mutex.h> | 40 | #include <linux/mutex.h> |
| 40 | 41 | ||
| 41 | #include "usbatm.h" | 42 | #include "usbatm.h" |
| 42 | 43 | ||
| 43 | #define DRIVER_AUTHOR "Roman Kagan, David Woodhouse, Duncan Sands" | 44 | #define DRIVER_AUTHOR "Roman Kagan, David Woodhouse, Duncan Sands, Simon Arlott" |
| 44 | #define DRIVER_VERSION "0.2" | 45 | #define DRIVER_VERSION "0.3" |
| 45 | #define DRIVER_DESC "Conexant AccessRunner ADSL USB modem driver" | 46 | #define DRIVER_DESC "Conexant AccessRunner ADSL USB modem driver" |
| 46 | 47 | ||
| 47 | static const char cxacru_driver_name[] = "cxacru"; | 48 | static const char cxacru_driver_name[] = "cxacru"; |
| @@ -64,7 +65,7 @@ static const char cxacru_driver_name[] = "cxacru"; | |||
| 64 | #define SDRAM_ENA 0x1 | 65 | #define SDRAM_ENA 0x1 |
| 65 | 66 | ||
| 66 | #define CMD_TIMEOUT 2000 /* msecs */ | 67 | #define CMD_TIMEOUT 2000 /* msecs */ |
| 67 | #define POLL_INTERVAL 5000 /* msecs */ | 68 | #define POLL_INTERVAL 1 /* secs */ |
| 68 | 69 | ||
| 69 | /* commands for interaction with the modem through the control channel before | 70 | /* commands for interaction with the modem through the control channel before |
| 70 | * firmware is loaded */ | 71 | * firmware is loaded */ |
| @@ -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,7 +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; |
| 172 | u32 card_info[CXINF_MAX]; | ||
| 173 | struct mutex poll_state_serialize; | ||
| 174 | int poll_state; | ||
| 162 | 175 | ||
| 163 | /* contol handles */ | 176 | /* contol handles */ |
| 164 | struct mutex cm_serialize; | 177 | struct mutex cm_serialize; |
| @@ -170,6 +183,275 @@ struct cxacru_data { | |||
| 170 | struct completion snd_done; | 183 | struct completion snd_done; |
| 171 | }; | 184 | }; |
| 172 | 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 | |||
| 190 | /* Card info exported through sysfs */ | ||
| 191 | #define CXACRU__ATTR_INIT(_name) \ | ||
| 192 | static DEVICE_ATTR(_name, S_IRUGO, cxacru_sysfs_show_##_name, NULL) | ||
| 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 | |||
| 198 | #define CXACRU_ATTR_INIT(_value, _type, _name) \ | ||
| 199 | static ssize_t cxacru_sysfs_show_##_name(struct device *dev, \ | ||
| 200 | struct device_attribute *attr, char *buf) \ | ||
| 201 | { \ | ||
| 202 | struct usb_interface *intf = to_usb_interface(dev); \ | ||
| 203 | struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); \ | ||
| 204 | struct cxacru_data *instance = usbatm_instance->driver_data; \ | ||
| 205 | return cxacru_sysfs_showattr_##_type(instance->card_info[_value], buf); \ | ||
| 206 | } \ | ||
| 207 | CXACRU__ATTR_INIT(_name) | ||
| 208 | |||
| 209 | #define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name) | ||
| 210 | #define CXACRU_CMD_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) | ||
| 211 | #define CXACRU__ATTR_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) | ||
| 212 | |||
| 213 | #define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name) | ||
| 214 | #define CXACRU_CMD_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) | ||
| 215 | #define CXACRU__ATTR_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) | ||
| 216 | |||
| 217 | static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf) | ||
| 218 | { | ||
| 219 | return snprintf(buf, PAGE_SIZE, "%u\n", value); | ||
| 220 | } | ||
| 221 | |||
| 222 | static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf) | ||
| 223 | { | ||
| 224 | return snprintf(buf, PAGE_SIZE, "%d\n", value); | ||
| 225 | } | ||
| 226 | |||
| 227 | static ssize_t cxacru_sysfs_showattr_dB(s16 value, char *buf) | ||
| 228 | { | ||
| 229 | if (unlikely(value < 0)) { | ||
| 230 | return snprintf(buf, PAGE_SIZE, "%d.%02u\n", | ||
| 231 | value / 100, -value % 100); | ||
| 232 | } else { | ||
| 233 | return snprintf(buf, PAGE_SIZE, "%d.%02u\n", | ||
| 234 | value / 100, value % 100); | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | static ssize_t cxacru_sysfs_showattr_bool(u32 value, char *buf) | ||
| 239 | { | ||
| 240 | switch (value) { | ||
| 241 | case 0: return snprintf(buf, PAGE_SIZE, "no\n"); | ||
| 242 | case 1: return snprintf(buf, PAGE_SIZE, "yes\n"); | ||
| 243 | default: return 0; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf) | ||
| 248 | { | ||
| 249 | switch (value) { | ||
| 250 | case 1: return snprintf(buf, PAGE_SIZE, "not connected\n"); | ||
| 251 | case 2: return snprintf(buf, PAGE_SIZE, "connected\n"); | ||
| 252 | case 3: return snprintf(buf, PAGE_SIZE, "lost\n"); | ||
| 253 | default: return snprintf(buf, PAGE_SIZE, "unknown (%u)\n", value); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf) | ||
| 258 | { | ||
| 259 | switch (value) { | ||
| 260 | case 0: return snprintf(buf, PAGE_SIZE, "down\n"); | ||
| 261 | case 1: return snprintf(buf, PAGE_SIZE, "attempting to activate\n"); | ||
| 262 | case 2: return snprintf(buf, PAGE_SIZE, "training\n"); | ||
| 263 | case 3: return snprintf(buf, PAGE_SIZE, "channel analysis\n"); | ||
| 264 | case 4: return snprintf(buf, PAGE_SIZE, "exchange\n"); | ||
| 265 | case 5: return snprintf(buf, PAGE_SIZE, "up\n"); | ||
| 266 | case 6: return snprintf(buf, PAGE_SIZE, "waiting\n"); | ||
| 267 | case 7: return snprintf(buf, PAGE_SIZE, "initialising\n"); | ||
| 268 | default: return snprintf(buf, PAGE_SIZE, "unknown (%u)\n", value); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf) | ||
| 273 | { | ||
| 274 | switch (value) { | ||
| 275 | case 0: return 0; | ||
| 276 | case 1: return snprintf(buf, PAGE_SIZE, "ANSI T1.413\n"); | ||
| 277 | case 2: return snprintf(buf, PAGE_SIZE, "ITU-T G.992.1 (G.DMT)\n"); | ||
| 278 | case 3: return snprintf(buf, PAGE_SIZE, "ITU-T G.992.2 (G.LITE)\n"); | ||
| 279 | default: return snprintf(buf, PAGE_SIZE, "unknown (%u)\n", value); | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | /* | ||
| 284 | * This could use MAC_ADDRESS_HIGH and MAC_ADDRESS_LOW, but since | ||
| 285 | * this data is already in atm_dev there's no point. | ||
| 286 | * | ||
| 287 | * MAC_ADDRESS_HIGH = 0x????5544 | ||
| 288 | * MAC_ADDRESS_LOW = 0x33221100 | ||
| 289 | * Where 00-55 are bytes 0-5 of the MAC. | ||
| 290 | */ | ||
| 291 | static ssize_t cxacru_sysfs_show_mac_address(struct device *dev, | ||
| 292 | struct device_attribute *attr, char *buf) | ||
| 293 | { | ||
| 294 | struct usb_interface *intf = to_usb_interface(dev); | ||
| 295 | struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); | ||
| 296 | struct atm_dev *atm_dev = usbatm_instance->atm_dev; | ||
| 297 | |||
| 298 | return snprintf(buf, PAGE_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x\n", | ||
| 299 | atm_dev->esi[0], atm_dev->esi[1], atm_dev->esi[2], | ||
| 300 | atm_dev->esi[3], atm_dev->esi[4], atm_dev->esi[5]); | ||
| 301 | } | ||
| 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 | |||
| 416 | /* | ||
| 417 | * All device attributes are included in CXACRU_ALL_FILES | ||
| 418 | * so that the same list can be used multiple times: | ||
| 419 | * INIT (define the device attributes) | ||
| 420 | * CREATE (create all the device files) | ||
| 421 | * REMOVE (remove all the device files) | ||
| 422 | * | ||
| 423 | * With the last two being defined as needed in the functions | ||
| 424 | * they are used in before calling CXACRU_ALL_FILES() | ||
| 425 | */ | ||
| 426 | #define CXACRU_ALL_FILES(_action) \ | ||
| 427 | CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_RATE, u32, downstream_rate); \ | ||
| 428 | CXACRU_ATTR_##_action(CXINF_UPSTREAM_RATE, u32, upstream_rate); \ | ||
| 429 | CXACRU_ATTR_##_action(CXINF_LINK_STATUS, LINK, link_status); \ | ||
| 430 | CXACRU_ATTR_##_action(CXINF_LINE_STATUS, LINE, line_status); \ | ||
| 431 | CXACRU__ATTR_##_action( mac_address); \ | ||
| 432 | CXACRU_ATTR_##_action(CXINF_UPSTREAM_SNR_MARGIN, dB, upstream_snr_margin); \ | ||
| 433 | CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_SNR_MARGIN, dB, downstream_snr_margin); \ | ||
| 434 | CXACRU_ATTR_##_action(CXINF_UPSTREAM_ATTENUATION, dB, upstream_attenuation); \ | ||
| 435 | CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_ATTENUATION, dB, downstream_attenuation); \ | ||
| 436 | CXACRU_ATTR_##_action(CXINF_TRANSMITTER_POWER, s8, transmitter_power); \ | ||
| 437 | CXACRU_ATTR_##_action(CXINF_UPSTREAM_BITS_PER_FRAME, u32, upstream_bits_per_frame); \ | ||
| 438 | CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_BITS_PER_FRAME, u32, downstream_bits_per_frame); \ | ||
| 439 | CXACRU_ATTR_##_action(CXINF_STARTUP_ATTEMPTS, u32, startup_attempts); \ | ||
| 440 | CXACRU_ATTR_##_action(CXINF_UPSTREAM_CRC_ERRORS, u32, upstream_crc_errors); \ | ||
| 441 | CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_CRC_ERRORS, u32, downstream_crc_errors); \ | ||
| 442 | CXACRU_ATTR_##_action(CXINF_UPSTREAM_FEC_ERRORS, u32, upstream_fec_errors); \ | ||
| 443 | CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_FEC_ERRORS, u32, downstream_fec_errors); \ | ||
| 444 | CXACRU_ATTR_##_action(CXINF_UPSTREAM_HEC_ERRORS, u32, upstream_hec_errors); \ | ||
| 445 | CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_HEC_ERRORS, u32, downstream_hec_errors); \ | ||
| 446 | CXACRU_ATTR_##_action(CXINF_LINE_STARTABLE, bool, line_startable); \ | ||
| 447 | CXACRU_ATTR_##_action(CXINF_MODULATION, MODU, modulation); \ | ||
| 448 | CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND, u32, adsl_headend); \ | ||
| 449 | CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT, u32, adsl_headend_environment); \ | ||
| 450 | CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION, u32, adsl_controller_version); \ | ||
| 451 | CXACRU_CMD_##_action( adsl_state); | ||
| 452 | |||
| 453 | CXACRU_ALL_FILES(INIT); | ||
| 454 | |||
| 173 | /* the following three functions are stolen from drivers/usb/core/message.c */ | 455 | /* the following three functions are stolen from drivers/usb/core/message.c */ |
| 174 | static void cxacru_blocking_completion(struct urb *urb) | 456 | static void cxacru_blocking_completion(struct urb *urb) |
| 175 | { | 457 | { |
| @@ -347,8 +629,6 @@ static int cxacru_card_status(struct cxacru_data *instance) | |||
| 347 | return 0; | 629 | return 0; |
| 348 | } | 630 | } |
| 349 | 631 | ||
| 350 | static void cxacru_poll_status(struct work_struct *work); | ||
| 351 | |||
| 352 | static int cxacru_atm_start(struct usbatm_data *usbatm_instance, | 632 | static int cxacru_atm_start(struct usbatm_data *usbatm_instance, |
| 353 | struct atm_dev *atm_dev) | 633 | struct atm_dev *atm_dev) |
| 354 | { | 634 | { |
| @@ -357,6 +637,7 @@ static int cxacru_atm_start(struct usbatm_data *usbatm_instance, | |||
| 357 | struct atm_dev *atm_dev = usbatm_instance->atm_dev; | 637 | struct atm_dev *atm_dev = usbatm_instance->atm_dev; |
| 358 | */ | 638 | */ |
| 359 | int ret; | 639 | int ret; |
| 640 | int start_polling = 1; | ||
| 360 | 641 | ||
| 361 | dbg("cxacru_atm_start"); | 642 | dbg("cxacru_atm_start"); |
| 362 | 643 | ||
| @@ -369,14 +650,35 @@ static int cxacru_atm_start(struct usbatm_data *usbatm_instance, | |||
| 369 | } | 650 | } |
| 370 | 651 | ||
| 371 | /* start ADSL */ | 652 | /* start ADSL */ |
| 653 | mutex_lock(&instance->adsl_state_serialize); | ||
| 372 | 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); |
| 373 | if (ret < 0) { | 655 | if (ret < 0) { |
| 374 | 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); | ||
| 375 | return ret; | 658 | return ret; |
| 376 | } | 659 | } |
| 377 | 660 | ||
| 378 | /* Start status polling */ | 661 | /* Start status polling */ |
| 379 | 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); | ||
| 380 | return 0; | 682 | return 0; |
| 381 | } | 683 | } |
| 382 | 684 | ||
| @@ -387,14 +689,46 @@ static void cxacru_poll_status(struct work_struct *work) | |||
| 387 | u32 buf[CXINF_MAX] = {}; | 689 | u32 buf[CXINF_MAX] = {}; |
| 388 | struct usbatm_data *usbatm = instance->usbatm; | 690 | struct usbatm_data *usbatm = instance->usbatm; |
| 389 | struct atm_dev *atm_dev = usbatm->atm_dev; | 691 | struct atm_dev *atm_dev = usbatm->atm_dev; |
| 692 | int keep_polling = 1; | ||
| 390 | int ret; | 693 | int ret; |
| 391 | 694 | ||
| 392 | 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); |
| 393 | if (ret < 0) { | 696 | if (ret < 0) { |
| 394 | 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); | ||
| 395 | goto reschedule; | 709 | goto reschedule; |
| 396 | } | 710 | } |
| 397 | 711 | ||
| 712 | memcpy(instance->card_info, buf, sizeof(instance->card_info)); | ||
| 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 | |||
| 398 | if (instance->line_status == buf[CXINF_LINE_STATUS]) | 732 | if (instance->line_status == buf[CXINF_LINE_STATUS]) |
| 399 | goto reschedule; | 733 | goto reschedule; |
| 400 | 734 | ||
| @@ -449,7 +783,20 @@ static void cxacru_poll_status(struct work_struct *work) | |||
| 449 | break; | 783 | break; |
| 450 | } | 784 | } |
| 451 | reschedule: | 785 | reschedule: |
| 452 | schedule_delayed_work(&instance->poll_work, msecs_to_jiffies(POLL_INTERVAL)); | 786 | |
| 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)); | ||
| 453 | } | 800 | } |
| 454 | 801 | ||
| 455 | 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, |
| @@ -684,6 +1031,14 @@ static int cxacru_bind(struct usbatm_data *usbatm_instance, | |||
| 684 | 1031 | ||
| 685 | instance->usbatm = usbatm_instance; | 1032 | instance->usbatm = usbatm_instance; |
| 686 | instance->modem_type = (struct cxacru_modem_type *) id->driver_info; | 1033 | instance->modem_type = (struct cxacru_modem_type *) id->driver_info; |
| 1034 | memset(instance->card_info, 0, sizeof(instance->card_info)); | ||
| 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); | ||
| 687 | 1042 | ||
| 688 | instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL); | 1043 | instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL); |
| 689 | if (!instance->rcv_buf) { | 1044 | if (!instance->rcv_buf) { |
| @@ -710,6 +1065,13 @@ static int cxacru_bind(struct usbatm_data *usbatm_instance, | |||
| 710 | goto fail; | 1065 | goto fail; |
| 711 | } | 1066 | } |
| 712 | 1067 | ||
| 1068 | #define CXACRU_DEVICE_CREATE_FILE(_name) \ | ||
| 1069 | ret = device_create_file(&intf->dev, &dev_attr_##_name); \ | ||
| 1070 | if (unlikely(ret)) \ | ||
| 1071 | goto fail_sysfs; | ||
| 1072 | CXACRU_ALL_FILES(CREATE); | ||
| 1073 | #undef CXACRU_DEVICE_CREATE_FILE | ||
| 1074 | |||
| 713 | usb_fill_int_urb(instance->rcv_urb, | 1075 | usb_fill_int_urb(instance->rcv_urb, |
| 714 | usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD), | 1076 | usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD), |
| 715 | instance->rcv_buf, PAGE_SIZE, | 1077 | instance->rcv_buf, PAGE_SIZE, |
| @@ -730,6 +1092,14 @@ static int cxacru_bind(struct usbatm_data *usbatm_instance, | |||
| 730 | 1092 | ||
| 731 | return 0; | 1093 | return 0; |
| 732 | 1094 | ||
| 1095 | fail_sysfs: | ||
| 1096 | dbg("cxacru_bind: device_create_file failed (%d)\n", ret); | ||
| 1097 | |||
| 1098 | #define CXACRU_DEVICE_REMOVE_FILE(_name) \ | ||
| 1099 | device_remove_file(&intf->dev, &dev_attr_##_name); | ||
| 1100 | CXACRU_ALL_FILES(REMOVE); | ||
| 1101 | #undef CXACRU_DEVICE_REVOVE_FILE | ||
| 1102 | |||
| 733 | fail: | 1103 | fail: |
| 734 | free_page((unsigned long) instance->snd_buf); | 1104 | free_page((unsigned long) instance->snd_buf); |
| 735 | free_page((unsigned long) instance->rcv_buf); | 1105 | free_page((unsigned long) instance->rcv_buf); |
| @@ -744,6 +1114,7 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, | |||
| 744 | struct usb_interface *intf) | 1114 | struct usb_interface *intf) |
| 745 | { | 1115 | { |
| 746 | struct cxacru_data *instance = usbatm_instance->driver_data; | 1116 | struct cxacru_data *instance = usbatm_instance->driver_data; |
| 1117 | int is_polling = 1; | ||
| 747 | 1118 | ||
| 748 | dbg("cxacru_unbind entered"); | 1119 | dbg("cxacru_unbind entered"); |
| 749 | 1120 | ||
| @@ -752,8 +1123,20 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, | |||
| 752 | return; | 1123 | return; |
| 753 | } | 1124 | } |
| 754 | 1125 | ||
| 755 | while (!cancel_delayed_work(&instance->poll_work)) | 1126 | mutex_lock(&instance->poll_state_serialize); |
| 756 | 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); | ||
| 757 | 1140 | ||
| 758 | usb_kill_urb(instance->snd_urb); | 1141 | usb_kill_urb(instance->snd_urb); |
| 759 | usb_kill_urb(instance->rcv_urb); | 1142 | usb_kill_urb(instance->rcv_urb); |
| @@ -762,6 +1145,12 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, | |||
| 762 | 1145 | ||
| 763 | free_page((unsigned long) instance->snd_buf); | 1146 | free_page((unsigned long) instance->snd_buf); |
| 764 | free_page((unsigned long) instance->rcv_buf); | 1147 | free_page((unsigned long) instance->rcv_buf); |
| 1148 | |||
| 1149 | #define CXACRU_DEVICE_REMOVE_FILE(_name) \ | ||
| 1150 | device_remove_file(&intf->dev, &dev_attr_##_name); | ||
| 1151 | CXACRU_ALL_FILES(REMOVE); | ||
| 1152 | #undef CXACRU_DEVICE_REVOVE_FILE | ||
| 1153 | |||
| 765 | kfree(instance); | 1154 | kfree(instance); |
| 766 | 1155 | ||
| 767 | usbatm_instance->driver_data = NULL; | 1156 | usbatm_instance->driver_data = NULL; |
