blob: 64852e589ee8899282c243d993bdfa466af86f20 [file] [log] [blame]
/*
* Copyright (c) 2011 Broadcom Corporation
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "usb.h"
#include <ddk/protocol/usb.h>
#include <usb/usb.h>
#include <usb/usb-request.h>
#include <lib/sync/completion.h>
#include <zircon/status.h>
#include <threads.h>
#include "bcdc.h"
#include "brcm_hw_ids.h"
#include "brcmu_utils.h"
#include "brcmu_wifi.h"
#include "bus.h"
#include "common.h"
#include "core.h"
#include "debug.h"
#include "device.h"
#include "firmware.h"
#include "linuxisms.h"
#include "netbuf.h"
#define IOCTL_RESP_TIMEOUT_MSEC (2000)
#define BRCMF_USB_RESET_GETVER_SPINWAIT_MSEC 100 /* in unit of ms */
#define BRCMF_USB_RESET_GETVER_LOOP_CNT 10
#define BRCMF_POSTBOOT_ID 0xA123 /* ID to detect if dongle has boot up */
#define BRCMF_USB_NRXQ 50
#define BRCMF_USB_NTXQ 50
#define BRCMF_USB_CBCTL_WRITE 0
#define BRCMF_USB_CBCTL_READ 1
#define BRCMF_USB_MAX_PKT_SIZE 1600
BRCMF_FW_DEF(43143, "brcmfmac43143.bin")
BRCMF_FW_DEF(43236B, "brcmfmac43236b.bin")
BRCMF_FW_DEF(43242A, "brcmfmac43242a.bin")
BRCMF_FW_DEF(43569, "brcmfmac43569.bin")
BRCMF_FW_DEF(4373, "brcmfmac4373.bin")
static struct brcmf_firmware_mapping brcmf_usb_fwnames[] = {
BRCMF_FW_ENTRY(BRCM_CC_43143_CHIP_ID, 0xFFFFFFFF, 43143),
BRCMF_FW_ENTRY(BRCM_CC_43235_CHIP_ID, 0x00000008, 43236B),
BRCMF_FW_ENTRY(BRCM_CC_43236_CHIP_ID, 0x00000008, 43236B),
BRCMF_FW_ENTRY(BRCM_CC_43238_CHIP_ID, 0x00000008, 43236B),
BRCMF_FW_ENTRY(BRCM_CC_43242_CHIP_ID, 0xFFFFFFFF, 43242A),
BRCMF_FW_ENTRY(BRCM_CC_43566_CHIP_ID, 0xFFFFFFFF, 43569),
BRCMF_FW_ENTRY(BRCM_CC_43569_CHIP_ID, 0xFFFFFFFF, 43569),
BRCMF_FW_ENTRY(CY_CC_4373_CHIP_ID, 0xFFFFFFFF, 4373)
};
#define TRX_MAGIC 0x30524448 /* "HDR0" */
#define TRX_MAX_OFFSET 3 /* Max number of file offsets */
#define TRX_UNCOMP_IMAGE 0x20 /* Trx holds uncompressed img */
#define TRX_RDL_CHUNK 1500 /* size of each dl transfer */
#define TRX_OFFSETS_DLFWLEN_IDX 0
/* Control messages: bRequest values */
#define DL_GETSTATE 0 /* returns the rdl_state_t struct */
#define DL_CHECK_CRC 1 /* currently unused */
#define DL_GO 2 /* execute downloaded image */
#define DL_START 3 /* initialize dl state */
#define DL_REBOOT 4 /* reboot the device in 2 seconds */
#define DL_GETVER 5 /* returns the bootrom_id_t struct */
/* execute the downloaded code and set reset
* event to occur in 2 seconds. It is the
* responsibility of the downloaded code to
* clear this event
*/
#define DL_GO_PROTECTED 6
#define DL_EXEC 7 /* jump to a supplied address */
/* To support single enum on dongle
* - Not used by bootloader
*/
#define DL_RESETCFG 8
/* Potentially defer the response to setup
* if resp unavailable
*/
#define DL_DEFER_RESP_OK 9
/* states */
#define DL_WAITING 0 /* waiting to rx first pkt */
#define DL_READY 1 /* hdr was good, waiting for more of the compressed image */
#define DL_BAD_HDR 2 /* hdr was corrupted */
#define DL_BAD_CRC 3 /* compressed image was corrupted */
#define DL_RUNNABLE 4 /* download was successful,waiting for go cmd */
#define DL_START_FAIL 5 /* failed to initialize correctly */
#define DL_NVRAM_TOOBIG 6 /* host specified nvram data exceeds DL_NVRAM value */
#define DL_IMAGE_TOOBIG 7 /* firmware image too big */
struct trx_header_le {
uint32_t magic; /* "HDR0" */
uint32_t len; /* Length of file including header */
uint32_t crc32; /* CRC from flag_version to end of file */
uint32_t flag_version; /* 0:15 flags, 16:31 version */
uint32_t offsets[TRX_MAX_OFFSET]; /* Offsets of partitions from start of
* header
*/
};
struct rdl_state_le {
uint32_t state;
uint32_t bytes;
};
struct bootrom_id_le {
uint32_t chip; /* Chip id */
uint32_t chiprev; /* Chip rev */
uint32_t ramsize; /* Size of RAM */
uint32_t remapbase; /* Current remap base address */
uint32_t boardtype; /* Type of board */
uint32_t boardrev; /* Board revision */
};
struct brcmf_usb_image {
struct list_node list;
int8_t* fwname;
uint8_t* image;
int image_len;
};
struct brcmf_usbdev_info {
struct brcmf_usbdev bus_pub; /* MUST BE FIRST */
usb_protocol_t protocol;
//spinlock_t qlock;
struct list_node rx_freeq;
struct list_node rx_postq;
struct list_node tx_freeq;
struct list_node tx_postq;
uint8_t rx_endpoint, tx_endpoint;
int rx_low_watermark;
int tx_low_watermark;
int tx_high_watermark;
int tx_freecount;
bool tx_flowblock;
//spinlock_t tx_flowblock_lock;
struct brcmf_usbreq* tx_reqs;
struct brcmf_usbreq* rx_reqs;
char fw_name[BRCMF_FW_NAME_LEN];
const uint8_t* image; /* buffer for combine fw and nvram */
int image_len;
struct brcmf_usb_device* usbdev;
struct brcmf_device* dev;
mtx_t dev_init_lock;
struct brcmf_urb* ctl_urb; /* URB for control endpoint */
usb_setup_t ctl_write;
usb_setup_t ctl_read;
uint32_t ctl_urb_actual_length;
int ctl_urb_status;
sync_completion_t ioctl_resp_wait;
atomic_ulong ctl_op;
uint8_t ifnum;
struct brcmf_urb* bulk_urb; /* used for FW download */
bool wowl_enabled;
struct brcmf_mp_device* settings;
};
typedef void (*usb_complete_cb)(void* ctx, usb_request_t* req);
// Linux->ZX glue functions start here
struct brcmf_urb* brcmf_usb_allocate_urb(usb_protocol_t* usb, size_t parent_req_size) {
zx_status_t result;
struct brcmf_urb* urb;
urb = malloc(sizeof(*urb));
if (urb == NULL) {
return NULL;
}
result = usb_request_alloc(&urb->zxurb, USB_MAX_TRANSFER_SIZE, 0, parent_req_size);
if (result != ZX_OK) {
free(urb);
return NULL;
}
if (urb->zxurb == NULL) {
brcmf_dbg(TEMP, " * * OOPS! OK result with NULL zxurb!!!");
assert(0);
}
return urb;
}
void brcmf_usb_free_urb(struct brcmf_urb* urb) {
if (urb == NULL) {
return;
}
if (urb->devinfo == NULL) {
return;
}
usb_request_release(urb->zxurb);
free(urb);
}
static void brcmf_usb_init_urb(struct brcmf_urb* urb, struct brcmf_usbdev_info* devinfo,
void* buf, uint16_t size, bool zero_packet,
usb_complete_cb complete, void* context, bool out,
uint8_t ep_address) {
if (urb == NULL) {
brcmf_err("NULL URB\n");
assert(0);
return;
}
usb_request_t* zxurb = urb->zxurb;
if (zxurb == NULL) {
brcmf_err("NULL ZX_URB, urb %p\n", urb);
assert(0);
return;
}
urb->context = context;
urb->devinfo = devinfo;
zxurb->header.length = size;
zxurb->header.ep_address = ep_address;
zxurb->header.send_zlp = zero_packet;
if (out) {
if (size > 0) {
usb_request_copy_to(zxurb, buf, size, 0);
}
urb->recv_buffer = 0;
urb->desired_length = 0;
} else {
// Code in usb.c:brcmf_usb_*_complete() uses these.
urb->recv_buffer = buf;
urb->desired_length = size;
}
}
static void brcmf_usb_init_control_urb(struct brcmf_urb* urb, struct brcmf_usbdev_info* devinfo,
usb_setup_t* ctl_config,
void* buf, uint16_t size, usb_complete_cb complete,
void* context) {
brcmf_usb_init_urb(urb, devinfo, buf, size, false, complete, context,
(ctl_config->bmRequestType & USB_DIR_MASK) == USB_DIR_OUT, 0);
memcpy(&urb->zxurb->setup, ctl_config, sizeof(urb->zxurb->setup));
}
static void brcmf_usb_init_bulk_urb(struct brcmf_urb* urb, struct brcmf_usbdev_info* devinfo,
uint8_t ep_address, void* buf, uint16_t size, bool zero_packet,
usb_complete_cb complete, void* context) {
brcmf_usb_init_urb(urb, devinfo, buf, size, zero_packet, complete, context,
(ep_address & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_OUT, ep_address);
urb->zxurb->setup.wLength = 0xdead;
}
zx_status_t brcmf_usb_queue_urb(struct brcmf_urb* urb, usb_complete_cb cb) {
usb_protocol_t* usb_proto = &urb->devinfo->protocol;
usb_request_complete_t complete = {
.callback = cb,
.ctx = urb,
};
usb_request_queue(usb_proto, urb->zxurb, &complete);
return ZX_OK;
}
// Linux->ZX glue functions end here
static void brcmf_usb_rx_refill(struct brcmf_usbdev_info* devinfo, struct brcmf_usbreq* req);
static struct brcmf_usbdev* brcmf_usb_get_buspub(struct brcmf_device* dev) {
struct brcmf_bus* bus_if = dev_to_bus(dev);
return bus_if->bus_priv.usb;
}
static struct brcmf_usbdev_info* brcmf_usb_get_businfo(struct brcmf_device* dev) {
return brcmf_usb_get_buspub(dev)->devinfo;
}
static zx_status_t brcmf_usb_ioctl_resp_wait(struct brcmf_usbdev_info* devinfo) {
return sync_completion_wait(&devinfo->ioctl_resp_wait, ZX_MSEC(IOCTL_RESP_TIMEOUT_MSEC));
}
static void brcmf_usb_ioctl_resp_wake(struct brcmf_usbdev_info* devinfo) {
sync_completion_signal(&devinfo->ioctl_resp_wait);
}
static void brcmf_usb_ctl_complete(struct brcmf_usbdev_info* devinfo, int type, int status) {
brcmf_dbg(USB, "Enter, status=%d\n", status);
if (unlikely(devinfo == NULL)) {
return;
}
if (type == BRCMF_USB_CBCTL_READ) {
if (status == 0) {
devinfo->bus_pub.stats.rx_ctlpkts++;
} else {
devinfo->bus_pub.stats.rx_ctlerrs++;
}
} else if (type == BRCMF_USB_CBCTL_WRITE) {
if (status == 0) {
devinfo->bus_pub.stats.tx_ctlpkts++;
} else {
devinfo->bus_pub.stats.tx_ctlerrs++;
}
}
devinfo->ctl_urb_status = status;
brcmf_usb_ioctl_resp_wake(devinfo);
}
static void brcmf_usb_ctlread_complete(void* ctx, usb_request_t* zxurb) {
struct brcmf_urb* urb = ctx;
struct brcmf_usbdev_info* devinfo = (struct brcmf_usbdev_info*)urb->context;
brcmf_dbg(USB, "Enter\n");
assert(zxurb == urb->zxurb);
urb->actual_length = zxurb->response.actual;
urb->status = zxurb->response.status;
if (urb->status == ZX_OK && urb->recv_buffer != NULL && urb->actual_length > 0) {
if (urb->actual_length > urb->desired_length) {
brcmf_err("USB read gave more data than requested: %d > %d", urb->actual_length,
urb->desired_length);
urb->actual_length = urb->desired_length;
}
// TODO(cphoenix): At least some transfers malloc a buffer and copy to/from it, which
// is unnecessary given we're in userspace and already copying here. Clean that up.
usb_request_copy_from(zxurb, urb->recv_buffer, urb->actual_length, 0);
}
pthread_mutex_lock(&irq_callback_lock);
devinfo->ctl_urb_actual_length = urb->actual_length;
brcmf_usb_ctl_complete(devinfo, BRCMF_USB_CBCTL_READ, urb->status);
pthread_mutex_unlock(&irq_callback_lock);
}
static void brcmf_usb_ctlwrite_complete(void* ctx, usb_request_t* zxurb) {
struct brcmf_urb* urb = ctx;
struct brcmf_usbdev_info* devinfo = (struct brcmf_usbdev_info*)urb->context;
brcmf_dbg(USB, "Enter\n");
assert(zxurb == urb->zxurb);
urb->actual_length = zxurb->response.actual;
urb->status = zxurb->response.status;
pthread_mutex_lock(&irq_callback_lock);
brcmf_usb_ctl_complete(devinfo, BRCMF_USB_CBCTL_WRITE, urb->status);
pthread_mutex_unlock(&irq_callback_lock);
}
static zx_status_t brcmf_usb_send_ctl(struct brcmf_usbdev_info* devinfo, uint8_t* buf, int len) {
zx_status_t ret;
uint16_t size;
brcmf_dbg(USB, "Enter\n");
if (devinfo == NULL || buf == NULL || len == 0 || devinfo->ctl_urb == NULL) {
return ZX_ERR_INVALID_ARGS;
}
size = len;
devinfo->ctl_write.wLength = size;
devinfo->ctl_urb_status = 0;
devinfo->ctl_urb_actual_length = 0;
brcmf_usb_init_control_urb(devinfo->ctl_urb, devinfo, &devinfo->ctl_write, buf, size,
brcmf_usb_ctlwrite_complete, devinfo);
ret = brcmf_usb_queue_urb(devinfo->ctl_urb, brcmf_usb_ctlwrite_complete);
if (ret != ZX_OK) {
brcmf_err("usb_queue_urb failed %d\n", ret);
}
return ret;
}
static zx_status_t brcmf_usb_recv_ctl(struct brcmf_usbdev_info* devinfo, uint8_t* buf, int len) {
zx_status_t ret;
uint16_t size;
brcmf_dbg(USB, "Enter\n");
if ((devinfo == NULL) || (buf == NULL) || (len == 0) || (devinfo->ctl_urb == NULL)) {
return ZX_ERR_INVALID_ARGS;
}
size = len;
devinfo->ctl_read.wLength = size;
devinfo->ctl_read.bmRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
devinfo->ctl_read.bRequest = 1;
brcmf_usb_init_control_urb(devinfo->ctl_urb, devinfo, &devinfo->ctl_read, buf, size,
brcmf_usb_ctlread_complete, devinfo);
ret = brcmf_usb_queue_urb(devinfo->ctl_urb, brcmf_usb_ctlread_complete);
if (ret != ZX_OK) {
brcmf_err("usb_queue_urb failed %d\n", ret);
}
return ret;
}
static zx_status_t brcmf_usb_tx_ctlpkt(struct brcmf_device* dev, uint8_t* buf, uint32_t len) {
zx_status_t err = ZX_OK;
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(dev);
brcmf_dbg(USB, "Enter");
if (devinfo->bus_pub.state != BRCMFMAC_USB_STATE_UP) {
return ZX_ERR_IO;
}
if (brcmf_test_and_set_bit_in_array(0, &devinfo->ctl_op)) {
return ZX_ERR_IO;
}
sync_completion_reset(&devinfo->ioctl_resp_wait);
err = brcmf_usb_send_ctl(devinfo, buf, len);
if (err != ZX_OK) {
brcmf_err("fail %d bytes: %d\n", err, len);
brcmf_clear_bit_in_array(0, &devinfo->ctl_op);
return err;
}
err = brcmf_usb_ioctl_resp_wait(devinfo);
brcmf_clear_bit_in_array(0, &devinfo->ctl_op);
if (err != ZX_OK) {
brcmf_err("Txctl wait timed out\n");
err = ZX_ERR_IO;
}
return err;
}
static zx_status_t brcmf_usb_rx_ctlpkt(struct brcmf_device* dev, uint8_t* buf, uint32_t len,
int* urb_len_out) {
zx_status_t err = ZX_OK;
bool timeout;
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(dev);
brcmf_dbg(USB, "Enter\n");
if (devinfo->bus_pub.state != BRCMFMAC_USB_STATE_UP) {
return ZX_ERR_IO;
}
if (brcmf_test_and_set_bit_in_array(0, &devinfo->ctl_op)) {
return ZX_ERR_IO;
}
sync_completion_reset(&devinfo->ioctl_resp_wait);
err = brcmf_usb_recv_ctl(devinfo, buf, len);
if (err != ZX_OK) {
brcmf_err("fail %d bytes: %d\n", err, len);
brcmf_clear_bit_in_array(0, &devinfo->ctl_op);
return err;
}
timeout = brcmf_usb_ioctl_resp_wait(devinfo) != ZX_OK;
err = devinfo->ctl_urb_status;
brcmf_clear_bit_in_array(0, &devinfo->ctl_op);
if (timeout) {
brcmf_err("rxctl wait timed out\n");
err = ZX_ERR_IO;
}
if (err == ZX_OK) {
if (urb_len_out) {
*urb_len_out = devinfo->ctl_urb_actual_length;
}
return ZX_OK;
} else {
return err;
}
}
static struct brcmf_usbreq* brcmf_usb_deq(struct brcmf_usbdev_info* devinfo, struct list_node* q,
int* counter) {
struct brcmf_usbreq* req;
//spin_lock_irqsave(&devinfo->qlock, flags);
pthread_mutex_lock(&irq_callback_lock);
if (list_is_empty(q)) {
//spin_unlock_irqrestore(&devinfo->qlock, flags);
pthread_mutex_unlock(&irq_callback_lock);
return NULL;
}
req = containerof(q->next, struct brcmf_usbreq, list);
struct list_node* next = q->next;
list_delete(next);
list_initialize(next);
if (counter) {
(*counter)--;
}
//spin_unlock_irqrestore(&devinfo->qlock, flags);
pthread_mutex_unlock(&irq_callback_lock);
return req;
}
static void brcmf_usb_enq(struct brcmf_usbdev_info* devinfo, struct list_node* q,
struct brcmf_usbreq* req, int* counter) {
//spin_lock_irqsave(&devinfo->qlock, flags);
pthread_mutex_lock(&irq_callback_lock);
list_add_tail(q, &req->list);
if (counter) {
(*counter)++;
}
//spin_unlock_irqrestore(&devinfo->qlock, flags);
pthread_mutex_unlock(&irq_callback_lock);
}
static struct brcmf_usbreq* brcmf_usbdev_qinit(struct brcmf_usbdev_info* devinfo,
struct list_node* q, int qsize) {
int i;
struct brcmf_usbreq* req;
struct brcmf_usbreq* reqs;
reqs = calloc(qsize, sizeof(struct brcmf_usbreq));
if (reqs == NULL) {
return NULL;
}
req = reqs;
for (i = 0; i < qsize; i++) {
req->urb = brcmf_usb_allocate_urb(&devinfo->protocol, devinfo->usbdev->parent_req_size);
if (!req->urb) {
goto fail;
}
list_add_tail(q, &req->list);
req++;
}
return reqs;
fail:
brcmf_err("fail!\n");
while (!list_is_empty(q)) {
req = containerof(q->next, struct brcmf_usbreq, list);
if (req) {
brcmf_usb_free_urb(req->urb);
}
list_delete(q->next);
}
free(reqs);
return NULL;
}
static void brcmf_usb_free_q(struct brcmf_usbdev_info* devinfo, struct list_node* q, bool pending) {
struct brcmf_usbreq* req;
struct brcmf_usbreq* next;
list_for_every_entry_safe(q, req, next, struct brcmf_usbreq, list) {
if (!req->urb) {
brcmf_err("bad req\n");
break; // TODO(cphoenix): Should this be a "continue"?
}
if (pending) {
usb_cancel_all(&devinfo->protocol, req->urb->zxurb->header.ep_address);
} else {
brcmf_usb_free_urb(req->urb);
list_delete(&req->list);
list_initialize(&req->list);
}
}
}
static void brcmf_usb_del_fromq(struct brcmf_usbdev_info* devinfo, struct brcmf_usbreq* req) {
//spin_lock_irqsave(&devinfo->qlock, flags);
pthread_mutex_lock(&irq_callback_lock);
list_delete(&req->list);
list_initialize(&req->list);
//spin_unlock_irqrestore(&devinfo->qlock, flags);
pthread_mutex_unlock(&irq_callback_lock);
}
static void brcmf_usb_tx_complete(void* ctx, usb_request_t* zxurb) {
struct brcmf_urb* urb = ctx;
struct brcmf_usbreq* req = (struct brcmf_usbreq*)urb->context;
struct brcmf_usbdev_info* devinfo = req->devinfo;
urb->actual_length = zxurb->response.actual;
urb->status = zxurb->response.status;
if (urb->status == ZX_ERR_IO_REFUSED) {
usb_reset_endpoint(&devinfo->protocol, urb->zxurb->header.ep_address);
}
pthread_mutex_lock(&irq_callback_lock);
brcmf_dbg(USB, "Enter, urb->status=%d, netbuf=%p\n", urb->status, req->netbuf);
brcmf_usb_del_fromq(devinfo, req);
brcmf_proto_bcdc_txcomplete(devinfo->dev, req->netbuf, urb->status == 0);
req->netbuf = NULL;
brcmf_usb_enq(devinfo, &devinfo->tx_freeq, req, &devinfo->tx_freecount);
//spin_lock_irqsave(&devinfo->tx_flowblock_lock, flags);
if (devinfo->tx_freecount > devinfo->tx_high_watermark && devinfo->tx_flowblock) {
brcmf_proto_bcdc_txflowblock(devinfo->dev, false);
devinfo->tx_flowblock = false;
}
//spin_unlock_irqrestore(&devinfo->tx_flowblock_lock, flags);
pthread_mutex_unlock(&irq_callback_lock);
}
static void brcmf_usb_rx_complete(void* ctx, usb_request_t* zxurb) {
struct brcmf_urb* urb = ctx;
struct brcmf_usbreq* req = (struct brcmf_usbreq*)urb->context;
struct brcmf_usbdev_info* devinfo = req->devinfo;
struct brcmf_netbuf* netbuf;
urb->actual_length = zxurb->response.actual;
urb->status = zxurb->response.status;
if (urb->status == ZX_ERR_IO_REFUSED) {
usb_reset_endpoint(&devinfo->protocol, urb->zxurb->header.ep_address);
}
pthread_mutex_lock(&irq_callback_lock);
brcmf_dbg(USB, "Enter, urb->status=%d\n", urb->status);
brcmf_usb_del_fromq(devinfo, req);
netbuf = req->netbuf;
req->netbuf = NULL;
if (urb->status == ZX_OK && urb->recv_buffer != NULL && urb->actual_length > 0) {
if (urb->actual_length > urb->desired_length) {
brcmf_err("USB read gave more data than requested: %d > %d", urb->actual_length,
urb->desired_length);
urb->actual_length = urb->desired_length;
}
usb_request_copy_from(zxurb, urb->recv_buffer, urb->actual_length, 0);
}
/* zero length packets indicate usb "failure". Do not refill */
if (urb->status != 0 || !urb->actual_length) {
brcmu_pkt_buf_free_netbuf(netbuf);
brcmf_usb_enq(devinfo, &devinfo->rx_freeq, req, NULL);
pthread_mutex_unlock(&irq_callback_lock);
return;
}
if (devinfo->bus_pub.state == BRCMFMAC_USB_STATE_UP) {
brcmf_netbuf_grow_tail(netbuf, urb->actual_length);
brcmf_rx_frame(devinfo->dev, netbuf, true);
brcmf_usb_rx_refill(devinfo, req);
} else {
brcmu_pkt_buf_free_netbuf(netbuf);
brcmf_usb_enq(devinfo, &devinfo->rx_freeq, req, NULL);
}
pthread_mutex_unlock(&irq_callback_lock);
return;
}
static void brcmf_usb_rx_refill(struct brcmf_usbdev_info* devinfo, struct brcmf_usbreq* req) {
struct brcmf_netbuf* netbuf;
zx_status_t ret;
if (!req || !devinfo) {
return;
}
netbuf = brcmf_netbuf_allocate(devinfo->bus_pub.bus_mtu);
if (!netbuf) {
brcmf_usb_enq(devinfo, &devinfo->rx_freeq, req, NULL);
return;
}
req->netbuf = netbuf;
brcmf_usb_init_bulk_urb(req->urb, devinfo, devinfo->rx_endpoint, netbuf->data,
brcmf_netbuf_tail_space(netbuf), false, brcmf_usb_rx_complete, req);
req->devinfo = devinfo;
brcmf_usb_enq(devinfo, &devinfo->rx_postq, req, NULL);
ret = brcmf_usb_queue_urb(req->urb, brcmf_usb_rx_complete);
if (ret != ZX_OK) {
brcmf_usb_del_fromq(devinfo, req);
brcmu_pkt_buf_free_netbuf(req->netbuf);
req->netbuf = NULL;
brcmf_usb_enq(devinfo, &devinfo->rx_freeq, req, NULL);
}
return;
}
static void brcmf_usb_rx_fill_all(struct brcmf_usbdev_info* devinfo) {
struct brcmf_usbreq* req;
if (devinfo->bus_pub.state != BRCMFMAC_USB_STATE_UP) {
brcmf_err("bus is not up=%d\n", devinfo->bus_pub.state);
return;
}
while ((req = brcmf_usb_deq(devinfo, &devinfo->rx_freeq, NULL)) != NULL) {
brcmf_usb_rx_refill(devinfo, req);
}
}
static void brcmf_usb_state_change(struct brcmf_usbdev_info* devinfo, int state) {
struct brcmf_bus* bcmf_bus = devinfo->bus_pub.bus;
int old_state;
brcmf_dbg(USB, "Enter, current state=%d, new state=%d\n", devinfo->bus_pub.state, state);
if ((int)devinfo->bus_pub.state == state) {
return;
}
old_state = devinfo->bus_pub.state;
devinfo->bus_pub.state = state;
/* update state of upper layer */
if (state == BRCMFMAC_USB_STATE_DOWN) {
brcmf_dbg(USB, "DBUS is down\n");
brcmf_bus_change_state(bcmf_bus, BRCMF_BUS_DOWN);
} else if (state == BRCMFMAC_USB_STATE_UP) {
brcmf_dbg(USB, "DBUS is up\n");
brcmf_bus_change_state(bcmf_bus, BRCMF_BUS_UP);
} else {
brcmf_dbg(USB, "DBUS current state=%d\n", state);
}
brcmf_dbg(TEMP, "Exit");
}
static zx_status_t brcmf_usb_tx(struct brcmf_device* dev, struct brcmf_netbuf* netbuf) {
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(dev);
struct brcmf_usbreq* req;
zx_status_t ret;
brcmf_dbg(USB, "Enter, netbuf=%p\n", netbuf);
if (devinfo->bus_pub.state != BRCMFMAC_USB_STATE_UP) {
ret = ZX_ERR_IO;
goto fail;
}
req = brcmf_usb_deq(devinfo, &devinfo->tx_freeq, &devinfo->tx_freecount);
if (!req) {
brcmf_err("no req to send\n");
ret = ZX_ERR_NO_MEMORY;
goto fail;
}
req->netbuf = netbuf;
req->devinfo = devinfo;
brcmf_usb_init_bulk_urb(req->urb, devinfo, devinfo->tx_endpoint, netbuf->data, netbuf->len,
true, brcmf_usb_tx_complete, req);
brcmf_usb_enq(devinfo, &devinfo->tx_postq, req, NULL);
ret = brcmf_usb_queue_urb(req->urb, brcmf_usb_tx_complete);
if (ret != ZX_OK) {
brcmf_err("brcmf_usb_tx usb_queue_urb FAILED\n");
brcmf_usb_del_fromq(devinfo, req);
req->netbuf = NULL;
brcmf_usb_enq(devinfo, &devinfo->tx_freeq, req, &devinfo->tx_freecount);
goto fail;
}
//spin_lock_irqsave(&devinfo->tx_flowblock_lock, flags);
pthread_mutex_lock(&irq_callback_lock);
if (devinfo->tx_freecount < devinfo->tx_low_watermark && !devinfo->tx_flowblock) {
brcmf_proto_bcdc_txflowblock(dev, true);
devinfo->tx_flowblock = true;
}
//spin_unlock_irqrestore(&devinfo->tx_flowblock_lock, flags);
pthread_mutex_unlock(&irq_callback_lock);
return ZX_OK;
fail:
return ret;
}
static zx_status_t brcmf_usb_up(struct brcmf_device* dev) {
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(dev);
brcmf_dbg(USB, "Enter\n");
if (devinfo->bus_pub.state == BRCMFMAC_USB_STATE_UP) {
return ZX_OK;
}
/* Success, indicate devinfo is fully up */
brcmf_usb_state_change(devinfo, BRCMFMAC_USB_STATE_UP);
if (devinfo->ctl_urb) {
/* CTL Write */
devinfo->ctl_write.bmRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
devinfo->ctl_write.bRequest = 0;
devinfo->ctl_write.wValue = 0;
devinfo->ctl_write.wIndex = devinfo->ifnum;
/* CTL Read */
devinfo->ctl_read.bmRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
devinfo->ctl_read.bRequest = 1;
devinfo->ctl_read.wValue = 0;
devinfo->ctl_read.wIndex = devinfo->ifnum;
}
brcmf_usb_rx_fill_all(devinfo);
return ZX_OK;
}
static void brcmf_cancel_all_urbs(struct brcmf_usbdev_info* devinfo) {
brcmf_dbg(TEMP, "* * Entered cancel_all_urbs");
if (devinfo->ctl_urb) {
usb_cancel_all(&devinfo->protocol, 0);
}
if (devinfo->bulk_urb) {
usb_cancel_all(&devinfo->protocol, devinfo->bulk_urb->zxurb->header.ep_address);
}
brcmf_usb_free_q(devinfo, &devinfo->tx_postq, true);
brcmf_usb_free_q(devinfo, &devinfo->rx_postq, true);
}
static void brcmf_usb_down(struct brcmf_device* dev) {
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(dev);
brcmf_dbg(USB, "Enter\n");
if (devinfo == NULL) {
return;
}
if (devinfo->bus_pub.state == BRCMFMAC_USB_STATE_DOWN) {
return;
}
brcmf_usb_state_change(devinfo, BRCMFMAC_USB_STATE_DOWN);
brcmf_cancel_all_urbs(devinfo);
}
static void brcmf_usb_sync_complete(void* ctx, usb_request_t* zxurb) {
struct brcmf_urb* urb = ctx;
pthread_mutex_lock(&irq_callback_lock);
struct brcmf_usbdev_info* devinfo = (struct brcmf_usbdev_info*)urb->context;
urb->actual_length = zxurb->response.actual;
urb->status = zxurb->response.status;
if (urb->status == ZX_OK && urb->recv_buffer != NULL && urb->actual_length > 0) {
if (urb->actual_length > urb->desired_length) {
brcmf_err("USB read gave more data than requested: %d > %d", urb->actual_length,
urb->desired_length);
urb->actual_length = urb->desired_length;
}
usb_request_copy_from(zxurb, urb->recv_buffer, urb->actual_length, 0);
}
brcmf_usb_ioctl_resp_wake(devinfo);
pthread_mutex_unlock(&irq_callback_lock);
}
static zx_status_t brcmf_usb_dl_cmd(struct brcmf_usbdev_info* devinfo, uint8_t cmd, void* buffer,
int buflen) {
zx_status_t ret;
char* tmpbuf;
uint16_t size;
if ((!devinfo) || (devinfo->ctl_urb == NULL)) {
return ZX_ERR_INVALID_ARGS;
}
tmpbuf = malloc(buflen);
if (!tmpbuf) {
return ZX_ERR_NO_MEMORY;
}
size = buflen;
devinfo->ctl_read.wLength = size;
devinfo->ctl_read.bmRequestType = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE;
devinfo->ctl_read.bRequest = cmd;
brcmf_usb_init_control_urb(devinfo->ctl_urb, devinfo, &devinfo->ctl_read, (void*)tmpbuf, size,
brcmf_usb_sync_complete, devinfo);
sync_completion_reset(&devinfo->ioctl_resp_wait);
ret = brcmf_usb_queue_urb(devinfo->ctl_urb, brcmf_usb_sync_complete);
if (ret != ZX_OK) {
brcmf_err("usb_queue_urb failed %d\n", ret);
goto finalize;
}
if (brcmf_usb_ioctl_resp_wait(devinfo) != ZX_OK) {
brcmf_dbg(TEMP, "Timed out. Canceling endpoint 0.");
usb_cancel_all(&devinfo->protocol, 0);
ret = ZX_ERR_SHOULD_WAIT;
} else {
ret = devinfo->ctl_urb->status;
if (ret != ZX_OK) {
brcmf_dbg(TEMP, "dl_buflen got %d bytes, wanted %d (status %d)",
devinfo->ctl_urb->actual_length, buflen, devinfo->ctl_urb->status);
if (ret == ZX_ERR_IO_REFUSED) {
brcmf_dbg(USB, "Resetting endpoint 0");
usb_reset_endpoint(&devinfo->protocol, 0);
}
goto finalize;
}
memcpy(buffer, tmpbuf, buflen);
}
finalize:
free(tmpbuf);
return ret;
}
// For the temp hack below...
static zx_status_t brcmf_usb_resetcfg(struct brcmf_usbdev_info* devinfo);
static bool brcmf_usb_dlneeded(struct brcmf_usbdev_info* devinfo) {
struct bootrom_id_le id;
uint32_t chipid, chiprev;
brcmf_dbg(USB, "Enter\n");
if (devinfo == NULL) {
return false;
}
/* Check if firmware downloaded already by querying runtime ID */
id.chip = 0xDEAD;
zx_status_t result = brcmf_usb_dl_cmd(devinfo, DL_GETVER, &id, sizeof(id));
brcmf_dbg(TEMP, "result from dl_cmd %d", result);
chipid = id.chip;
chiprev = id.chiprev;
if ((chipid & 0x4300) == 0x4300) {
brcmf_dbg(USB, "chip 0x%x rev 0x%x\n", chipid, chiprev);
} else {
brcmf_dbg(USB, "chip %d rev 0x%x\n", chipid, chiprev);
}
if (chipid == BRCMF_POSTBOOT_ID) {
brcmf_dbg(USB, "firmware already downloaded\n");
brcmf_dbg(TEMP, " * * About to resetcfg since I quit early on firmware download");
if (brcmf_usb_resetcfg(devinfo) != ZX_OK) {
brcmf_err("Dongle not runnable (resetcfg failed)\n");
return ZX_ERR_IO_NOT_PRESENT;
}
brcmf_dbg(TEMP, "Got past resetcfg OK");
brcmf_usb_dl_cmd(devinfo, DL_RESETCFG, &id, sizeof(id));
return false;
} else {
devinfo->bus_pub.devid = chipid;
devinfo->bus_pub.chiprev = chiprev;
}
return true;
}
static zx_status_t brcmf_usb_resetcfg(struct brcmf_usbdev_info* devinfo) {
struct bootrom_id_le id;
uint32_t loop_cnt;
zx_status_t err;
brcmf_dbg(USB, "Enter\n");
loop_cnt = 0;
do {
msleep(BRCMF_USB_RESET_GETVER_SPINWAIT_MSEC);
loop_cnt++;
id.chip = 0xDEAD; /* Get the ID */
err = brcmf_usb_dl_cmd(devinfo, DL_GETVER, &id, sizeof(id));
if ((err != ZX_OK) && (err != ZX_ERR_SHOULD_WAIT) && (err != ZX_ERR_IO_REFUSED)) {
brcmf_dbg(USB, "Returning err %s from DL_GETVER", zx_status_get_string(err));
return err;
}
if (id.chip == BRCMF_POSTBOOT_ID) {
break;
}
} while (loop_cnt < BRCMF_USB_RESET_GETVER_LOOP_CNT);
if (id.chip == BRCMF_POSTBOOT_ID) {
brcmf_dbg(USB, "postboot chip 0x%x/rev 0x%x\n", id.chip,
id.chiprev);
brcmf_usb_dl_cmd(devinfo, DL_RESETCFG, &id, sizeof(id));
return ZX_OK;
} else {
brcmf_err("Cannot talk to Dongle. Firmware is not UP, %d ms\n",
BRCMF_USB_RESET_GETVER_SPINWAIT_MSEC * loop_cnt);
return ZX_ERR_INVALID_ARGS;
}
}
static zx_status_t brcmf_usb_dl_send_bulk(struct brcmf_usbdev_info* devinfo, void* buffer,
int len) {
zx_status_t ret;
if ((devinfo == NULL) || (devinfo->bulk_urb == NULL)) {
return ZX_ERR_INVALID_ARGS;
}
/* Prepare the URB */
brcmf_usb_init_bulk_urb(devinfo->bulk_urb, devinfo, devinfo->tx_endpoint, buffer, len, true,
brcmf_usb_sync_complete, devinfo);
sync_completion_reset(&devinfo->ioctl_resp_wait);
ret = brcmf_usb_queue_urb(devinfo->bulk_urb, brcmf_usb_sync_complete);
if (ret != ZX_OK) {
brcmf_err("usb_queue_urb failed %d\n", ret);
return ret;
}
ret = brcmf_usb_ioctl_resp_wait(devinfo);
return ret;
}
static zx_status_t brcmf_usb_dl_writeimage(struct brcmf_usbdev_info* devinfo, uint8_t* fw,
int fwlen) {
unsigned int sendlen, sent, dllen;
char* bulkchunk = NULL;
char* dlpos;
struct rdl_state_le state;
uint32_t rdlstate, rdlbytes;
zx_status_t err = ZX_OK;
brcmf_dbg(USB, "Enter, fw %p, len %d\n", fw, fwlen);
bulkchunk = malloc(TRX_RDL_CHUNK);
if (bulkchunk == NULL) {
err = ZX_ERR_NO_MEMORY;
goto fail;
}
/* 1) Prepare USB boot loader for runtime image */
brcmf_usb_dl_cmd(devinfo, DL_START, &state, sizeof(state));
rdlstate = state.state;
rdlbytes = state.bytes;
brcmf_dbg(TEMP, "Before download, state %d, bytes %d", rdlstate, rdlbytes);
/* 2) Check we are in the Waiting state */
if (rdlstate != DL_WAITING) {
brcmf_err("Failed to DL_START\n");
err = ZX_ERR_BAD_STATE;
goto fail;
}
sent = 0;
dlpos = (char*)fw;
dllen = fwlen;
/* Get chip id and rev */
while (rdlbytes != dllen) {
/* Wait until the usb device reports it received all
* the bytes we sent */
if ((rdlbytes == sent) && (rdlbytes != dllen)) {
if ((dllen - sent) < TRX_RDL_CHUNK) {
sendlen = dllen - sent;
} else {
sendlen = TRX_RDL_CHUNK;
}
/* simply avoid having to send a ZLP by ensuring we
* never have an even
* multiple of 64
*/
if (!(sendlen % 64)) {
sendlen -= 4;
}
/* send data */
memcpy(bulkchunk, dlpos, sendlen);
if (brcmf_usb_dl_send_bulk(devinfo, bulkchunk, sendlen) != ZX_OK) {
brcmf_err("send_bulk failed\n");
err = ZX_ERR_INTERNAL;
goto fail;
}
dlpos += sendlen;
sent += sendlen;
}
err = brcmf_usb_dl_cmd(devinfo, DL_GETSTATE, &state, sizeof(state));
if (err != ZX_OK) {
brcmf_err("DL_GETSTATE Failed\n");
goto fail;
}
rdlstate = state.state;
rdlbytes = state.bytes;
/* restart if an error is reported */
if (rdlstate == DL_BAD_HDR || rdlstate == DL_BAD_CRC) {
brcmf_err("Bad Hdr or Bad CRC state %d\n", rdlstate);
err = ZX_ERR_IO_DATA_INTEGRITY;
goto fail;
}
}
fail:
free(bulkchunk);
brcmf_dbg(USB, "Exit, err=%d\n", err);
return err;
}
static zx_status_t brcmf_usb_dlstart(struct brcmf_usbdev_info* devinfo, uint8_t* fw, int len) {
zx_status_t err;
brcmf_dbg(USB, "Enter\n");
if (devinfo == NULL) {
return ZX_ERR_INVALID_ARGS;
}
if (devinfo->bus_pub.devid == 0xDEAD) {
return ZX_ERR_IO_NOT_PRESENT;
}
err = brcmf_usb_dl_writeimage(devinfo, fw, len);
if (err == ZX_OK) {
devinfo->bus_pub.state = BRCMFMAC_USB_STATE_DL_DONE;
} else {
devinfo->bus_pub.state = BRCMFMAC_USB_STATE_DL_FAIL;
}
brcmf_dbg(USB, "Exit, err=%d\n", err);
return err;
}
static zx_status_t brcmf_usb_dlrun(struct brcmf_usbdev_info* devinfo) {
struct rdl_state_le state;
brcmf_dbg(USB, "Enter\n");
if (!devinfo) {
return ZX_ERR_INVALID_ARGS;
}
if (devinfo->bus_pub.devid == 0xDEAD) {
return ZX_ERR_IO_NOT_PRESENT;
}
/* Check we are runnable */
state.state = 0;
brcmf_usb_dl_cmd(devinfo, DL_GETSTATE, &state, sizeof(state));
/* Start the image */
if (state.state == DL_RUNNABLE) {
if (brcmf_usb_dl_cmd(devinfo, DL_GO, &state, sizeof(state)) != ZX_OK) {
brcmf_err("Dongle not runnable (DL_GO failed)\n");
return ZX_ERR_IO_NOT_PRESENT;
}
// TODO(cphoenix): Hack since the dongle does re-enumerate, and the driver shouldn't
// do anything else on this go-round; this zx_device goes away, and the driver's bind
// entry point will be called again soon with a new one.
brcmf_dbg(TEMP, " * * Early exit - will resetcfg on next entry.");
return ZX_ERR_IO_NOT_PRESENT;
/* brcmf_dbg(TEMP, "About to resetcfg");
if (brcmf_usb_resetcfg(devinfo) != ZX_OK) {
brcmf_err("Dongle not runnable (resetcfg failed)\n");
return ZX_ERR_IO_NOT_PRESENT;
}
brcmf_dbg(TEMP, "Survived resetcfg");*/
/* The Dongle may go for re-enumeration. */
} else {
brcmf_err("Dongle not runnable\n");
return ZX_ERR_IO_NOT_PRESENT;
}
brcmf_dbg(USB, "Exit\n");
return ZX_OK;
}
static zx_status_t brcmf_usb_fw_download(struct brcmf_usbdev_info* devinfo) {
zx_status_t err;
brcmf_dbg(USB, "Enter\n");
if (devinfo == NULL) {
return ZX_ERR_INVALID_ARGS;
}
if (!devinfo->image) {
brcmf_err("No firmware!\n");
return ZX_ERR_BAD_STATE;
}
err = brcmf_usb_dlstart(devinfo, (uint8_t*)devinfo->image, devinfo->image_len);
if (err == ZX_OK) {
err = brcmf_usb_dlrun(devinfo);
}
brcmf_dbg(TEMP, "Exit\n");
return err;
}
static void brcmf_usb_detach(struct brcmf_usbdev_info* devinfo) {
brcmf_dbg(USB, "Enter, devinfo %p\n", devinfo);
/* free the URBS */
brcmf_usb_free_q(devinfo, &devinfo->rx_freeq, false);
brcmf_usb_free_q(devinfo, &devinfo->tx_freeq, false);
brcmf_usb_free_urb(devinfo->ctl_urb);
brcmf_usb_free_urb(devinfo->bulk_urb);
free(devinfo->tx_reqs);
free(devinfo->rx_reqs);
if (devinfo->settings) {
brcmf_release_module_param(devinfo->settings);
}
}
static zx_status_t check_file(const uint8_t* headers) {
struct trx_header_le* trx;
brcmf_dbg(USB, "Enter\n");
/* Extract trx header */
trx = (struct trx_header_le*)headers;
if (trx->magic != TRX_MAGIC) {
return ZX_ERR_INTERNAL;
}
headers += sizeof(struct trx_header_le);
if (trx->flag_version & TRX_UNCOMP_IMAGE) {
return ZX_OK;
}
return ZX_ERR_INTERNAL;
}
static struct brcmf_usbdev* brcmf_usb_attach(struct brcmf_usbdev_info* devinfo, int nrxq,
int ntxq) {
brcmf_dbg(USB, "Enter\n");
devinfo->bus_pub.nrxq = nrxq;
devinfo->rx_low_watermark = nrxq / 2;
devinfo->bus_pub.devinfo = devinfo;
devinfo->bus_pub.ntxq = ntxq;
devinfo->bus_pub.state = BRCMFMAC_USB_STATE_DOWN;
/* flow control when too many tx urbs posted */
devinfo->tx_low_watermark = ntxq / 4;
devinfo->tx_high_watermark = devinfo->tx_low_watermark * 3;
/* Size of buffer for rx */
devinfo->bus_pub.bus_mtu = BRCMF_USB_MAX_PKT_SIZE;
/* Initialize other structure content */
devinfo->ioctl_resp_wait = SYNC_COMPLETION_INIT;
/* Initialize the spinlocks */
//spin_lock_init(&devinfo->qlock);
//spin_lock_init(&devinfo->tx_flowblock_lock);
list_initialize(&devinfo->rx_freeq);
list_initialize(&devinfo->rx_postq);
list_initialize(&devinfo->tx_freeq);
list_initialize(&devinfo->tx_postq);
devinfo->tx_flowblock = false;
devinfo->rx_reqs = brcmf_usbdev_qinit(devinfo, &devinfo->rx_freeq, nrxq);
if (!devinfo->rx_reqs) {
goto error;
}
devinfo->tx_reqs = brcmf_usbdev_qinit(devinfo, &devinfo->tx_freeq, ntxq);
if (!devinfo->tx_reqs) {
goto error;
}
devinfo->tx_freecount = ntxq;
devinfo->ctl_urb = brcmf_usb_allocate_urb(&devinfo->protocol,
devinfo->usbdev->parent_req_size);
if (!devinfo->ctl_urb) {
goto error;
}
devinfo->bulk_urb = brcmf_usb_allocate_urb(&devinfo->protocol,
devinfo->usbdev->parent_req_size);
if (!devinfo->bulk_urb) {
goto error;
}
return &devinfo->bus_pub;
error:
brcmf_err("failed!\n");
brcmf_usb_detach(devinfo);
return NULL;
}
static void brcmf_usb_wowl_config(struct brcmf_device* dev, bool enabled) {
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(dev);
brcmf_dbg(USB, "Configuring WOWL, enabled=%d\n", enabled);
devinfo->wowl_enabled = enabled;
if (enabled) {
device_set_wakeup_enable(devinfo->dev, true);
} else {
device_set_wakeup_enable(devinfo->dev, false);
}
}
static zx_status_t brcmf_usb_get_fwname(struct brcmf_device* dev, uint32_t chip, uint32_t chiprev,
uint8_t* fw_name) {
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(dev);
zx_status_t ret = ZX_OK;
if (devinfo->fw_name[0] != '\0') {
strlcpy((char*)fw_name, devinfo->fw_name, BRCMF_FW_NAME_LEN);
} else
ret = brcmf_fw_map_chip_to_name(chip, chiprev, brcmf_usb_fwnames,
countof(brcmf_usb_fwnames), (char*)fw_name, NULL);
return ret;
}
static zx_status_t brcmf_usb_get_bootloader_macaddr(struct brcmf_device* dev, uint8_t* mac_addr) {
return ZX_ERR_NOT_SUPPORTED;
}
static const struct brcmf_bus_ops brcmf_usb_bus_ops = {
.txdata = brcmf_usb_tx,
.stop = brcmf_usb_down,
.txctl = brcmf_usb_tx_ctlpkt,
.rxctl = brcmf_usb_rx_ctlpkt,
.wowl_config = brcmf_usb_wowl_config,
.get_fwname = brcmf_usb_get_fwname,
.get_bootloader_macaddr = brcmf_usb_get_bootloader_macaddr,
};
static zx_status_t brcmf_usb_bus_setup(struct brcmf_usbdev_info* devinfo) {
zx_status_t ret;
/* Attach to the common driver interface */
ret = brcmf_attach(devinfo->dev, devinfo->settings);
if (ret != ZX_OK) {
brcmf_err("brcmf_attach failed\n");
return ret;
}
ret = brcmf_usb_up(devinfo->dev);
if (ret != ZX_OK) {
goto fail;
}
ret = brcmf_bus_started(devinfo->dev);
if (ret != ZX_OK) {
goto fail;
}
return ZX_OK;
fail:
brcmf_detach(devinfo->dev);
return ret;
}
static void brcmf_usb_probe_phase2(struct brcmf_device* dev, zx_status_t ret,
const struct brcmf_firmware* fw, void* nvram, uint32_t nvlen) {
struct brcmf_bus* bus = dev_to_bus(dev);
struct brcmf_usbdev_info* devinfo = bus->bus_priv.usb->devinfo;
if (ret != ZX_OK) {
goto error;
}
brcmf_dbg(USB, "Start fw downloading\n");
ret = check_file(fw->data);
if (ret != ZX_OK) {
ret = ZX_ERR_IO;
brcmf_err("invalid firmware\n");
goto error;
}
devinfo->image = fw->data;
devinfo->image_len = fw->size;
ret = brcmf_usb_fw_download(devinfo);
if (ret != ZX_OK) {
goto error;
}
ret = brcmf_usb_bus_setup(devinfo);
if (ret != ZX_OK) {
goto error;
}
mtx_unlock(&devinfo->dev_init_lock);
return;
error:
brcmf_dbg(TRACE, "failed: dev=%s, err=%d\n", device_get_name(dev->zxdev), ret);
mtx_unlock(&devinfo->dev_init_lock);
brcmf_err("Need to implement driver release logic (WLAN-888)\n");
// TODO(WLAN-888)
// device_release_driver(dev);
}
static zx_status_t brcmf_usb_probe_cb(struct brcmf_usbdev_info* devinfo) {
struct brcmf_bus* bus = NULL;
struct brcmf_usbdev* bus_pub = NULL;
struct brcmf_device* dev = devinfo->dev;
zx_status_t ret;
brcmf_dbg(USB, "Enter\n");
bus_pub = brcmf_usb_attach(devinfo, BRCMF_USB_NRXQ, BRCMF_USB_NTXQ);
if (!bus_pub) {
return ZX_ERR_IO_NOT_PRESENT;
}
bus = calloc(1, sizeof(struct brcmf_bus));
if (!bus) {
ret = ZX_ERR_NO_MEMORY;
goto fail;
}
bus->dev = dev;
bus_pub->bus = bus;
bus->bus_priv.usb = bus_pub;
dev->bus = bus;
bus->ops = &brcmf_usb_bus_ops;
bus->proto_type = BRCMF_PROTO_BCDC;
bus->always_use_fws_queue = true;
#ifdef CONFIG_PM
bus->wowl_supported = true;
#endif
devinfo->settings =
brcmf_get_module_param(bus->dev, BRCMF_BUSTYPE_USB, bus_pub->devid, bus_pub->chiprev);
if (!devinfo->settings) {
ret = ZX_ERR_NO_MEMORY;
goto fail;
}
if (!brcmf_usb_dlneeded(devinfo)) {
ret = brcmf_usb_bus_setup(devinfo);
if (ret != ZX_OK) {
goto fail;
}
/* we are done */
mtx_unlock(&devinfo->dev_init_lock);
return ZX_OK;
}
bus->chip = bus_pub->devid;
bus->chiprev = bus_pub->chiprev;
ret = brcmf_fw_map_chip_to_name(bus_pub->devid, bus_pub->chiprev, brcmf_usb_fwnames,
countof(brcmf_usb_fwnames), devinfo->fw_name, NULL);
if (ret != ZX_OK) {
goto fail;
}
/* request firmware here */
ret = brcmf_fw_get_firmwares(dev, 0, devinfo->fw_name, NULL, brcmf_usb_probe_phase2);
if (ret != ZX_OK) {
brcmf_err("firmware request failed: %d\n", ret);
goto fail;
}
return ZX_OK;
fail:
/* Release resources in reverse order */
free(bus);
brcmf_usb_detach(devinfo);
return ret;
}
static void brcmf_usb_disconnect_cb(struct brcmf_usbdev_info* devinfo) {
if (!devinfo) {
return;
}
brcmf_dbg(USB, "Enter, bus_pub %p\n", devinfo);
brcmf_detach(devinfo->dev);
free(devinfo->bus_pub.bus);
brcmf_usb_detach(devinfo);
}
static zx_status_t brcmf_usb_probe(struct brcmf_usb_interface* intf, usb_protocol_t* usb_proto) {
struct brcmf_usb_device* usb = intf_to_usbdev(intf);
struct brcmf_usbdev_info* devinfo;
struct brcmf_usb_interface_descriptor* desc;
usb_endpoint_descriptor_t* endpoint;
zx_status_t ret = ZX_OK;
uint32_t num_of_eps;
uint8_t endpoint_num, ep;
devinfo = calloc(1, sizeof(*devinfo));
if (devinfo == NULL) {
return ZX_ERR_NO_MEMORY;
}
devinfo->usbdev = usb;
memcpy(&devinfo->protocol, usb_proto, sizeof(devinfo->protocol));
devinfo->dev = &usb->dev;
/* Take an init lock, to protect for disconnect while still loading.
* Necessary because of the asynchronous firmware load construction
*/
mtx_init(&devinfo->dev_init_lock, mtx_plain);
mtx_lock(&devinfo->dev_init_lock);
intf->intfdata = devinfo;
/* Check that the device supports only one configuration */
if (usb->descriptor.bNumConfigurations != 1) {
brcmf_err("Number of configurations: %d not supported\n",
usb->descriptor.bNumConfigurations);
ret = ZX_ERR_WRONG_TYPE;
goto fail;
}
if ((usb->descriptor.bDeviceClass != USB_CLASS_VENDOR) &&
(usb->descriptor.bDeviceClass != USB_CLASS_MISC) &&
(usb->descriptor.bDeviceClass != USB_CLASS_WIRELESS)) {
brcmf_err("Device class: 0x%x not supported\n", usb->descriptor.bDeviceClass);
ret = ZX_ERR_WRONG_TYPE;
goto fail;
}
desc = &intf->altsetting[0].desc;
if ((desc->bInterfaceClass != USB_CLASS_VENDOR) || (desc->bInterfaceSubClass != 2) ||
(desc->bInterfaceProtocol != 0xff)) {
brcmf_err("non WLAN interface %d: 0x%x:0x%x:0x%x\n", desc->bInterfaceNumber,
desc->bInterfaceClass, desc->bInterfaceSubClass, desc->bInterfaceProtocol);
ret = ZX_ERR_WRONG_TYPE;
goto fail;
}
num_of_eps = desc->bNumEndpoints;
for (ep = 0; ep < num_of_eps; ep++) {
endpoint = &intf->altsetting[0].endpoint[ep].desc;
endpoint_num = endpoint->bEndpointAddress & 0xf;
if (usb_ep_type(endpoint) != USB_ENDPOINT_BULK) {
continue;
}
if (usb_ep_direction(endpoint) == USB_ENDPOINT_IN) {
if (!devinfo->rx_endpoint) {
devinfo->rx_endpoint = endpoint->bEndpointAddress;
}
} else {
if (!devinfo->tx_endpoint) {
devinfo->tx_endpoint = endpoint->bEndpointAddress;
}
}
}
if (devinfo->rx_endpoint == 0) {
brcmf_err("No RX (in) Bulk EP found\n");
ret = ZX_ERR_IO_NOT_PRESENT;
goto fail;
}
if (devinfo->tx_endpoint == 0) {
brcmf_err("No TX (out) Bulk EP found\n");
ret = ZX_ERR_IO_NOT_PRESENT;
goto fail;
}
devinfo->ifnum = desc->bInterfaceNumber;
/* voydanoff@ says ZX USB doesn't distinguish between SUPER and SUPER_PLUS.
if (usb->speed == USB_SPEED_SUPER_PLUS) {
brcmf_dbg(USB, "Broadcom super speed plus USB WLAN interface detected\n");
} else*/
if (usb->speed == USB_SPEED_SUPER) {
brcmf_dbg(USB, "Broadcom super speed or super speed plus USB WLAN interface detected\n");
} else if (usb->speed == USB_SPEED_HIGH) {
brcmf_dbg(USB, "Broadcom high speed USB WLAN interface detected\n");
} else {
brcmf_dbg(USB, "Broadcom full speed USB WLAN interface detected\n");
}
ret = brcmf_usb_probe_cb(devinfo);
if (ret != ZX_OK) {
goto fail;
}
/* Success */
return ZX_OK;
fail:
mtx_unlock(&devinfo->dev_init_lock);
free(devinfo);
intf->intfdata = NULL;
return ret;
}
// Was used in struct usb_driver.disconnect
static void brcmf_usb_disconnect(struct brcmf_usb_interface* intf) {
struct brcmf_usbdev_info* devinfo;
brcmf_dbg(USB, "Enter\n");
devinfo = (struct brcmf_usbdev_info*)(intf->intfdata);
if (devinfo) {
mtx_lock(&devinfo->dev_init_lock);
/* Make sure that devinfo still exists. Firmware probe routines
* may have released the device and cleared the intfdata.
*/
if (intf->intfdata == NULL) {
goto done;
}
brcmf_usb_disconnect_cb(devinfo);
free(devinfo);
}
done:
brcmf_dbg(USB, "Exit\n");
}
/*
* only need to signal the bus being down and update the state.
*/
// Was used in struct usb_driver.suspend
static zx_status_t brcmf_usb_suspend(struct brcmf_usb_interface* intf, uint64_t state) {
struct brcmf_usb_device* usb = intf_to_usbdev(intf);
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(&usb->dev);
brcmf_dbg(USB, "Enter\n");
devinfo->bus_pub.state = BRCMFMAC_USB_STATE_SLEEP;
if (devinfo->wowl_enabled) {
brcmf_cancel_all_urbs(devinfo);
} else {
brcmf_detach(&usb->dev);
}
return ZX_OK;
}
/*
* (re-) start the bus.
*/
// Was used in struct usb_driver.resume
static zx_status_t brcmf_usb_resume(struct brcmf_usb_interface* intf) {
struct brcmf_usb_device* usb = intf_to_usbdev(intf);
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(&usb->dev);
brcmf_dbg(USB, "Enter\n");
if (!devinfo->wowl_enabled) {
return brcmf_usb_bus_setup(devinfo);
}
// TODO(cphoenix): Is this a logic fail?
// Resume calls usb_bus_setup (if !devinfo->wowl_enabled) and usb_rx_fill_all()
// usb_bus_setup calls usb_up
// usb_up calls usb_rx_fill_all()
devinfo->bus_pub.state = BRCMFMAC_USB_STATE_UP;
brcmf_usb_rx_fill_all(devinfo);
return ZX_OK;
}
// Was used in struct usb_driver.reset_resume
static zx_status_t brcmf_usb_reset_resume(struct brcmf_usb_interface* intf) {
struct brcmf_usb_device* usb = intf_to_usbdev(intf);
struct brcmf_usbdev_info* devinfo = brcmf_usb_get_businfo(&usb->dev);
brcmf_dbg(USB, "Enter\n");
return brcmf_fw_get_firmwares(&usb->dev, 0, devinfo->fw_name, NULL, brcmf_usb_probe_phase2);
}
#ifdef TODO_ADD_USB_IDS
#define BROADCOM_USB_DEVICE(dev_id) \
{ .idVendor=BRCM_USB_VENDOR_ID_BROADCOM, .idProduct=dev_id }
#define LINKSYS_USB_DEVICE(dev_id) \
{ .idVendor=BRCM_USB_VENDOR_ID_LINKSYS, .idProduct=dev_id }
#define CYPRESS_USB_DEVICE(dev_id) \
{ .idVendor=CY_USB_VENDOR_ID_CYPRESS, .idProduct=dev_id }
#define LG_USB_DEVICE(dev_id) \
{ .idVendor=BRCM_USB_VENDOR_ID_LG, .idProduct=dev_id }
// Was used in struct usb_driver.id_table
// TODO(cphoenix): Decide which of these to link back in and supply firmware for.
static const struct brcmf_usb_device_id brcmf_usb_devid_table[] = {
BROADCOM_USB_DEVICE(BRCM_USB_43143_DEVICE_ID),
BROADCOM_USB_DEVICE(BRCM_USB_43236_DEVICE_ID),
BROADCOM_USB_DEVICE(BRCM_USB_43242_DEVICE_ID),
BROADCOM_USB_DEVICE(BRCM_USB_43569_DEVICE_ID),
LINKSYS_USB_DEVICE(BRCM_USB_43235_LINKSYS_DEVICE_ID),
CYPRESS_USB_DEVICE(CY_USB_4373_DEVICE_ID),
LG_USB_DEVICE(BRCM_USB_43242_LG_DEVICE_ID),
/* special entry for device with firmware loaded and running */
BROADCOM_USB_DEVICE(BRCM_USB_BCMFW_DEVICE_ID),
CYPRESS_USB_DEVICE(BRCM_USB_BCMFW_DEVICE_ID),
{/* end: all zeroes */}
};
#endif // TODO_ADD_USB_IDS
static zx_status_t brcmf_usb_reset_device(struct brcmf_device* dev, void* notused) {
/* device past is the usb interface so we
* need to use parent here.
*/
brcmf_dev_reset(dev->parent);
return ZX_OK;
}
// TODO(cphoenix): power management: "struct usb_driver.disable_hub_initiated_lpm = 1"
// TODO(cphoenix): This is just to prevent "unused function" warnings - clean up.
struct brcmf_usb_driver {
void* disconnect;
void* suspend;
void* reset;
void* resume;
void* reset_resume;
const struct brcmf_usb_device_id* id_table;
};
struct brcmf_usb_driver brcmf_usbdrvr = {
.disconnect = brcmf_usb_disconnect,
.suspend = brcmf_usb_suspend,
.reset = brcmf_usb_reset_device,
.resume = brcmf_usb_resume,
.reset_resume = brcmf_usb_reset_resume,
//.id_table = brcmf_usb_devid_table,
};
void brcmf_usb_exit(void) {
// TODO(cphoenix): Implement deallocate / unregister
// brcmf_usbdrvr was a struct usb_driver
/* struct device_driver* drv = &brcmf_usbdrvr.drvwrap.driver;
int ret;
brcmf_dbg(USB, "Enter\n");
ret = driver_for_each_device(drv, NULL, NULL, brcmf_usb_reset_device);
usb_deregister(&brcmf_usbdrvr);*/
}
zx_status_t brcmf_usb_register(zx_device_t* zxdev, usb_protocol_t* usb_proto) {
brcmf_dbg(USB, "Enter\n");
usb_device_descriptor_t descriptor;
zx_status_t result;
usb_get_device_descriptor(usb_proto, &descriptor);
brcmf_dbg(USB, "Probing 0x%04x:0x%04x\n", descriptor.idVendor, descriptor.idProduct);
struct brcmf_usb_device* usb_device = calloc(1, sizeof(*usb_device));
if (usb_device == NULL) {
return ZX_ERR_NO_MEMORY;
}
usb_device->speed = usb_get_speed(usb_proto);
usb_device->dev.zxdev = zxdev;
usb_device->descriptor.bNumConfigurations = descriptor.bNumConfigurations;
usb_device->descriptor.bDeviceClass = descriptor.bDeviceClass;
struct brcmf_usb_altsetting* altsetting = calloc(1, sizeof(*altsetting));
if (altsetting == NULL) {
free(usb_device);
return ZX_ERR_NO_MEMORY;
}
usb_device->parent_req_size = usb_get_request_size(usb_proto);
usb_desc_iter_t iter;
result = usb_desc_iter_init(usb_proto, &iter);
if (result != ZX_OK) {
free(usb_device);
free(altsetting);
return result;
}
usb_interface_descriptor_t* intfd = usb_desc_iter_next_interface(&iter, true);
if (intfd == NULL) {
usb_desc_iter_release(&iter);
free(usb_device);
free(altsetting);
return ZX_ERR_NOT_SUPPORTED;
}
altsetting->desc.bInterfaceClass = intfd->bInterfaceClass;
altsetting->desc.bInterfaceNumber = intfd->bInterfaceNumber;
altsetting->desc.bInterfaceProtocol = intfd->bInterfaceProtocol;
altsetting->desc.bInterfaceSubClass = intfd->bInterfaceSubClass;
altsetting->desc.bNumEndpoints = intfd->bNumEndpoints;
altsetting->endpoint =
calloc(altsetting->desc.bNumEndpoints, sizeof(struct brcmf_endpoint_container));
if (altsetting->endpoint == NULL) {
usb_desc_iter_release(&iter);
free(usb_device);
free(altsetting);
return ZX_ERR_NO_MEMORY;
}
struct brcmf_endpoint_container* endpt_container = altsetting->endpoint;
int n_endpoints = 0;
usb_endpoint_descriptor_t* endpt = usb_desc_iter_next_endpoint(&iter);
while (endpt && n_endpoints <= altsetting->desc.bNumEndpoints) {
memcpy(&endpt_container->desc, endpt, sizeof(endpt_container->desc));
endpt = usb_desc_iter_next_endpoint(&iter);
endpt_container++;
n_endpoints++;
}
brcmf_dbg(TEMP, "After loop, bNumEndpoints %d, n_endpoints %d, endpt %p (should be = and null)",
altsetting->desc.bNumEndpoints, n_endpoints, endpt);
intfd = usb_desc_iter_next_interface(&iter, true);
if (intfd != NULL) {
brcmf_dbg(TEMP, " * * * Unexpected second interface - debug this!");
}
usb_desc_iter_release(&iter);
struct brcmf_usb_interface* intf = calloc(1, sizeof(*intf));
if (intf == NULL) {
free(usb_device);
free(altsetting->endpoint);
free(altsetting);
return ZX_ERR_NO_MEMORY;
}
intf->usb_device = usb_device;
intf->altsetting = altsetting;
result = brcmf_usb_probe(intf, usb_proto);
if (result != ZX_OK) {
free(usb_device);
free(altsetting->endpoint);
free(altsetting);
free(intf);
}
return result;
}