// SPDX-License-Identifier: GPL-2.0+ /****************************************************************************** * cxacru.c - driver for USB ADSL modems based on * Conexant AccessRunner chipset * * Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) * Copyright (C) 2007 Simon Arlott * Copyright (C) 2009 Simon Arlott
******************************************************************************/
/* * Credit is due for Josep Comas, who created the original patch to speedtch.c * to support the different padding used by the AccessRunner (now generalized * into usbatm), and the userspace firmware loading utility.
*/
/* commands for interaction with the modem through the control channel before
* firmware is loaded */ enum cxacru_fw_request {
FW_CMD_ERR,
FW_GET_VER,
FW_READ_MEM,
FW_WRITE_MEM,
FW_RMW_MEM,
FW_CHECKSUM_MEM,
FW_GOTO_MEM,
};
/* commands for interaction with the modem through the control channel once
* firmware is loaded */ enum cxacru_cm_request {
CM_REQUEST_UNDEFINED = 0x80,
CM_REQUEST_TEST,
CM_REQUEST_CHIP_GET_MAC_ADDRESS,
CM_REQUEST_CHIP_GET_DP_VERSIONS,
CM_REQUEST_CHIP_ADSL_LINE_START,
CM_REQUEST_CHIP_ADSL_LINE_STOP,
CM_REQUEST_CHIP_ADSL_LINE_GET_STATUS,
CM_REQUEST_CHIP_ADSL_LINE_GET_SPEED,
CM_REQUEST_CARD_INFO_GET,
CM_REQUEST_CARD_DATA_GET,
CM_REQUEST_CARD_DATA_SET,
CM_REQUEST_COMMAND_HW_IO,
CM_REQUEST_INTERFACE_HW_IO,
CM_REQUEST_CARD_SERIAL_DATA_PATH_GET,
CM_REQUEST_CARD_SERIAL_DATA_PATH_SET,
CM_REQUEST_CARD_CONTROLLER_VERSION_GET,
CM_REQUEST_CARD_GET_STATUS,
CM_REQUEST_CARD_GET_MAC_ADDRESS,
CM_REQUEST_CARD_GET_DATA_LINK_STATUS,
CM_REQUEST_MAX,
};
/* commands for interaction with the flash memory * * read: response is the contents of the first 60 bytes of flash memory * write: request contains the 60 bytes of data to write to flash memory * response is the contents of the first 60 bytes of flash memory * * layout: PP PP VV VV MM MM MM MM MM MM ?? ?? SS SS SS SS SS SS SS SS * SS SS SS SS SS SS SS SS 00 00 00 00 00 00 00 00 00 00 00 00 * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * * P: le16 USB Product ID * V: le16 USB Vendor ID * M: be48 MAC Address * S: le16 ASCII Serial Number
*/ enum cxacru_cm_flash {
CM_FLASH_READ = 0xa1,
CM_FLASH_WRITE = 0xa2
};
/* reply codes to the commands above */ enum cxacru_cm_status {
CM_STATUS_UNDEFINED,
CM_STATUS_SUCCESS,
CM_STATUS_ERROR,
CM_STATUS_UNSUPPORTED,
CM_STATUS_UNIMPLEMENTED,
CM_STATUS_PARAMETER_ERROR,
CM_STATUS_DBG_LOOPBACK,
CM_STATUS_MAX,
};
/* indices into CARD_INFO_GET return array */ enum cxacru_info_idx {
CXINF_DOWNSTREAM_RATE,
CXINF_UPSTREAM_RATE,
CXINF_LINK_STATUS,
CXINF_LINE_STATUS,
CXINF_MAC_ADDRESS_HIGH,
CXINF_MAC_ADDRESS_LOW,
CXINF_UPSTREAM_SNR_MARGIN,
CXINF_DOWNSTREAM_SNR_MARGIN,
CXINF_UPSTREAM_ATTENUATION,
CXINF_DOWNSTREAM_ATTENUATION,
CXINF_TRANSMITTER_POWER,
CXINF_UPSTREAM_BITS_PER_FRAME,
CXINF_DOWNSTREAM_BITS_PER_FRAME,
CXINF_STARTUP_ATTEMPTS,
CXINF_UPSTREAM_CRC_ERRORS,
CXINF_DOWNSTREAM_CRC_ERRORS,
CXINF_UPSTREAM_FEC_ERRORS,
CXINF_DOWNSTREAM_FEC_ERRORS,
CXINF_UPSTREAM_HEC_ERRORS,
CXINF_DOWNSTREAM_HEC_ERRORS,
CXINF_LINE_STARTABLE,
CXINF_MODULATION,
CXINF_ADSL_HEADEND,
CXINF_ADSL_HEADEND_ENVIRONMENT,
CXINF_CONTROLLER_VERSION, /* dunno what the missing two mean */
CXINF_MAX = 0x1c,
};
/* * This could use MAC_ADDRESS_HIGH and MAC_ADDRESS_LOW, but since * this data is already in atm_dev there's no point. * * MAC_ADDRESS_HIGH = 0x????5544 * MAC_ADDRESS_LOW = 0x33221100 * Where 00-55 are bytes 0-5 of the MAC.
*/ static ssize_t mac_address_show(struct device *dev, struct device_attribute *attr, char *buf)
{ struct cxacru_data *instance = to_usbatm_driver_data(
to_usb_interface(dev));
if (instance == NULL || instance->usbatm->atm_dev == NULL) return -ENODEV;
value = instance->card_info[CXINF_LINE_STARTABLE]; if (unlikely(value >= ARRAY_SIZE(str))) return sprintf(buf, "%u\n", value); return sprintf(buf, "%s\n", str[value]);
}
static ssize_t adsl_state_store(struct device *dev, struct device_attribute *attr, constchar *buf, size_t count)
{ struct cxacru_data *instance = to_usbatm_driver_data(
to_usb_interface(dev)); int ret; int poll = -1; char str_cmd[8]; int len = strlen(buf);
if (!capable(CAP_NET_ADMIN)) return -EACCES;
ret = sscanf(buf, "%7s", str_cmd); if (ret != 1) return -EINVAL;
ret = 0;
if (instance == NULL) return -ENODEV;
if (mutex_lock_interruptible(&instance->adsl_state_serialize)) return -ERESTARTSYS;
if (!strcmp(str_cmd, "stop") || !strcmp(str_cmd, "restart")) {
ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0, NULL, 0); if (ret < 0) {
atm_err(instance->usbatm, "change adsl state:" " CHIP_ADSL_LINE_STOP returned %d\n", ret);
ret = -EIO;
} else {
ret = len;
poll = CXPOLL_STOPPED;
}
}
/* Line status is only updated every second * and the device appears to only react to * START/STOP every second too. Wait 1.5s to
* be sure that restart will have an effect. */ if (!strcmp(str_cmd, "restart"))
msleep(1500);
if (!strcmp(str_cmd, "start") || !strcmp(str_cmd, "restart")) {
ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); if (ret < 0) {
atm_err(instance->usbatm, "change adsl state:" " CHIP_ADSL_LINE_START returned %d\n", ret);
ret = -EIO;
} else {
ret = len;
poll = CXPOLL_POLLING;
}
}
if (!strcmp(str_cmd, "poll")) {
ret = len;
poll = CXPOLL_POLLING;
}
if (ret == 0) {
ret = -EINVAL;
poll = -1;
}
if (poll == CXPOLL_POLLING) {
mutex_lock(&instance->poll_state_serialize); switch (instance->poll_state) { case CXPOLL_STOPPED: /* start polling */
instance->poll_state = CXPOLL_POLLING; break;
/* send config values when data buffer is full * or no more data
*/ if (pos >= len || num >= CMD_MAX_CONFIG) { char log[CMD_MAX_CONFIG * 12 + 1]; /* %02x=%08x */
data[0] = cpu_to_le32(num);
ret = cxacru_cm(instance, CM_REQUEST_CARD_DATA_SET,
(u8 *) data, 4 + num * 8, NULL, 0); if (ret < 0) {
atm_err(instance->usbatm, "set card data returned %d\n", ret); return -EIO;
}
/* the following three functions are stolen from drivers/usb/core/message.c */ staticvoid cxacru_blocking_completion(struct urb *urb)
{
complete(urb->context);
}
if (actual_length)
*actual_length = urb->actual_length; return urb->status; /* must read status after completion */
}
staticint cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm,
u8 *wdata, int wsize, u8 *rdata, int rsize)
{ int ret, actlen; int offb, offd; constint stride = CMD_PACKET_SIZE - 4;
u8 *wbuf = instance->snd_buf;
u8 *rbuf = instance->rcv_buf; int wbuflen = ((wsize - 1) / stride + 1) * CMD_PACKET_SIZE; int rbuflen = ((rsize - 1) / stride + 1) * CMD_PACKET_SIZE;
if (wbuflen > PAGE_SIZE || rbuflen > PAGE_SIZE) { if (printk_ratelimit())
usb_err(instance->usbatm, "requested transfer size too large (%d, %d)\n",
wbuflen, rbuflen);
ret = -ENOMEM; goto err;
}
mutex_lock(&instance->cm_serialize);
/* submit reading urb before the writing one */
init_completion(&instance->rcv_done);
ret = usb_submit_urb(instance->rcv_urb, GFP_KERNEL); if (ret < 0) { if (printk_ratelimit())
usb_err(instance->usbatm, "submit of read urb for cm %#x failed (%d)\n",
cm, ret); goto fail;
}
instance->snd_urb->transfer_buffer_length = wbuflen;
init_completion(&instance->snd_done);
ret = usb_submit_urb(instance->snd_urb, GFP_KERNEL); if (ret < 0) { if (printk_ratelimit())
usb_err(instance->usbatm, "submit of write urb for cm %#x failed (%d)\n",
cm, ret); goto fail;
}
ret = cxacru_start_wait_urb(instance->snd_urb, &instance->snd_done, NULL); if (ret < 0) { if (printk_ratelimit())
usb_err(instance->usbatm, "send of cm %#x failed (%d)\n", cm, ret); goto fail;
}
ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done, &actlen); if (ret < 0) { if (printk_ratelimit())
usb_err(instance->usbatm, "receive of cm %#x failed (%d)\n", cm, ret); goto fail;
} if (actlen % CMD_PACKET_SIZE || !actlen) { if (printk_ratelimit())
usb_err(instance->usbatm, "invalid response length to cm %#x: %d\n",
cm, actlen);
ret = -EIO; goto fail;
}
/* check the return status and copy the data to the output buffer, if needed */ for (offb = offd = 0; offd < rsize && offb < actlen; offb += CMD_PACKET_SIZE) { if (rbuf[offb] != cm) { if (printk_ratelimit())
usb_err(instance->usbatm, "wrong cm %#x in response to cm %#x\n",
rbuf[offb], cm);
ret = -EIO; goto fail;
} if (rbuf[offb + 1] != CM_STATUS_SUCCESS) { if (printk_ratelimit())
usb_err(instance->usbatm, "response to cm %#x failed: %#x\n",
cm, rbuf[offb + 1]);
ret = -EIO; goto fail;
} if (offd >= rsize) break;
memcpy(rdata + offd, rbuf + offb + 4, min_t(int, stride, rsize - offd));
offd += stride;
}
staticint cxacru_cm_get_array(struct cxacru_data *instance, enum cxacru_cm_request cm,
u32 *data, int size)
{ int ret, len;
__le32 *buf; int offb; unsignedint offd; constint stride = CMD_PACKET_SIZE / (4 * 2) - 1; int buflen = ((size - 1) / stride + 1 + size * 2) * 4;
buf = kmalloc(buflen, GFP_KERNEL); if (!buf) return -ENOMEM;
ret = cxacru_cm(instance, cm, NULL, 0, (u8 *) buf, buflen); if (ret < 0) goto cleanup;
/* len > 0 && len % 4 == 0 guaranteed by cxacru_cm() */
len = ret / 4; for (offb = 0; offb < len; ) { int l = le32_to_cpu(buf[offb++]);
if (l < 0 || l > stride || l > (len - offb) / 2) { if (printk_ratelimit())
usb_err(instance->usbatm, "invalid data length from cm %#x: %d\n",
cm, l);
ret = -EIO; goto cleanup;
} while (l--) {
offd = le32_to_cpu(buf[offb++]); if (offd >= size) { if (printk_ratelimit())
usb_err(instance->usbatm, "wrong index %#x in response to cm %#x\n",
offd, cm);
ret = -EIO; goto cleanup;
}
data[offd] = le32_to_cpu(buf[offb++]);
}
}
ret = 0;
cleanup:
kfree(buf); return ret;
}
staticint cxacru_card_status(struct cxacru_data *instance)
{ int ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
if (ret < 0) { /* firmware not loaded */
usb_dbg(instance->usbatm, "cxacru_adsl_start: CARD_GET_STATUS returned %d\n", ret); return ret;
} return 0;
}
ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX); if (ret < 0) { if (ret != -ESHUTDOWN)
atm_warn(usbatm, "poll status: error %d\n", ret);
mutex_lock(&instance->poll_state_serialize); if (instance->poll_state != CXPOLL_SHUTDOWN) {
instance->poll_state = CXPOLL_STOPPED;
if (ret != -ESHUTDOWN)
atm_warn(usbatm, "polling disabled, set adsl_state" " to 'start' or 'poll' to resume\n");
}
mutex_unlock(&instance->poll_state_serialize); goto reschedule;
}
ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0); if (ret < 0) {
usb_err(usbatm, "modem failed to initialize: %d\n", ret); goto done;
}
done: if (instance->modem_type->boot_rom_patch)
release_firmware(bp);
release_firmware(fw);
ret = cxacru_card_status(instance); if (ret)
usb_dbg(usbatm_instance, "modem initialisation failed\n"); else
usb_dbg(usbatm_instance, "done setting up the modem\n");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.