diff --git a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/bcmsdh.c b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/bcmsdh.c index 8ab7d1e34a6e..6a3f187320fc 100644 --- a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/bcmsdh.c +++ b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/bcmsdh.c @@ -1,1269 +1,1269 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2010 Broadcom Corporation */ /* ****************** SDIO CARD Interface Functions **************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "chip.h" #include "bus.h" #include "debug.h" #include "sdio.h" #include "core.h" #include "common.h" #define SDIOH_API_ACCESS_RETRY_LIMIT 2 #define DMA_ALIGN_MASK 0x03 #define SDIO_FUNC1_BLOCKSIZE 64 #define SDIO_FUNC2_BLOCKSIZE 512 #define SDIO_4373_FUNC2_BLOCKSIZE 256 #define SDIO_435X_FUNC2_BLOCKSIZE 256 #define SDIO_4329_FUNC2_BLOCKSIZE 128 /* Maximum milliseconds to wait for F2 to come up */ #define SDIO_WAIT_F2RDY 3000 #define BRCMF_DEFAULT_RXGLOM_SIZE 32 /* max rx frames in glom chain */ struct brcmf_sdiod_freezer { atomic_t freezing; atomic_t thread_count; u32 frozen_count; wait_queue_head_t thread_freeze; struct completion resumed; }; static irqreturn_t brcmf_sdiod_oob_irqhandler(int irq, void *dev_id) { struct brcmf_bus *bus_if = dev_get_drvdata(dev_id); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; brcmf_dbg(INTR, "OOB intr triggered\n"); /* out-of-band interrupt is level-triggered which won't * be cleared until dpc */ if (sdiodev->irq_en) { disable_irq_nosync(irq); sdiodev->irq_en = false; } brcmf_sdio_isr(sdiodev->bus, true); return IRQ_HANDLED; } static void brcmf_sdiod_ib_irqhandler(struct sdio_func *func) { struct brcmf_bus *bus_if = dev_get_drvdata(&func->dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; brcmf_dbg(INTR, "IB intr triggered\n"); brcmf_sdio_isr(sdiodev->bus, false); } /* dummy handler for SDIO function 2 interrupt */ static void brcmf_sdiod_dummy_irqhandler(struct sdio_func *func) { } int brcmf_sdiod_intr_register(struct brcmf_sdio_dev *sdiodev) { struct brcmfmac_sdio_pd *pdata; int ret = 0; u8 data; u32 addr, gpiocontrol; pdata = &sdiodev->settings->bus.sdio; if (pdata->oob_irq_supported) { brcmf_dbg(SDIO, "Enter, register OOB IRQ %d\n", pdata->oob_irq_nr); spin_lock_init(&sdiodev->irq_en_lock); sdiodev->irq_en = true; ret = request_irq(pdata->oob_irq_nr, brcmf_sdiod_oob_irqhandler, pdata->oob_irq_flags, "brcmf_oob_intr", &sdiodev->func1->dev); if (ret != 0) { brcmf_err("request_irq failed %d\n", ret); return ret; } sdiodev->oob_irq_requested = true; ret = enable_irq_wake(pdata->oob_irq_nr); if (ret != 0) { brcmf_err("enable_irq_wake failed %d\n", ret); return ret; } disable_irq_wake(pdata->oob_irq_nr); sdio_claim_host(sdiodev->func1); if (sdiodev->bus_if->chip == BRCM_CC_43362_CHIP_ID) { /* assign GPIO to SDIO core */ addr = brcmf_chip_enum_base(sdiodev->func1->device); addr = CORE_CC_REG(addr, gpiocontrol); gpiocontrol = brcmf_sdiod_readl(sdiodev, addr, &ret); gpiocontrol |= 0x2; brcmf_sdiod_writel(sdiodev, addr, gpiocontrol, &ret); brcmf_sdiod_writeb(sdiodev, SBSDIO_GPIO_SELECT, 0xf, &ret); brcmf_sdiod_writeb(sdiodev, SBSDIO_GPIO_OUT, 0, &ret); brcmf_sdiod_writeb(sdiodev, SBSDIO_GPIO_EN, 0x2, &ret); } /* must configure SDIO_CCCR_IENx to enable irq */ data = brcmf_sdiod_func0_rb(sdiodev, SDIO_CCCR_IENx, &ret); data |= SDIO_CCCR_IEN_FUNC1 | SDIO_CCCR_IEN_FUNC2 | SDIO_CCCR_IEN_FUNC0; brcmf_sdiod_func0_wb(sdiodev, SDIO_CCCR_IENx, data, &ret); /* redirect, configure and enable io for interrupt signal */ data = SDIO_CCCR_BRCM_SEPINT_MASK | SDIO_CCCR_BRCM_SEPINT_OE; if (pdata->oob_irq_flags & IRQF_TRIGGER_HIGH) data |= SDIO_CCCR_BRCM_SEPINT_ACT_HI; brcmf_sdiod_func0_wb(sdiodev, SDIO_CCCR_BRCM_SEPINT, data, &ret); sdio_release_host(sdiodev->func1); } else { brcmf_dbg(SDIO, "Entering\n"); sdio_claim_host(sdiodev->func1); sdio_claim_irq(sdiodev->func1, brcmf_sdiod_ib_irqhandler); sdio_claim_irq(sdiodev->func2, brcmf_sdiod_dummy_irqhandler); sdio_release_host(sdiodev->func1); sdiodev->sd_irq_requested = true; } return 0; } void brcmf_sdiod_intr_unregister(struct brcmf_sdio_dev *sdiodev) { brcmf_dbg(SDIO, "Entering oob=%d sd=%d\n", sdiodev->oob_irq_requested, sdiodev->sd_irq_requested); if (sdiodev->oob_irq_requested) { struct brcmfmac_sdio_pd *pdata; pdata = &sdiodev->settings->bus.sdio; sdio_claim_host(sdiodev->func1); brcmf_sdiod_func0_wb(sdiodev, SDIO_CCCR_BRCM_SEPINT, 0, NULL); brcmf_sdiod_func0_wb(sdiodev, SDIO_CCCR_IENx, 0, NULL); sdio_release_host(sdiodev->func1); sdiodev->oob_irq_requested = false; free_irq(pdata->oob_irq_nr, &sdiodev->func1->dev); sdiodev->irq_en = false; sdiodev->oob_irq_requested = false; } if (sdiodev->sd_irq_requested) { sdio_claim_host(sdiodev->func1); sdio_release_irq(sdiodev->func2); sdio_release_irq(sdiodev->func1); sdio_release_host(sdiodev->func1); sdiodev->sd_irq_requested = false; } } void brcmf_sdiod_change_state(struct brcmf_sdio_dev *sdiodev, enum brcmf_sdiod_state state) { if (sdiodev->state == BRCMF_SDIOD_NOMEDIUM || state == sdiodev->state) return; brcmf_dbg(TRACE, "%d -> %d\n", sdiodev->state, state); switch (sdiodev->state) { case BRCMF_SDIOD_DATA: /* any other state means bus interface is down */ brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_DOWN); break; case BRCMF_SDIOD_DOWN: /* transition from DOWN to DATA means bus interface is up */ if (state == BRCMF_SDIOD_DATA) brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_UP); break; default: break; } sdiodev->state = state; } static int brcmf_sdiod_set_backplane_window(struct brcmf_sdio_dev *sdiodev, u32 addr) { u32 v, bar0 = addr & SBSDIO_SBWINDOW_MASK; int err = 0, i; if (bar0 == sdiodev->sbwad) return 0; v = bar0 >> 8; for (i = 0 ; i < 3 && !err ; i++, v >>= 8) brcmf_sdiod_writeb(sdiodev, SBSDIO_FUNC1_SBADDRLOW + i, v & 0xff, &err); if (!err) sdiodev->sbwad = bar0; return err; } u32 brcmf_sdiod_readl(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret) { u32 data = 0; int retval; retval = brcmf_sdiod_set_backplane_window(sdiodev, addr); if (retval) goto out; addr &= SBSDIO_SB_OFT_ADDR_MASK; addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; data = sdio_readl(sdiodev->func1, addr, &retval); out: if (ret) *ret = retval; return data; } void brcmf_sdiod_writel(struct brcmf_sdio_dev *sdiodev, u32 addr, u32 data, int *ret) { int retval; retval = brcmf_sdiod_set_backplane_window(sdiodev, addr); if (retval) goto out; addr &= SBSDIO_SB_OFT_ADDR_MASK; addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; sdio_writel(sdiodev->func1, data, addr, &retval); out: if (ret) *ret = retval; } static int brcmf_sdiod_skbuff_read(struct brcmf_sdio_dev *sdiodev, struct sdio_func *func, u32 addr, struct sk_buff *skb) { unsigned int req_sz; int err; /* Single skb use the standard mmc interface */ req_sz = skb->len + 3; req_sz &= (uint)~3; switch (func->num) { case 1: err = sdio_memcpy_fromio(func, ((u8 *)(skb->data)), addr, req_sz); break; case 2: err = sdio_readsb(func, ((u8 *)(skb->data)), addr, req_sz); break; default: /* bail out as things are really fishy here */ WARN(1, "invalid sdio function number: %d\n", func->num); err = -ENOMEDIUM; } if (err == -ENOMEDIUM) brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM); return err; } static int brcmf_sdiod_skbuff_write(struct brcmf_sdio_dev *sdiodev, struct sdio_func *func, u32 addr, struct sk_buff *skb) { unsigned int req_sz; int err; /* Single skb use the standard mmc interface */ req_sz = skb->len + 3; req_sz &= (uint)~3; err = sdio_memcpy_toio(func, addr, ((u8 *)(skb->data)), req_sz); if (err == -ENOMEDIUM) brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM); return err; } static int mmc_submit_one(struct mmc_data *md, struct mmc_request *mr, struct mmc_command *mc, int sg_cnt, int req_sz, int func_blk_sz, u32 *addr, struct brcmf_sdio_dev *sdiodev, struct sdio_func *func, int write) { int ret; md->sg_len = sg_cnt; md->blocks = req_sz / func_blk_sz; mc->arg |= (*addr & 0x1FFFF) << 9; /* address */ mc->arg |= md->blocks & 0x1FF; /* block count */ /* incrementing addr for function 1 */ if (func->num == 1) *addr += req_sz; mmc_set_data_timeout(md, func->card); mmc_wait_for_req(func->card->host, mr); ret = mc->error ? mc->error : md->error; if (ret == -ENOMEDIUM) { brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM); } else if (ret != 0) { brcmf_err("CMD53 sg block %s failed %d\n", write ? "write" : "read", ret); ret = -EIO; } return ret; } /** * brcmf_sdiod_sglist_rw - SDIO interface function for block data access * @sdiodev: brcmfmac sdio device * @func: SDIO function * @write: direction flag * @addr: dongle memory address as source/destination * @pktlist: skb buffer head pointer * * This function takes the respbonsibility as the interface function to MMC * stack for block data access. It assumes that the skb passed down by the * caller has already been padded and aligned. */ static int brcmf_sdiod_sglist_rw(struct brcmf_sdio_dev *sdiodev, struct sdio_func *func, bool write, u32 addr, struct sk_buff_head *pktlist) { unsigned int req_sz, func_blk_sz, sg_cnt, sg_data_sz, pkt_offset; unsigned int max_req_sz, src_offset, dst_offset; unsigned char *pkt_data, *orig_data, *dst_data; struct sk_buff_head local_list, *target_list; struct sk_buff *pkt_next = NULL, *src; unsigned short max_seg_cnt; struct mmc_request mmc_req; struct mmc_command mmc_cmd; struct mmc_data mmc_dat; struct scatterlist *sgl; int ret = 0; if (!pktlist->qlen) return -EINVAL; target_list = pktlist; /* for host with broken sg support, prepare a page aligned list */ __skb_queue_head_init(&local_list); if (!write && sdiodev->settings->bus.sdio.broken_sg_support) { req_sz = 0; skb_queue_walk(pktlist, pkt_next) req_sz += pkt_next->len; req_sz = ALIGN(req_sz, func->cur_blksize); while (req_sz > PAGE_SIZE) { pkt_next = brcmu_pkt_buf_get_skb(PAGE_SIZE); if (pkt_next == NULL) { ret = -ENOMEM; goto exit; } __skb_queue_tail(&local_list, pkt_next); req_sz -= PAGE_SIZE; } pkt_next = brcmu_pkt_buf_get_skb(req_sz); if (pkt_next == NULL) { ret = -ENOMEM; goto exit; } __skb_queue_tail(&local_list, pkt_next); target_list = &local_list; } func_blk_sz = func->cur_blksize; max_req_sz = sdiodev->max_request_size; max_seg_cnt = min_t(unsigned short, sdiodev->max_segment_count, target_list->qlen); memset(&mmc_req, 0, sizeof(struct mmc_request)); memset(&mmc_cmd, 0, sizeof(struct mmc_command)); memset(&mmc_dat, 0, sizeof(struct mmc_data)); mmc_dat.sg = sdiodev->sgtable.sgl; mmc_dat.blksz = func_blk_sz; mmc_dat.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; mmc_cmd.opcode = SD_IO_RW_EXTENDED; mmc_cmd.arg = write ? 1<<31 : 0; /* write flag */ mmc_cmd.arg |= (func->num & 0x7) << 28; /* SDIO func num */ mmc_cmd.arg |= 1 << 27; /* block mode */ /* for function 1 the addr will be incremented */ mmc_cmd.arg |= (func->num == 1) ? 1 << 26 : 0; mmc_cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; mmc_req.cmd = &mmc_cmd; mmc_req.data = &mmc_dat; req_sz = 0; sg_cnt = 0; sgl = sdiodev->sgtable.sgl; skb_queue_walk(target_list, pkt_next) { pkt_offset = 0; while (pkt_offset < pkt_next->len) { pkt_data = pkt_next->data + pkt_offset; sg_data_sz = pkt_next->len - pkt_offset; if (sg_data_sz > sdiodev->max_segment_size) sg_data_sz = sdiodev->max_segment_size; if (sg_data_sz > max_req_sz - req_sz) sg_data_sz = max_req_sz - req_sz; if (!sgl) { /* out of (pre-allocated) scatterlist entries */ ret = -ENOMEM; goto exit; } sg_set_buf(sgl, pkt_data, sg_data_sz); sg_cnt++; sgl = sg_next(sgl); req_sz += sg_data_sz; pkt_offset += sg_data_sz; if (req_sz >= max_req_sz || sg_cnt >= max_seg_cnt) { ret = mmc_submit_one(&mmc_dat, &mmc_req, &mmc_cmd, sg_cnt, req_sz, func_blk_sz, &addr, sdiodev, func, write); if (ret) goto exit_queue_walk; req_sz = 0; sg_cnt = 0; sgl = sdiodev->sgtable.sgl; } } } if (sg_cnt) ret = mmc_submit_one(&mmc_dat, &mmc_req, &mmc_cmd, sg_cnt, req_sz, func_blk_sz, &addr, sdiodev, func, write); exit_queue_walk: if (!write && sdiodev->settings->bus.sdio.broken_sg_support) { src = __skb_peek(&local_list); src_offset = 0; skb_queue_walk(pktlist, pkt_next) { dst_offset = 0; /* This is safe because we must have enough SKB data * in the local list to cover everything in pktlist. */ while (1) { req_sz = pkt_next->len - dst_offset; if (req_sz > src->len - src_offset) req_sz = src->len - src_offset; orig_data = src->data + src_offset; dst_data = pkt_next->data + dst_offset; memcpy(dst_data, orig_data, req_sz); src_offset += req_sz; if (src_offset == src->len) { src_offset = 0; src = skb_peek_next(src, &local_list); } dst_offset += req_sz; if (dst_offset == pkt_next->len) break; } } } exit: sg_init_table(sdiodev->sgtable.sgl, sdiodev->sgtable.orig_nents); while ((pkt_next = __skb_dequeue(&local_list)) != NULL) brcmu_pkt_buf_free_skb(pkt_next); return ret; } int brcmf_sdiod_recv_buf(struct brcmf_sdio_dev *sdiodev, u8 *buf, uint nbytes) { struct sk_buff *mypkt; int err; mypkt = brcmu_pkt_buf_get_skb(nbytes); if (!mypkt) { brcmf_err("brcmu_pkt_buf_get_skb failed: len %d\n", nbytes); return -EIO; } err = brcmf_sdiod_recv_pkt(sdiodev, mypkt); if (!err) memcpy(buf, mypkt->data, nbytes); brcmu_pkt_buf_free_skb(mypkt); return err; } int brcmf_sdiod_recv_pkt(struct brcmf_sdio_dev *sdiodev, struct sk_buff *pkt) { u32 addr = sdiodev->cc_core->base; int err = 0; brcmf_dbg(SDIO, "addr = 0x%x, size = %d\n", addr, pkt->len); err = brcmf_sdiod_set_backplane_window(sdiodev, addr); if (err) goto done; addr &= SBSDIO_SB_OFT_ADDR_MASK; addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; err = brcmf_sdiod_skbuff_read(sdiodev, sdiodev->func2, addr, pkt); done: return err; } int brcmf_sdiod_recv_chain(struct brcmf_sdio_dev *sdiodev, struct sk_buff_head *pktq, uint totlen) { struct sk_buff *glom_skb = NULL; struct sk_buff *skb; u32 addr = sdiodev->cc_core->base; int err = 0; brcmf_dbg(SDIO, "addr = 0x%x, size = %d\n", addr, pktq->qlen); err = brcmf_sdiod_set_backplane_window(sdiodev, addr); if (err) goto done; addr &= SBSDIO_SB_OFT_ADDR_MASK; addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; if (pktq->qlen == 1) err = brcmf_sdiod_skbuff_read(sdiodev, sdiodev->func2, addr, __skb_peek(pktq)); else if (!sdiodev->sg_support) { glom_skb = brcmu_pkt_buf_get_skb(totlen); if (!glom_skb) return -ENOMEM; err = brcmf_sdiod_skbuff_read(sdiodev, sdiodev->func2, addr, glom_skb); if (err) goto done; skb_queue_walk(pktq, skb) { memcpy(skb->data, glom_skb->data, skb->len); skb_pull(glom_skb, skb->len); } } else err = brcmf_sdiod_sglist_rw(sdiodev, sdiodev->func2, false, addr, pktq); done: brcmu_pkt_buf_free_skb(glom_skb); return err; } int brcmf_sdiod_send_buf(struct brcmf_sdio_dev *sdiodev, u8 *buf, uint nbytes) { struct sk_buff *mypkt; u32 addr = sdiodev->cc_core->base; int err; mypkt = brcmu_pkt_buf_get_skb(nbytes); if (!mypkt) { brcmf_err("brcmu_pkt_buf_get_skb failed: len %d\n", nbytes); return -EIO; } memcpy(mypkt->data, buf, nbytes); err = brcmf_sdiod_set_backplane_window(sdiodev, addr); if (err) goto out; addr &= SBSDIO_SB_OFT_ADDR_MASK; addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; err = brcmf_sdiod_skbuff_write(sdiodev, sdiodev->func2, addr, mypkt); out: brcmu_pkt_buf_free_skb(mypkt); return err; } int brcmf_sdiod_send_pkt(struct brcmf_sdio_dev *sdiodev, struct sk_buff_head *pktq) { struct sk_buff *skb; u32 addr = sdiodev->cc_core->base; int err; brcmf_dbg(SDIO, "addr = 0x%x, size = %d\n", addr, pktq->qlen); err = brcmf_sdiod_set_backplane_window(sdiodev, addr); if (err) return err; addr &= SBSDIO_SB_OFT_ADDR_MASK; addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; if (pktq->qlen == 1 || !sdiodev->sg_support) { skb_queue_walk(pktq, skb) { err = brcmf_sdiod_skbuff_write(sdiodev, sdiodev->func2, addr, skb); if (err) break; } } else { err = brcmf_sdiod_sglist_rw(sdiodev, sdiodev->func2, true, addr, pktq); } return err; } int brcmf_sdiod_ramrw(struct brcmf_sdio_dev *sdiodev, bool write, u32 address, u8 *data, uint size) { int err = 0; struct sk_buff *pkt; u32 sdaddr; uint dsize; dsize = min_t(uint, SBSDIO_SB_OFT_ADDR_LIMIT, size); pkt = dev_alloc_skb(dsize); if (!pkt) { brcmf_err("dev_alloc_skb failed: len %d\n", dsize); return -EIO; } pkt->priority = 0; /* Determine initial transfer parameters */ sdaddr = address & SBSDIO_SB_OFT_ADDR_MASK; if ((sdaddr + size) & SBSDIO_SBWINDOW_MASK) dsize = (SBSDIO_SB_OFT_ADDR_LIMIT - sdaddr); else dsize = size; sdio_claim_host(sdiodev->func1); /* Do the transfer(s) */ while (size) { /* Set the backplane window to include the start address */ err = brcmf_sdiod_set_backplane_window(sdiodev, address); if (err) break; brcmf_dbg(SDIO, "%s %d bytes at offset 0x%08x in window 0x%08x\n", write ? "write" : "read", dsize, sdaddr, address & SBSDIO_SBWINDOW_MASK); sdaddr &= SBSDIO_SB_OFT_ADDR_MASK; sdaddr |= SBSDIO_SB_ACCESS_2_4B_FLAG; skb_put(pkt, dsize); if (write) { memcpy(pkt->data, data, dsize); err = brcmf_sdiod_skbuff_write(sdiodev, sdiodev->func1, sdaddr, pkt); } else { err = brcmf_sdiod_skbuff_read(sdiodev, sdiodev->func1, sdaddr, pkt); } if (err) { brcmf_err("membytes transfer failed\n"); break; } if (!write) memcpy(data, pkt->data, dsize); skb_trim(pkt, 0); /* Adjust for next transfer (if any) */ size -= dsize; if (size) { data += dsize; address += dsize; sdaddr = 0; dsize = min_t(uint, SBSDIO_SB_OFT_ADDR_LIMIT, size); } } dev_kfree_skb(pkt); sdio_release_host(sdiodev->func1); return err; } int brcmf_sdiod_abort(struct brcmf_sdio_dev *sdiodev, struct sdio_func *func) { brcmf_dbg(SDIO, "Enter\n"); /* Issue abort cmd52 command through F0 */ brcmf_sdiod_func0_wb(sdiodev, SDIO_CCCR_ABORT, func->num, NULL); brcmf_dbg(SDIO, "Exit\n"); return 0; } void brcmf_sdiod_sgtable_alloc(struct brcmf_sdio_dev *sdiodev) { struct sdio_func *func; struct mmc_host *host; uint max_blocks; uint nents; int err; func = sdiodev->func2; host = func->card->host; sdiodev->sg_support = host->max_segs > 1; max_blocks = min_t(uint, host->max_blk_count, 511u); sdiodev->max_request_size = min_t(uint, host->max_req_size, max_blocks * func->cur_blksize); sdiodev->max_segment_count = min_t(uint, host->max_segs, SG_MAX_SINGLE_ALLOC); sdiodev->max_segment_size = host->max_seg_size; if (!sdiodev->sg_support) return; nents = max_t(uint, BRCMF_DEFAULT_RXGLOM_SIZE, sdiodev->settings->bus.sdio.txglomsz); nents *= 2; WARN_ON(nents > sdiodev->max_segment_count); brcmf_dbg(TRACE, "nents=%d\n", nents); err = sg_alloc_table(&sdiodev->sgtable, nents, GFP_KERNEL); if (err < 0) { brcmf_err("allocation failed: disable scatter-gather"); sdiodev->sg_support = false; } sdiodev->txglomsz = sdiodev->settings->bus.sdio.txglomsz; } static int brcmf_sdiod_freezer_attach(struct brcmf_sdio_dev *sdiodev) { if (!IS_ENABLED(CONFIG_PM_SLEEP)) return 0; sdiodev->freezer = kzalloc(sizeof(*sdiodev->freezer), GFP_KERNEL); if (!sdiodev->freezer) return -ENOMEM; atomic_set(&sdiodev->freezer->thread_count, 0); atomic_set(&sdiodev->freezer->freezing, 0); init_waitqueue_head(&sdiodev->freezer->thread_freeze); init_completion(&sdiodev->freezer->resumed); return 0; } static void brcmf_sdiod_freezer_detach(struct brcmf_sdio_dev *sdiodev) { if (sdiodev->freezer) { WARN_ON(atomic_read(&sdiodev->freezer->freezing)); kfree(sdiodev->freezer); sdiodev->freezer = NULL; } } static int brcmf_sdiod_freezer_on(struct brcmf_sdio_dev *sdiodev) { atomic_t *expect = &sdiodev->freezer->thread_count; int res = 0; sdiodev->freezer->frozen_count = 0; reinit_completion(&sdiodev->freezer->resumed); atomic_set(&sdiodev->freezer->freezing, 1); brcmf_sdio_trigger_dpc(sdiodev->bus); wait_event(sdiodev->freezer->thread_freeze, atomic_read(expect) == sdiodev->freezer->frozen_count); sdio_claim_host(sdiodev->func1); res = brcmf_sdio_sleep(sdiodev->bus, true); sdio_release_host(sdiodev->func1); return res; } static void brcmf_sdiod_freezer_off(struct brcmf_sdio_dev *sdiodev) { sdio_claim_host(sdiodev->func1); brcmf_sdio_sleep(sdiodev->bus, false); sdio_release_host(sdiodev->func1); atomic_set(&sdiodev->freezer->freezing, 0); complete_all(&sdiodev->freezer->resumed); } bool brcmf_sdiod_freezing(struct brcmf_sdio_dev *sdiodev) { return IS_ENABLED(CONFIG_PM_SLEEP) && atomic_read(&sdiodev->freezer->freezing); } void brcmf_sdiod_try_freeze(struct brcmf_sdio_dev *sdiodev) { if (!brcmf_sdiod_freezing(sdiodev)) return; sdiodev->freezer->frozen_count++; wake_up(&sdiodev->freezer->thread_freeze); wait_for_completion(&sdiodev->freezer->resumed); } void brcmf_sdiod_freezer_count(struct brcmf_sdio_dev *sdiodev) { if (IS_ENABLED(CONFIG_PM_SLEEP)) atomic_inc(&sdiodev->freezer->thread_count); } void brcmf_sdiod_freezer_uncount(struct brcmf_sdio_dev *sdiodev) { if (IS_ENABLED(CONFIG_PM_SLEEP)) atomic_dec(&sdiodev->freezer->thread_count); } int brcmf_sdiod_remove(struct brcmf_sdio_dev *sdiodev) { sdiodev->state = BRCMF_SDIOD_DOWN; if (sdiodev->bus) { brcmf_sdio_remove(sdiodev->bus); sdiodev->bus = NULL; } brcmf_sdiod_freezer_detach(sdiodev); /* Disable functions 2 then 1. */ sdio_claim_host(sdiodev->func1); sdio_disable_func(sdiodev->func2); sdio_disable_func(sdiodev->func1); sdio_release_host(sdiodev->func1); sg_free_table(&sdiodev->sgtable); sdiodev->sbwad = 0; pm_runtime_allow(sdiodev->func1->card->host->parent); return 0; } static void brcmf_sdiod_host_fixup(struct mmc_host *host) { /* runtime-pm powers off the device */ pm_runtime_forbid(host->parent); /* avoid removal detection upon resume */ host->caps |= MMC_CAP_NONREMOVABLE; } int brcmf_sdiod_probe(struct brcmf_sdio_dev *sdiodev) { int ret = 0; unsigned int f2_blksz = SDIO_FUNC2_BLOCKSIZE; sdio_claim_host(sdiodev->func1); ret = sdio_set_block_size(sdiodev->func1, SDIO_FUNC1_BLOCKSIZE); if (ret) { brcmf_err("Failed to set F1 blocksize\n"); sdio_release_host(sdiodev->func1); return ret; } switch (sdiodev->func2->device) { case SDIO_DEVICE_ID_BROADCOM_CYPRESS_4373: f2_blksz = SDIO_4373_FUNC2_BLOCKSIZE; break; case SDIO_DEVICE_ID_BROADCOM_4359: case SDIO_DEVICE_ID_BROADCOM_4354: case SDIO_DEVICE_ID_BROADCOM_4356: f2_blksz = SDIO_435X_FUNC2_BLOCKSIZE; break; case SDIO_DEVICE_ID_BROADCOM_4329: f2_blksz = SDIO_4329_FUNC2_BLOCKSIZE; break; default: break; } ret = sdio_set_block_size(sdiodev->func2, f2_blksz); if (ret) { brcmf_err("Failed to set F2 blocksize\n"); sdio_release_host(sdiodev->func1); return ret; } else { brcmf_dbg(SDIO, "set F2 blocksize to %d\n", f2_blksz); } /* increase F2 timeout */ sdiodev->func2->enable_timeout = SDIO_WAIT_F2RDY; /* Enable Function 1 */ ret = sdio_enable_func(sdiodev->func1); sdio_release_host(sdiodev->func1); if (ret) { brcmf_err("Failed to enable F1: err=%d\n", ret); goto out; } ret = brcmf_sdiod_freezer_attach(sdiodev); if (ret) goto out; /* try to attach to the target device */ sdiodev->bus = brcmf_sdio_probe(sdiodev); if (IS_ERR(sdiodev->bus)) { ret = PTR_ERR(sdiodev->bus); goto out; } brcmf_sdiod_host_fixup(sdiodev->func2->card->host); out: if (ret) brcmf_sdiod_remove(sdiodev); return ret; } #define BRCMF_SDIO_DEVICE(dev_id, fw_vend) \ { \ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, dev_id), \ .driver_data = BRCMF_FWVENDOR_ ## fw_vend \ } #define CYW_SDIO_DEVICE(dev_id, fw_vend) \ { \ SDIO_DEVICE(SDIO_VENDOR_ID_CYPRESS, dev_id), \ .driver_data = BRCMF_FWVENDOR_ ## fw_vend \ } /* devices we support, null terminated */ static const struct sdio_device_id brcmf_sdmmc_ids[] = { BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43143, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43241, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4329, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4330, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4334, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43340, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43341, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43362, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43364, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4335_4339, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4339, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43430, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43439, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4345, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43455, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4354, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4356, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4359, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43751, WCC), + BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43752, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_4373, CYW), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_43012, CYW), - BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_43752, CYW), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_89359, CYW), CYW_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_43439, CYW), { /* end: all zeroes */ } }; MODULE_DEVICE_TABLE(sdio, brcmf_sdmmc_ids); static void brcmf_sdiod_acpi_save_power_manageable(struct brcmf_sdio_dev *sdiodev) { #if IS_ENABLED(CONFIG_ACPI) struct acpi_device *adev; adev = ACPI_COMPANION(&sdiodev->func1->dev); if (adev) sdiodev->func1_power_manageable = adev->flags.power_manageable; adev = ACPI_COMPANION(&sdiodev->func2->dev); if (adev) sdiodev->func2_power_manageable = adev->flags.power_manageable; #endif } static void brcmf_sdiod_acpi_set_power_manageable(struct brcmf_sdio_dev *sdiodev, int enable) { #if IS_ENABLED(CONFIG_ACPI) struct acpi_device *adev; adev = ACPI_COMPANION(&sdiodev->func1->dev); if (adev) adev->flags.power_manageable = enable ? sdiodev->func1_power_manageable : 0; adev = ACPI_COMPANION(&sdiodev->func2->dev); if (adev) adev->flags.power_manageable = enable ? sdiodev->func2_power_manageable : 0; #endif } static int brcmf_ops_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { int err; struct brcmf_sdio_dev *sdiodev; struct brcmf_bus *bus_if; if (!id) { dev_err(&func->dev, "Error no sdio_device_id passed for %x:%x\n", func->vendor, func->device); return -ENODEV; } brcmf_dbg(SDIO, "Enter\n"); brcmf_dbg(SDIO, "Class=%x\n", func->class); brcmf_dbg(SDIO, "sdio vendor ID: 0x%04x\n", func->vendor); brcmf_dbg(SDIO, "sdio device ID: 0x%04x\n", func->device); brcmf_dbg(SDIO, "Function#: %d\n", func->num); /* Set MMC_QUIRK_LENIENT_FN0 for this card */ func->card->quirks |= MMC_QUIRK_LENIENT_FN0; /* Consume func num 1 but dont do anything with it. */ if (func->num == 1) return 0; /* Ignore anything but func 2 */ if (func->num != 2) return -ENODEV; bus_if = kzalloc(sizeof(*bus_if), GFP_KERNEL); if (!bus_if) return -ENOMEM; sdiodev = kzalloc(sizeof(*sdiodev), GFP_KERNEL); if (!sdiodev) { kfree(bus_if); return -ENOMEM; } /* store refs to functions used. mmc_card does * not hold the F0 function pointer. */ sdiodev->func1 = func->card->sdio_func[0]; sdiodev->func2 = func; sdiodev->bus_if = bus_if; bus_if->bus_priv.sdio = sdiodev; bus_if->proto_type = BRCMF_PROTO_BCDC; bus_if->fwvid = id->driver_data; dev_set_drvdata(&func->dev, bus_if); dev_set_drvdata(&sdiodev->func1->dev, bus_if); sdiodev->dev = &sdiodev->func1->dev; brcmf_sdiod_acpi_save_power_manageable(sdiodev); brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_DOWN); brcmf_dbg(SDIO, "F2 found, calling brcmf_sdiod_probe...\n"); err = brcmf_sdiod_probe(sdiodev); if (err) { brcmf_err("F2 error, probe failed %d...\n", err); goto fail; } brcmf_dbg(SDIO, "F2 init completed...\n"); return 0; fail: dev_set_drvdata(&func->dev, NULL); dev_set_drvdata(&sdiodev->func1->dev, NULL); kfree(sdiodev); kfree(bus_if); return err; } static void brcmf_ops_sdio_remove(struct sdio_func *func) { struct brcmf_bus *bus_if; struct brcmf_sdio_dev *sdiodev; brcmf_dbg(SDIO, "Enter\n"); brcmf_dbg(SDIO, "sdio vendor ID: 0x%04x\n", func->vendor); brcmf_dbg(SDIO, "sdio device ID: 0x%04x\n", func->device); brcmf_dbg(SDIO, "Function: %d\n", func->num); bus_if = dev_get_drvdata(&func->dev); if (bus_if) { sdiodev = bus_if->bus_priv.sdio; /* start by unregistering irqs */ brcmf_sdiod_intr_unregister(sdiodev); if (func->num != 1) return; /* only proceed with rest of cleanup if func 1 */ brcmf_sdiod_remove(sdiodev); dev_set_drvdata(&sdiodev->func1->dev, NULL); dev_set_drvdata(&sdiodev->func2->dev, NULL); kfree(bus_if); kfree(sdiodev); } brcmf_dbg(SDIO, "Exit\n"); } void brcmf_sdio_wowl_config(struct device *dev, bool enabled) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; mmc_pm_flag_t pm_caps = sdio_get_host_pm_caps(sdiodev->func1); /* Power must be preserved to be able to support WOWL. */ if (!(pm_caps & MMC_PM_KEEP_POWER)) goto notsup; if (sdiodev->settings->bus.sdio.oob_irq_supported || pm_caps & MMC_PM_WAKE_SDIO_IRQ) { /* Stop ACPI from turning off the device when wowl is enabled */ brcmf_sdiod_acpi_set_power_manageable(sdiodev, !enabled); sdiodev->wowl_enabled = enabled; brcmf_dbg(SDIO, "Configuring WOWL, enabled=%d\n", enabled); return; } notsup: brcmf_dbg(SDIO, "WOWL not supported\n"); } static int brcmf_ops_sdio_suspend(struct device *dev) { struct sdio_func *func; struct brcmf_bus *bus_if; struct brcmf_sdio_dev *sdiodev; mmc_pm_flag_t sdio_flags; bool cap_power_off; int ret = 0; func = container_of(dev, struct sdio_func, dev); brcmf_dbg(SDIO, "Enter: F%d\n", func->num); if (func->num != 1) return 0; cap_power_off = !!(func->card->host->caps & MMC_CAP_POWER_OFF_CARD); bus_if = dev_get_drvdata(dev); sdiodev = bus_if->bus_priv.sdio; if (sdiodev->wowl_enabled || !cap_power_off) { brcmf_sdiod_freezer_on(sdiodev); brcmf_sdio_wd_timer(sdiodev->bus, 0); sdio_flags = MMC_PM_KEEP_POWER; if (sdiodev->wowl_enabled) { if (sdiodev->settings->bus.sdio.oob_irq_supported) enable_irq_wake(sdiodev->settings->bus.sdio.oob_irq_nr); else sdio_flags |= MMC_PM_WAKE_SDIO_IRQ; } if (sdio_set_host_pm_flags(sdiodev->func1, sdio_flags)) brcmf_err("Failed to set pm_flags %x\n", sdio_flags); } else { /* power will be cut so remove device, probe again in resume */ brcmf_sdiod_intr_unregister(sdiodev); ret = brcmf_sdiod_remove(sdiodev); if (ret) brcmf_err("Failed to remove device on suspend\n"); } return ret; } static int brcmf_ops_sdio_resume(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct sdio_func *func = container_of(dev, struct sdio_func, dev); int ret = 0; bool cap_power_off = !!(func->card->host->caps & MMC_CAP_POWER_OFF_CARD); brcmf_dbg(SDIO, "Enter: F%d\n", func->num); if (func->num != 2) return 0; if (!sdiodev->wowl_enabled && cap_power_off) { /* bus was powered off and device removed, probe again */ ret = brcmf_sdiod_probe(sdiodev); if (ret) brcmf_err("Failed to probe device on resume\n"); } else { if (sdiodev->wowl_enabled && sdiodev->settings->bus.sdio.oob_irq_supported) disable_irq_wake(sdiodev->settings->bus.sdio.oob_irq_nr); brcmf_sdiod_freezer_off(sdiodev); } return ret; } static DEFINE_SIMPLE_DEV_PM_OPS(brcmf_sdio_pm_ops, brcmf_ops_sdio_suspend, brcmf_ops_sdio_resume); static struct sdio_driver brcmf_sdmmc_driver = { .probe = brcmf_ops_sdio_probe, .remove = brcmf_ops_sdio_remove, .name = KBUILD_MODNAME, .id_table = brcmf_sdmmc_ids, .drv = { .pm = pm_sleep_ptr(&brcmf_sdio_pm_ops), .coredump = brcmf_dev_coredump, }, }; int brcmf_sdio_register(void) { return sdio_register_driver(&brcmf_sdmmc_driver); } void brcmf_sdio_exit(void) { brcmf_dbg(SDIO, "Enter\n"); sdio_unregister_driver(&brcmf_sdmmc_driver); } diff --git a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/cfg80211.c b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/cfg80211.c index 35f68b370714..43afaa5249a6 100644 --- a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -1,8667 +1,8689 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2010 Broadcom Corporation */ /* Toplevel file. Relies on dhd_linux.c to send commands to the dongle. */ #if defined(__FreeBSD__) #include #endif #include #include #include #include #if defined(__linux__) #include #endif #include #include #if defined(__FreeBSD__) #include #endif #include #include #include #include #include "core.h" #include "debug.h" #include "tracepoint.h" #include "fwil_types.h" #include "p2p.h" #include "btcoex.h" #include "pno.h" #include "fwsignal.h" #include "cfg80211.h" #include "feature.h" #include "fwil.h" #include "proto.h" #include "vendor.h" #include "bus.h" #include "common.h" #include "fwvid.h" #define BRCMF_SCAN_IE_LEN_MAX 2048 #if defined(__FreeBSD__) #ifdef WPA_OUI #undef WPA_OUI #endif #ifdef WPA_OUI_TYPE #undef WPA_OUI_TYPE #endif #ifdef RSN_OUI #undef RSN_OUI #endif #ifdef WME_OUI_TYPE #undef WME_OUI_TYPE #endif #ifdef WPS_OUI_TYPE #undef WPS_OUI_TYPE #endif #endif #define WPA_OUI "\x00\x50\xF2" /* WPA OUI */ #define WPA_OUI_TYPE 1 #define RSN_OUI "\x00\x0F\xAC" /* RSN OUI */ #define WME_OUI_TYPE 2 #define WPS_OUI_TYPE 4 #define VS_IE_FIXED_HDR_LEN 6 #define WPA_IE_VERSION_LEN 2 #define WPA_IE_MIN_OUI_LEN 4 #define WPA_IE_SUITE_COUNT_LEN 2 #define WPA_CIPHER_NONE 0 /* None */ #define WPA_CIPHER_WEP_40 1 /* WEP (40-bit) */ #define WPA_CIPHER_TKIP 2 /* TKIP: default for WPA */ #define WPA_CIPHER_AES_CCM 4 /* AES (CCM) */ #define WPA_CIPHER_WEP_104 5 /* WEP (104-bit) */ #define RSN_AKM_NONE 0 /* None (IBSS) */ #define RSN_AKM_UNSPECIFIED 1 /* Over 802.1x */ #define RSN_AKM_PSK 2 /* Pre-shared Key */ #define RSN_AKM_SHA256_1X 5 /* SHA256, 802.1X */ #define RSN_AKM_SHA256_PSK 6 /* SHA256, Pre-shared Key */ #define RSN_AKM_SAE 8 /* SAE */ #define RSN_CAP_LEN 2 /* Length of RSN capabilities */ #define RSN_CAP_PTK_REPLAY_CNTR_MASK (BIT(2) | BIT(3)) #define RSN_CAP_MFPR_MASK BIT(6) #define RSN_CAP_MFPC_MASK BIT(7) #define RSN_PMKID_COUNT_LEN 2 #define VNDR_IE_CMD_LEN 4 /* length of the set command * string :"add", "del" (+ NUL) */ #define VNDR_IE_COUNT_OFFSET 4 #define VNDR_IE_PKTFLAG_OFFSET 8 #define VNDR_IE_VSIE_OFFSET 12 #define VNDR_IE_HDR_SIZE 12 #define VNDR_IE_PARSE_LIMIT 5 #define DOT11_BCN_PRB_FIXED_LEN 12 /* beacon/probe fixed length */ #define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320 #define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400 #define BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS 20 #define BRCMF_SCAN_CHANNEL_TIME 40 #define BRCMF_SCAN_UNASSOC_TIME 40 #define BRCMF_SCAN_PASSIVE_TIME 120 #define BRCMF_ND_INFO_TIMEOUT msecs_to_jiffies(2000) #define BRCMF_PS_MAX_TIMEOUT_MS 2000 /* Dump obss definitions */ #define ACS_MSRMNT_DELAY 80 #define CHAN_NOISE_DUMMY (-80) #define OBSS_TOKEN_IDX 15 #define IBSS_TOKEN_IDX 15 #define TX_TOKEN_IDX 14 #define CTG_TOKEN_IDX 13 #define PKT_TOKEN_IDX 15 #define IDLE_TOKEN_IDX 12 #define BRCMF_ASSOC_PARAMS_FIXED_SIZE \ (sizeof(struct brcmf_assoc_params_le) - sizeof(u16)) #define BRCMF_MAX_CHANSPEC_LIST \ (BRCMF_DCMD_MEDLEN / sizeof(__le32) - 1) struct brcmf_dump_survey { u32 obss; u32 ibss; u32 no_ctg; u32 no_pckt; u32 tx; u32 idle; }; struct cca_stats_n_flags { u32 msrmnt_time; /* Time for Measurement (msec) */ u32 msrmnt_done; /* flag set when measurement complete */ char buf[1]; }; struct cca_msrmnt_query { u32 msrmnt_query; u32 time_req; }; static bool check_vif_up(struct brcmf_cfg80211_vif *vif) { if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) { brcmf_dbg(INFO, "device is not ready : status (%lu)\n", vif->sme_state); return false; } return true; } #define RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2) #define RATETAB_ENT(_rateid, _flags) \ { \ .bitrate = RATE_TO_BASE100KBPS(_rateid), \ .hw_value = (_rateid), \ .flags = (_flags), \ } static struct ieee80211_rate __wl_rates[] = { RATETAB_ENT(BRCM_RATE_1M, 0), RATETAB_ENT(BRCM_RATE_2M, IEEE80211_RATE_SHORT_PREAMBLE), RATETAB_ENT(BRCM_RATE_5M5, IEEE80211_RATE_SHORT_PREAMBLE), RATETAB_ENT(BRCM_RATE_11M, IEEE80211_RATE_SHORT_PREAMBLE), RATETAB_ENT(BRCM_RATE_6M, 0), RATETAB_ENT(BRCM_RATE_9M, 0), RATETAB_ENT(BRCM_RATE_12M, 0), RATETAB_ENT(BRCM_RATE_18M, 0), RATETAB_ENT(BRCM_RATE_24M, 0), RATETAB_ENT(BRCM_RATE_36M, 0), RATETAB_ENT(BRCM_RATE_48M, 0), RATETAB_ENT(BRCM_RATE_54M, 0), }; #define wl_g_rates (__wl_rates + 0) #define wl_g_rates_size ARRAY_SIZE(__wl_rates) #define wl_a_rates (__wl_rates + 4) #define wl_a_rates_size (wl_g_rates_size - 4) #define CHAN2G(_channel, _freq) { \ .band = NL80211_BAND_2GHZ, \ .center_freq = (_freq), \ .hw_value = (_channel), \ .max_antenna_gain = 0, \ .max_power = 30, \ } #define CHAN5G(_channel) { \ .band = NL80211_BAND_5GHZ, \ .center_freq = 5000 + (5 * (_channel)), \ .hw_value = (_channel), \ .max_antenna_gain = 0, \ .max_power = 30, \ } static struct ieee80211_channel __wl_2ghz_channels[] = { CHAN2G(1, 2412), CHAN2G(2, 2417), CHAN2G(3, 2422), CHAN2G(4, 2427), CHAN2G(5, 2432), CHAN2G(6, 2437), CHAN2G(7, 2442), CHAN2G(8, 2447), CHAN2G(9, 2452), CHAN2G(10, 2457), CHAN2G(11, 2462), CHAN2G(12, 2467), CHAN2G(13, 2472), CHAN2G(14, 2484) }; static struct ieee80211_channel __wl_5ghz_channels[] = { CHAN5G(34), CHAN5G(36), CHAN5G(38), CHAN5G(40), CHAN5G(42), CHAN5G(44), CHAN5G(46), CHAN5G(48), CHAN5G(52), CHAN5G(56), CHAN5G(60), CHAN5G(64), CHAN5G(100), CHAN5G(104), CHAN5G(108), CHAN5G(112), CHAN5G(116), CHAN5G(120), CHAN5G(124), CHAN5G(128), CHAN5G(132), CHAN5G(136), CHAN5G(140), CHAN5G(144), CHAN5G(149), CHAN5G(153), CHAN5G(157), CHAN5G(161), CHAN5G(165) }; /* Band templates duplicated per wiphy. The channel info * above is added to the band during setup. */ static const struct ieee80211_supported_band __wl_band_2ghz = { .band = NL80211_BAND_2GHZ, .bitrates = wl_g_rates, .n_bitrates = wl_g_rates_size, }; static const struct ieee80211_supported_band __wl_band_5ghz = { .band = NL80211_BAND_5GHZ, .bitrates = wl_a_rates, .n_bitrates = wl_a_rates_size, }; /* This is to override regulatory domains defined in cfg80211 module (reg.c) * By default world regulatory domain defined in reg.c puts the flags * NL80211_RRF_NO_IR for 5GHz channels (for * 36..48 and 149..165). * With respect to these flags, wpa_supplicant doesn't * start p2p * operations on 5GHz channels. All the changes in world regulatory * domain are to be done here. */ static const struct ieee80211_regdomain brcmf_regdom = { .n_reg_rules = 4, .alpha2 = "99", .reg_rules = { /* IEEE 802.11b/g, channels 1..11 */ REG_RULE(2412-10, 2472+10, 40, 6, 20, 0), /* If any */ /* IEEE 802.11 channel 14 - Only JP enables * this and for 802.11b only */ REG_RULE(2484-10, 2484+10, 20, 6, 20, 0), /* IEEE 802.11a, channel 36..64 */ REG_RULE(5150-10, 5350+10, 160, 6, 20, 0), /* IEEE 802.11a, channel 100..165 */ REG_RULE(5470-10, 5850+10, 160, 6, 20, 0), } }; /* Note: brcmf_cipher_suites is an array of int defining which cipher suites * are supported. A pointer to this array and the number of entries is passed * on to upper layers. AES_CMAC defines whether or not the driver supports MFP. * So the cipher suite AES_CMAC has to be the last one in the array, and when * device does not support MFP then the number of suites will be decreased by 1 */ static const u32 brcmf_cipher_suites[] = { WLAN_CIPHER_SUITE_WEP40, WLAN_CIPHER_SUITE_WEP104, WLAN_CIPHER_SUITE_TKIP, WLAN_CIPHER_SUITE_CCMP, /* Keep as last entry: */ WLAN_CIPHER_SUITE_AES_CMAC }; /* Vendor specific ie. id = 221, oui and type defines exact ie */ struct brcmf_vs_tlv { u8 id; u8 len; u8 oui[3]; u8 oui_type; }; struct parsed_vndr_ie_info { #if defined(__linux__) u8 *ie_ptr; #elif defined(__FreeBSD__) const u8 *ie_ptr; #endif u32 ie_len; /* total length including id & length field */ struct brcmf_vs_tlv vndrie; }; struct parsed_vndr_ies { u32 count; struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT]; }; #define WL_INTERFACE_CREATE_VER_1 1 #define WL_INTERFACE_CREATE_VER_2 2 #define WL_INTERFACE_CREATE_VER_3 3 #define WL_INTERFACE_CREATE_VER_MAX WL_INTERFACE_CREATE_VER_3 #define WL_INTERFACE_MAC_DONT_USE 0x0 #define WL_INTERFACE_MAC_USE 0x2 #define WL_INTERFACE_CREATE_STA 0x0 #define WL_INTERFACE_CREATE_AP 0x1 struct wl_interface_create_v1 { u16 ver; /* structure version */ u32 flags; /* flags for operation */ u8 mac_addr[ETH_ALEN]; /* MAC address */ u32 wlc_index; /* optional for wlc index */ }; struct wl_interface_create_v2 { u16 ver; /* structure version */ u8 pad1[2]; u32 flags; /* flags for operation */ u8 mac_addr[ETH_ALEN]; /* MAC address */ u8 iftype; /* type of interface created */ u8 pad2; u32 wlc_index; /* optional for wlc index */ }; struct wl_interface_create_v3 { u16 ver; /* structure version */ u16 len; /* length of structure + data */ u16 fixed_len; /* length of structure */ u8 iftype; /* type of interface created */ u8 wlc_index; /* optional for wlc index */ u32 flags; /* flags for operation */ u8 mac_addr[ETH_ALEN]; /* MAC address */ u8 bssid[ETH_ALEN]; /* optional for BSSID */ u8 if_index; /* interface index request */ u8 pad[3]; u8 data[]; /* Optional for specific data */ }; static u8 nl80211_band_to_fwil(enum nl80211_band band) { switch (band) { case NL80211_BAND_2GHZ: return WLC_BAND_2G; case NL80211_BAND_5GHZ: return WLC_BAND_5G; default: WARN_ON(1); break; } return 0; } static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf, struct cfg80211_chan_def *ch) { struct brcmu_chan ch_inf; s32 primary_offset; brcmf_dbg(TRACE, "chandef: control %d center %d width %d\n", ch->chan->center_freq, ch->center_freq1, ch->width); ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq1); primary_offset = ch->chan->center_freq - ch->center_freq1; switch (ch->width) { case NL80211_CHAN_WIDTH_20: case NL80211_CHAN_WIDTH_20_NOHT: ch_inf.bw = BRCMU_CHAN_BW_20; WARN_ON(primary_offset != 0); break; case NL80211_CHAN_WIDTH_40: ch_inf.bw = BRCMU_CHAN_BW_40; if (primary_offset > 0) ch_inf.sb = BRCMU_CHAN_SB_U; else ch_inf.sb = BRCMU_CHAN_SB_L; break; case NL80211_CHAN_WIDTH_80: ch_inf.bw = BRCMU_CHAN_BW_80; if (primary_offset == -30) ch_inf.sb = BRCMU_CHAN_SB_LL; else if (primary_offset == -10) ch_inf.sb = BRCMU_CHAN_SB_LU; else if (primary_offset == 10) ch_inf.sb = BRCMU_CHAN_SB_UL; else ch_inf.sb = BRCMU_CHAN_SB_UU; break; case NL80211_CHAN_WIDTH_160: ch_inf.bw = BRCMU_CHAN_BW_160; if (primary_offset == -70) ch_inf.sb = BRCMU_CHAN_SB_LLL; else if (primary_offset == -50) ch_inf.sb = BRCMU_CHAN_SB_LLU; else if (primary_offset == -30) ch_inf.sb = BRCMU_CHAN_SB_LUL; else if (primary_offset == -10) ch_inf.sb = BRCMU_CHAN_SB_LUU; else if (primary_offset == 10) ch_inf.sb = BRCMU_CHAN_SB_ULL; else if (primary_offset == 30) ch_inf.sb = BRCMU_CHAN_SB_ULU; else if (primary_offset == 50) ch_inf.sb = BRCMU_CHAN_SB_UUL; else ch_inf.sb = BRCMU_CHAN_SB_UUU; break; case NL80211_CHAN_WIDTH_80P80: case NL80211_CHAN_WIDTH_5: case NL80211_CHAN_WIDTH_10: default: WARN_ON_ONCE(1); } switch (ch->chan->band) { case NL80211_BAND_2GHZ: ch_inf.band = BRCMU_CHAN_BAND_2G; break; case NL80211_BAND_5GHZ: ch_inf.band = BRCMU_CHAN_BAND_5G; break; case NL80211_BAND_60GHZ: default: WARN_ON_ONCE(1); } d11inf->encchspec(&ch_inf); brcmf_dbg(TRACE, "chanspec: 0x%x\n", ch_inf.chspec); return ch_inf.chspec; } u16 channel_to_chanspec(struct brcmu_d11inf *d11inf, struct ieee80211_channel *ch) { struct brcmu_chan ch_inf; ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq); ch_inf.bw = BRCMU_CHAN_BW_20; d11inf->encchspec(&ch_inf); return ch_inf.chspec; } /* Traverse a string of 1-byte tag/1-byte length/variable-length value * triples, returning a pointer to the substring whose first element * matches tag */ static const struct brcmf_tlv * brcmf_parse_tlvs(const void *buf, int buflen, uint key) { const struct brcmf_tlv *elt = buf; int totlen = buflen; /* find tagged parameter */ while (totlen >= TLV_HDR_LEN) { int len = elt->len; /* validate remaining totlen */ if ((elt->id == key) && (totlen >= (len + TLV_HDR_LEN))) return elt; #if defined(__linux__) elt = (struct brcmf_tlv *)((u8 *)elt + (len + TLV_HDR_LEN)); #elif defined(__FreeBSD__) elt = (const struct brcmf_tlv *)((const u8 *)elt + (len + TLV_HDR_LEN)); #endif totlen -= (len + TLV_HDR_LEN); } return NULL; } /* Is any of the tlvs the expected entry? If * not update the tlvs buffer pointer/length. */ static bool brcmf_tlv_has_ie(const u8 *ie, const u8 **tlvs, u32 *tlvs_len, const u8 *oui, u32 oui_len, u8 type) { /* If the contents match the OUI and the type */ if (ie[TLV_LEN_OFF] >= oui_len + 1 && !memcmp(&ie[TLV_BODY_OFF], oui, oui_len) && type == ie[TLV_BODY_OFF + oui_len]) { return true; } if (tlvs == NULL) return false; /* point to the next ie */ ie += ie[TLV_LEN_OFF] + TLV_HDR_LEN; /* calculate the length of the rest of the buffer */ *tlvs_len -= (int)(ie - *tlvs); /* update the pointer to the start of the buffer */ *tlvs = ie; return false; } #if defined(__linux__) static struct brcmf_vs_tlv * #elif defined(__FreeBSD__) static const struct brcmf_vs_tlv * #endif brcmf_find_wpaie(const u8 *parse, u32 len) { const struct brcmf_tlv *ie; while ((ie = brcmf_parse_tlvs(parse, len, WLAN_EID_VENDOR_SPECIFIC))) { if (brcmf_tlv_has_ie((const u8 *)ie, &parse, &len, WPA_OUI, TLV_OUI_LEN, WPA_OUI_TYPE)) #if defined(__linux__) return (struct brcmf_vs_tlv *)ie; #elif defined(__FreeBSD__) return (const struct brcmf_vs_tlv *)ie; #endif } return NULL; } #if defined(__linux__) static struct brcmf_vs_tlv * #elif defined(__FreeBSD__) static const struct brcmf_vs_tlv * #endif brcmf_find_wpsie(const u8 *parse, u32 len) { const struct brcmf_tlv *ie; while ((ie = brcmf_parse_tlvs(parse, len, WLAN_EID_VENDOR_SPECIFIC))) { #if defined(__linux__) if (brcmf_tlv_has_ie((u8 *)ie, &parse, &len, WPA_OUI, TLV_OUI_LEN, WPS_OUI_TYPE)) return (struct brcmf_vs_tlv *)ie; #elif defined(__FreeBSD__) if (brcmf_tlv_has_ie((const u8 *)ie, &parse, &len, WPA_OUI, TLV_OUI_LEN, WPS_OUI_TYPE)) return (const struct brcmf_vs_tlv *)ie; #endif } return NULL; } static int brcmf_vif_change_validate(struct brcmf_cfg80211_info *cfg, struct brcmf_cfg80211_vif *vif, enum nl80211_iftype new_type) { struct brcmf_cfg80211_vif *pos; bool check_combos = false; int ret = 0; struct iface_combination_params params = { .num_different_channels = 1, }; list_for_each_entry(pos, &cfg->vif_list, list) if (pos == vif) { params.iftype_num[new_type]++; } else { /* concurrent interfaces so need check combinations */ check_combos = true; params.iftype_num[pos->wdev.iftype]++; } if (check_combos) ret = cfg80211_check_combinations(cfg->wiphy, ¶ms); return ret; } static int brcmf_vif_add_validate(struct brcmf_cfg80211_info *cfg, enum nl80211_iftype new_type) { struct brcmf_cfg80211_vif *pos; struct iface_combination_params params = { .num_different_channels = 1, }; list_for_each_entry(pos, &cfg->vif_list, list) params.iftype_num[pos->wdev.iftype]++; params.iftype_num[new_type]++; return cfg80211_check_combinations(cfg->wiphy, ¶ms); } static void convert_key_from_CPU(struct brcmf_wsec_key *key, struct brcmf_wsec_key_le *key_le) { key_le->index = cpu_to_le32(key->index); key_le->len = cpu_to_le32(key->len); key_le->algo = cpu_to_le32(key->algo); key_le->flags = cpu_to_le32(key->flags); key_le->rxiv.hi = cpu_to_le32(key->rxiv.hi); key_le->rxiv.lo = cpu_to_le16(key->rxiv.lo); key_le->iv_initialized = cpu_to_le32(key->iv_initialized); memcpy(key_le->data, key->data, sizeof(key->data)); memcpy(key_le->ea, key->ea, sizeof(key->ea)); } static int send_key_to_dongle(struct brcmf_if *ifp, struct brcmf_wsec_key *key) { struct brcmf_pub *drvr = ifp->drvr; int err; struct brcmf_wsec_key_le key_le; convert_key_from_CPU(key, &key_le); brcmf_netdev_wait_pend8021x(ifp); err = brcmf_fil_bsscfg_data_set(ifp, "wsec_key", &key_le, sizeof(key_le)); if (err) bphy_err(drvr, "wsec_key error (%d)\n", err); return err; } static void brcmf_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev) { struct brcmf_cfg80211_vif *vif; struct brcmf_if *ifp; vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); ifp = vif->ifp; if ((wdev->iftype == NL80211_IFTYPE_ADHOC) || (wdev->iftype == NL80211_IFTYPE_AP) || (wdev->iftype == NL80211_IFTYPE_P2P_GO)) brcmf_proto_configure_addr_mode(ifp->drvr, ifp->ifidx, ADDR_DIRECT); else brcmf_proto_configure_addr_mode(ifp->drvr, ifp->ifidx, ADDR_INDIRECT); } static int brcmf_get_first_free_bsscfgidx(struct brcmf_pub *drvr) { int bsscfgidx; for (bsscfgidx = 0; bsscfgidx < BRCMF_MAX_IFS; bsscfgidx++) { /* bsscfgidx 1 is reserved for legacy P2P */ if (bsscfgidx == 1) continue; if (!drvr->iflist[bsscfgidx]) return bsscfgidx; } return -ENOMEM; } static void brcmf_set_vif_sta_macaddr(struct brcmf_if *ifp, u8 *mac_addr) { u8 mac_idx = ifp->drvr->sta_mac_idx; /* set difference MAC address with locally administered bit */ memcpy(mac_addr, ifp->mac_addr, ETH_ALEN); mac_addr[0] |= 0x02; mac_addr[3] ^= mac_idx ? 0xC0 : 0xA0; mac_idx++; mac_idx = mac_idx % 2; ifp->drvr->sta_mac_idx = mac_idx; } static int brcmf_cfg80211_request_sta_if(struct brcmf_if *ifp, u8 *macaddr) { struct wl_interface_create_v1 iface_v1; struct wl_interface_create_v2 iface_v2; struct wl_interface_create_v3 iface_v3; u32 iface_create_ver; int err; /* interface_create version 1 */ memset(&iface_v1, 0, sizeof(iface_v1)); iface_v1.ver = WL_INTERFACE_CREATE_VER_1; iface_v1.flags = WL_INTERFACE_CREATE_STA | WL_INTERFACE_MAC_USE; if (!is_zero_ether_addr(macaddr)) memcpy(iface_v1.mac_addr, macaddr, ETH_ALEN); else brcmf_set_vif_sta_macaddr(ifp, iface_v1.mac_addr); err = brcmf_fil_iovar_data_get(ifp, "interface_create", &iface_v1, sizeof(iface_v1)); if (err) { brcmf_info("failed to create interface(v1), err=%d\n", err); } else { brcmf_dbg(INFO, "interface created(v1)\n"); return 0; } /* interface_create version 2 */ memset(&iface_v2, 0, sizeof(iface_v2)); iface_v2.ver = WL_INTERFACE_CREATE_VER_2; iface_v2.flags = WL_INTERFACE_MAC_USE; iface_v2.iftype = WL_INTERFACE_CREATE_STA; if (!is_zero_ether_addr(macaddr)) memcpy(iface_v2.mac_addr, macaddr, ETH_ALEN); else brcmf_set_vif_sta_macaddr(ifp, iface_v2.mac_addr); err = brcmf_fil_iovar_data_get(ifp, "interface_create", &iface_v2, sizeof(iface_v2)); if (err) { brcmf_info("failed to create interface(v2), err=%d\n", err); } else { brcmf_dbg(INFO, "interface created(v2)\n"); return 0; } /* interface_create version 3+ */ /* get supported version from firmware side */ iface_create_ver = 0; err = brcmf_fil_bsscfg_int_query(ifp, "interface_create", &iface_create_ver); if (err) { brcmf_err("fail to get supported version, err=%d\n", err); return -EOPNOTSUPP; } switch (iface_create_ver) { case WL_INTERFACE_CREATE_VER_3: memset(&iface_v3, 0, sizeof(iface_v3)); iface_v3.ver = WL_INTERFACE_CREATE_VER_3; iface_v3.flags = WL_INTERFACE_MAC_USE; iface_v3.iftype = WL_INTERFACE_CREATE_STA; if (!is_zero_ether_addr(macaddr)) memcpy(iface_v3.mac_addr, macaddr, ETH_ALEN); else brcmf_set_vif_sta_macaddr(ifp, iface_v3.mac_addr); err = brcmf_fil_iovar_data_get(ifp, "interface_create", &iface_v3, sizeof(iface_v3)); if (!err) brcmf_dbg(INFO, "interface created(v3)\n"); break; default: brcmf_err("not support interface create(v%d)\n", iface_create_ver); err = -EOPNOTSUPP; break; } if (err) { brcmf_info("station interface creation failed (%d)\n", err); return -EIO; } return 0; } static int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp) { struct wl_interface_create_v1 iface_v1; struct wl_interface_create_v2 iface_v2; struct wl_interface_create_v3 iface_v3; u32 iface_create_ver; struct brcmf_pub *drvr = ifp->drvr; struct brcmf_mbss_ssid_le mbss_ssid_le; int bsscfgidx; int err; /* interface_create version 1 */ memset(&iface_v1, 0, sizeof(iface_v1)); iface_v1.ver = WL_INTERFACE_CREATE_VER_1; iface_v1.flags = WL_INTERFACE_CREATE_AP | WL_INTERFACE_MAC_USE; brcmf_set_vif_sta_macaddr(ifp, iface_v1.mac_addr); err = brcmf_fil_iovar_data_get(ifp, "interface_create", &iface_v1, sizeof(iface_v1)); if (err) { brcmf_info("failed to create interface(v1), err=%d\n", err); } else { brcmf_dbg(INFO, "interface created(v1)\n"); return 0; } /* interface_create version 2 */ memset(&iface_v2, 0, sizeof(iface_v2)); iface_v2.ver = WL_INTERFACE_CREATE_VER_2; iface_v2.flags = WL_INTERFACE_MAC_USE; iface_v2.iftype = WL_INTERFACE_CREATE_AP; brcmf_set_vif_sta_macaddr(ifp, iface_v2.mac_addr); err = brcmf_fil_iovar_data_get(ifp, "interface_create", &iface_v2, sizeof(iface_v2)); if (err) { brcmf_info("failed to create interface(v2), err=%d\n", err); } else { brcmf_dbg(INFO, "interface created(v2)\n"); return 0; } /* interface_create version 3+ */ /* get supported version from firmware side */ iface_create_ver = 0; err = brcmf_fil_bsscfg_int_query(ifp, "interface_create", &iface_create_ver); if (err) { brcmf_err("fail to get supported version, err=%d\n", err); return -EOPNOTSUPP; } switch (iface_create_ver) { case WL_INTERFACE_CREATE_VER_3: memset(&iface_v3, 0, sizeof(iface_v3)); iface_v3.ver = WL_INTERFACE_CREATE_VER_3; iface_v3.flags = WL_INTERFACE_MAC_USE; iface_v3.iftype = WL_INTERFACE_CREATE_AP; brcmf_set_vif_sta_macaddr(ifp, iface_v3.mac_addr); err = brcmf_fil_iovar_data_get(ifp, "interface_create", &iface_v3, sizeof(iface_v3)); if (!err) brcmf_dbg(INFO, "interface created(v3)\n"); break; default: brcmf_err("not support interface create(v%d)\n", iface_create_ver); err = -EOPNOTSUPP; break; } if (err) { brcmf_info("Does not support interface_create (%d)\n", err); memset(&mbss_ssid_le, 0, sizeof(mbss_ssid_le)); bsscfgidx = brcmf_get_first_free_bsscfgidx(ifp->drvr); if (bsscfgidx < 0) return bsscfgidx; mbss_ssid_le.bsscfgidx = cpu_to_le32(bsscfgidx); mbss_ssid_le.SSID_len = cpu_to_le32(5); sprintf(mbss_ssid_le.SSID, "ssid%d", bsscfgidx); err = brcmf_fil_bsscfg_data_set(ifp, "bsscfg:ssid", &mbss_ssid_le, sizeof(mbss_ssid_le)); if (err < 0) bphy_err(drvr, "setting ssid failed %d\n", err); } return err; } /** * brcmf_apsta_add_vif() - create a new AP or STA virtual interface * * @wiphy: wiphy device of new interface. * @name: name of the new interface. * @params: contains mac address for AP or STA device. * @type: interface type. * * Return: pointer to new vif on success, ERR_PTR(-errno) if not */ static struct wireless_dev *brcmf_apsta_add_vif(struct wiphy *wiphy, const char *name, struct vif_params *params, enum nl80211_iftype type) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_vif *vif; int err; if (type != NL80211_IFTYPE_STATION && type != NL80211_IFTYPE_AP) return ERR_PTR(-EINVAL); if (brcmf_cfg80211_vif_event_armed(cfg)) return ERR_PTR(-EBUSY); brcmf_dbg(INFO, "Adding vif \"%s\"\n", name); vif = brcmf_alloc_vif(cfg, type); if (IS_ERR(vif)) return (struct wireless_dev *)vif; brcmf_cfg80211_arm_vif_event(cfg, vif); if (type == NL80211_IFTYPE_STATION) err = brcmf_cfg80211_request_sta_if(ifp, params->macaddr); else err = brcmf_cfg80211_request_ap_if(ifp); if (err) { brcmf_cfg80211_arm_vif_event(cfg, NULL); goto fail; } /* wait for firmware event */ err = brcmf_cfg80211_wait_vif_event(cfg, BRCMF_E_IF_ADD, BRCMF_VIF_EVENT_TIMEOUT); brcmf_cfg80211_arm_vif_event(cfg, NULL); if (!err) { bphy_err(drvr, "timeout occurred\n"); err = -EIO; goto fail; } /* interface created in firmware */ ifp = vif->ifp; if (!ifp) { bphy_err(drvr, "no if pointer provided\n"); err = -ENOENT; goto fail; } strscpy(ifp->ndev->name, name, sizeof(ifp->ndev->name)); err = brcmf_net_attach(ifp, true); if (err) { bphy_err(drvr, "Registering netdevice failed\n"); free_netdev(ifp->ndev); goto fail; } return &ifp->vif->wdev; fail: brcmf_free_vif(vif); return ERR_PTR(err); } static bool brcmf_is_apmode(struct brcmf_cfg80211_vif *vif) { enum nl80211_iftype iftype; iftype = vif->wdev.iftype; return iftype == NL80211_IFTYPE_AP || iftype == NL80211_IFTYPE_P2P_GO; } static bool brcmf_is_ibssmode(struct brcmf_cfg80211_vif *vif) { return vif->wdev.iftype == NL80211_IFTYPE_ADHOC; } /** * brcmf_mon_add_vif() - create monitor mode virtual interface * * @wiphy: wiphy device of new interface. * @name: name of the new interface. * * Return: pointer to new vif on success, ERR_PTR(-errno) if not */ static struct wireless_dev *brcmf_mon_add_vif(struct wiphy *wiphy, const char *name) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_cfg80211_vif *vif; struct net_device *ndev; struct brcmf_if *ifp; int err; if (cfg->pub->mon_if) { err = -EEXIST; goto err_out; } vif = brcmf_alloc_vif(cfg, NL80211_IFTYPE_MONITOR); if (IS_ERR(vif)) { err = PTR_ERR(vif); goto err_out; } ndev = alloc_netdev(sizeof(*ifp), name, NET_NAME_UNKNOWN, ether_setup); if (!ndev) { err = -ENOMEM; goto err_free_vif; } ndev->type = ARPHRD_IEEE80211_RADIOTAP; ndev->ieee80211_ptr = &vif->wdev; ndev->needs_free_netdev = true; ndev->priv_destructor = brcmf_cfg80211_free_netdev; SET_NETDEV_DEV(ndev, wiphy_dev(cfg->wiphy)); ifp = netdev_priv(ndev); ifp->vif = vif; ifp->ndev = ndev; ifp->drvr = cfg->pub; vif->ifp = ifp; vif->wdev.netdev = ndev; err = brcmf_net_mon_attach(ifp); if (err) { brcmf_err("Failed to attach %s device\n", ndev->name); free_netdev(ndev); goto err_free_vif; } cfg->pub->mon_if = ifp; return &vif->wdev; err_free_vif: brcmf_free_vif(vif); err_out: return ERR_PTR(err); } static int brcmf_mon_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = wdev->netdev; ndev->netdev_ops->ndo_stop(ndev); brcmf_net_detach(ndev, true); cfg->pub->mon_if = NULL; return 0; } static struct wireless_dev *brcmf_cfg80211_add_iface(struct wiphy *wiphy, const char *name, unsigned char name_assign_type, enum nl80211_iftype type, struct vif_params *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct wireless_dev *wdev; int err; brcmf_dbg(TRACE, "enter: %s type %d\n", name, type); err = brcmf_vif_add_validate(wiphy_to_cfg(wiphy), type); if (err) { bphy_err(drvr, "iface validation failed: err=%d\n", err); return ERR_PTR(err); } switch (type) { case NL80211_IFTYPE_ADHOC: case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_WDS: case NL80211_IFTYPE_MESH_POINT: return ERR_PTR(-EOPNOTSUPP); case NL80211_IFTYPE_MONITOR: return brcmf_mon_add_vif(wiphy, name); case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_AP: wdev = brcmf_apsta_add_vif(wiphy, name, params, type); break; case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_GO: case NL80211_IFTYPE_P2P_DEVICE: wdev = brcmf_p2p_add_vif(wiphy, name, name_assign_type, type, params); break; case NL80211_IFTYPE_UNSPECIFIED: default: return ERR_PTR(-EINVAL); } if (IS_ERR(wdev)) bphy_err(drvr, "add iface %s type %d failed: err=%d\n", name, type, (int)PTR_ERR(wdev)); else brcmf_cfg80211_update_proto_addr_mode(wdev); return wdev; } static void brcmf_scan_config_mpc(struct brcmf_if *ifp, int mpc) { if (brcmf_feat_is_quirk_enabled(ifp, BRCMF_FEAT_QUIRK_NEED_MPC)) brcmf_set_mpc(ifp, mpc); } void brcmf_set_mpc(struct brcmf_if *ifp, int mpc) { struct brcmf_pub *drvr = ifp->drvr; s32 err = 0; if (check_vif_up(ifp->vif)) { err = brcmf_fil_iovar_int_set(ifp, "mpc", mpc); if (err) { bphy_err(drvr, "fail to set mpc\n"); return; } brcmf_dbg(INFO, "MPC : %d\n", mpc); } } bool brcmf_is_apmode_operating(struct wiphy *wiphy) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_cfg80211_vif *vif; bool ret = false; list_for_each_entry(vif, &cfg->vif_list, list) { if (brcmf_is_apmode(vif) && test_bit(BRCMF_VIF_STATUS_AP_CREATED, &vif->sme_state)) ret = true; } return ret; } static void brcmf_scan_params_v2_to_v1(struct brcmf_scan_params_v2_le *params_v2_le, struct brcmf_scan_params_le *params_le) { size_t params_size; u32 ch; int n_channels, n_ssids; memcpy(¶ms_le->ssid_le, ¶ms_v2_le->ssid_le, sizeof(params_le->ssid_le)); memcpy(¶ms_le->bssid, ¶ms_v2_le->bssid, sizeof(params_le->bssid)); params_le->bss_type = params_v2_le->bss_type; params_le->scan_type = le32_to_cpu(params_v2_le->scan_type); params_le->nprobes = params_v2_le->nprobes; params_le->active_time = params_v2_le->active_time; params_le->passive_time = params_v2_le->passive_time; params_le->home_time = params_v2_le->home_time; params_le->channel_num = params_v2_le->channel_num; ch = le32_to_cpu(params_v2_le->channel_num); n_channels = ch & BRCMF_SCAN_PARAMS_COUNT_MASK; n_ssids = ch >> BRCMF_SCAN_PARAMS_NSSID_SHIFT; params_size = sizeof(u16) * n_channels; if (n_ssids > 0) { params_size = roundup(params_size, sizeof(u32)); params_size += sizeof(struct brcmf_ssid_le) * n_ssids; } memcpy(¶ms_le->channel_list[0], ¶ms_v2_le->channel_list[0], params_size); } static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, struct brcmf_scan_params_v2_le *params_le, struct cfg80211_scan_request *request) { u32 n_ssids; u32 n_channels; s32 i; s32 offset; u16 chanspec; char *ptr; int length; struct brcmf_ssid_le ssid_le; eth_broadcast_addr(params_le->bssid); length = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE; params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2); params_le->bss_type = DOT11_BSSTYPE_ANY; params_le->scan_type = cpu_to_le32(BRCMF_SCANTYPE_ACTIVE); params_le->channel_num = 0; params_le->nprobes = cpu_to_le32(-1); params_le->active_time = cpu_to_le32(-1); params_le->passive_time = cpu_to_le32(-1); params_le->home_time = cpu_to_le32(-1); memset(¶ms_le->ssid_le, 0, sizeof(params_le->ssid_le)); /* Scan abort */ if (!request) { length += sizeof(u16); params_le->channel_num = cpu_to_le32(1); params_le->channel_list[0] = cpu_to_le16(-1); params_le->length = cpu_to_le16(length); return; } n_ssids = request->n_ssids; n_channels = request->n_channels; /* Copy channel array if applicable */ brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n", n_channels); if (n_channels > 0) { length += roundup(sizeof(u16) * n_channels, sizeof(u32)); for (i = 0; i < n_channels; i++) { chanspec = channel_to_chanspec(&cfg->d11inf, request->channels[i]); brcmf_dbg(SCAN, "Chan : %d, Channel spec: %x\n", request->channels[i]->hw_value, chanspec); params_le->channel_list[i] = cpu_to_le16(chanspec); } } else { brcmf_dbg(SCAN, "Scanning all channels\n"); } /* Copy ssid array if applicable */ brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids); if (n_ssids > 0) { offset = offsetof(struct brcmf_scan_params_v2_le, channel_list) + n_channels * sizeof(u16); offset = roundup(offset, sizeof(u32)); length += sizeof(ssid_le) * n_ssids; ptr = (char *)params_le + offset; for (i = 0; i < n_ssids; i++) { memset(&ssid_le, 0, sizeof(ssid_le)); ssid_le.SSID_len = cpu_to_le32(request->ssids[i].ssid_len); memcpy(ssid_le.SSID, request->ssids[i].ssid, request->ssids[i].ssid_len); if (!ssid_le.SSID_len) brcmf_dbg(SCAN, "%d: Broadcast scan\n", i); else brcmf_dbg(SCAN, "%d: scan for %.32s size=%d\n", i, ssid_le.SSID, ssid_le.SSID_len); memcpy(ptr, &ssid_le, sizeof(ssid_le)); ptr += sizeof(ssid_le); } } else { brcmf_dbg(SCAN, "Performing passive scan\n"); params_le->scan_type = cpu_to_le32(BRCMF_SCANTYPE_PASSIVE); } params_le->length = cpu_to_le16(length); /* Adding mask to channel numbers */ params_le->channel_num = cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK)); } s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, bool aborted, bool fw_abort) { struct brcmf_pub *drvr = cfg->pub; struct brcmf_scan_params_v2_le params_v2_le; struct cfg80211_scan_request *scan_request; u64 reqid; u32 bucket; s32 err = 0; brcmf_dbg(SCAN, "Enter\n"); /* clear scan request, because the FW abort can cause a second call */ /* to this functon and might cause a double cfg80211_scan_done */ scan_request = cfg->scan_request; cfg->scan_request = NULL; timer_delete_sync(&cfg->escan_timeout); if (fw_abort) { /* Do a scan abort to stop the driver's scan engine */ brcmf_dbg(SCAN, "ABORT scan in firmware\n"); brcmf_escan_prep(cfg, ¶ms_v2_le, NULL); /* E-Scan (or anyother type) can be aborted by SCAN */ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, ¶ms_v2_le, sizeof(params_v2_le)); } else { struct brcmf_scan_params_le params_le; brcmf_scan_params_v2_to_v1(¶ms_v2_le, ¶ms_le); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, ¶ms_le, sizeof(params_le)); } if (err) bphy_err(drvr, "Scan abort failed\n"); } brcmf_scan_config_mpc(ifp, 1); /* * e-scan can be initiated internally * which takes precedence. */ if (cfg->int_escan_map) { brcmf_dbg(SCAN, "scheduled scan completed (%x)\n", cfg->int_escan_map); while (cfg->int_escan_map) { bucket = __ffs(cfg->int_escan_map); cfg->int_escan_map &= ~BIT(bucket); reqid = brcmf_pno_find_reqid_by_bucket(cfg->pno, bucket); if (!aborted) { #if defined(__linux__) brcmf_dbg(SCAN, "report results: reqid=%llu\n", reqid); #elif defined(__FreeBSD__) brcmf_dbg(SCAN, "report results: reqid=%ju\n", (uintmax_t)reqid); #endif cfg80211_sched_scan_results(cfg_to_wiphy(cfg), reqid); } } } else if (scan_request) { struct cfg80211_scan_info info = { .aborted = aborted, }; brcmf_dbg(SCAN, "ESCAN Completed scan: %s\n", aborted ? "Aborted" : "Done"); cfg80211_scan_done(scan_request, &info); } if (!test_and_clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) brcmf_dbg(SCAN, "Scan complete, probably P2P scan\n"); return err; } static int brcmf_cfg80211_del_apsta_iface(struct wiphy *wiphy, struct wireless_dev *wdev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = wdev->netdev; struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; int ret; int err; brcmf_cfg80211_arm_vif_event(cfg, ifp->vif); err = brcmf_fil_bsscfg_data_set(ifp, "interface_remove", NULL, 0); if (err) { bphy_err(drvr, "interface_remove failed %d\n", err); goto err_unarm; } /* wait for firmware event */ ret = brcmf_cfg80211_wait_vif_event(cfg, BRCMF_E_IF_DEL, BRCMF_VIF_EVENT_TIMEOUT); if (!ret) { bphy_err(drvr, "timeout occurred\n"); err = -EIO; goto err_unarm; } brcmf_remove_interface(ifp, true); err_unarm: brcmf_cfg80211_arm_vif_event(cfg, NULL); return err; } static int brcmf_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = wdev->netdev; if (ndev && ndev == cfg_to_ndev(cfg)) return -ENOTSUPP; /* vif event pending in firmware */ if (brcmf_cfg80211_vif_event_armed(cfg)) return -EBUSY; if (ndev) { if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status) && cfg->escan_info.ifp == netdev_priv(ndev)) brcmf_notify_escan_complete(cfg, netdev_priv(ndev), true, true); brcmf_fil_iovar_int_set(netdev_priv(ndev), "mpc", 1); } switch (wdev->iftype) { case NL80211_IFTYPE_ADHOC: case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_WDS: case NL80211_IFTYPE_MESH_POINT: return -EOPNOTSUPP; case NL80211_IFTYPE_MONITOR: return brcmf_mon_del_vif(wiphy, wdev); case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_AP: return brcmf_cfg80211_del_apsta_iface(wiphy, wdev); case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_GO: case NL80211_IFTYPE_P2P_DEVICE: return brcmf_p2p_del_vif(wiphy, wdev); case NL80211_IFTYPE_UNSPECIFIED: default: return -EINVAL; } return -EOPNOTSUPP; } static s32 brcmf_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, enum nl80211_iftype type, struct vif_params *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_vif *vif = ifp->vif; struct brcmf_pub *drvr = cfg->pub; s32 infra = 0; s32 ap = 0; s32 err = 0; brcmf_dbg(TRACE, "Enter, bsscfgidx=%d, type=%d\n", ifp->bsscfgidx, type); /* WAR: There are a number of p2p interface related problems which * need to be handled initially (before doing the validate). * wpa_supplicant tends to do iface changes on p2p device/client/go * which are not always possible/allowed. However we need to return * OK otherwise the wpa_supplicant wont start. The situation differs * on configuration and setup (p2pon=1 module param). The first check * is to see if the request is a change to station for p2p iface. */ if ((type == NL80211_IFTYPE_STATION) && ((vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) || (vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) || (vif->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE))) { brcmf_dbg(TRACE, "Ignoring cmd for p2p if\n"); /* Now depending on whether module param p2pon=1 was used the * response needs to be either 0 or EOPNOTSUPP. The reason is * that if p2pon=1 is used, but a newer supplicant is used then * we should return an error, as this combination wont work. * In other situations 0 is returned and supplicant will start * normally. It will give a trace in cfg80211, but it is the * only way to get it working. Unfortunately this will result * in situation where we wont support new supplicant in * combination with module param p2pon=1, but that is the way * it is. If the user tries this then unloading of driver might * fail/lock. */ if (cfg->p2p.p2pdev_dynamically) return -EOPNOTSUPP; else return 0; } err = brcmf_vif_change_validate(wiphy_to_cfg(wiphy), vif, type); if (err) { bphy_err(drvr, "iface validation failed: err=%d\n", err); return err; } switch (type) { case NL80211_IFTYPE_MONITOR: case NL80211_IFTYPE_WDS: bphy_err(drvr, "type (%d) : currently we do not support this type\n", type); return -EOPNOTSUPP; case NL80211_IFTYPE_ADHOC: infra = 0; break; case NL80211_IFTYPE_STATION: infra = 1; break; case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: ap = 1; break; default: err = -EINVAL; goto done; } if (ap) { if (type == NL80211_IFTYPE_P2P_GO) { brcmf_dbg(INFO, "IF Type = P2P GO\n"); err = brcmf_p2p_ifchange(cfg, BRCMF_FIL_P2P_IF_GO); } if (!err) { brcmf_dbg(INFO, "IF Type = AP\n"); } } else { err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_INFRA, infra); if (err) { bphy_err(drvr, "WLC_SET_INFRA error (%d)\n", err); err = -EAGAIN; goto done; } brcmf_dbg(INFO, "IF Type = %s\n", brcmf_is_ibssmode(vif) ? "Adhoc" : "Infra"); } ndev->ieee80211_ptr->iftype = type; brcmf_cfg80211_update_proto_addr_mode(&vif->wdev); done: brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, struct cfg80211_scan_request *request) { struct brcmf_pub *drvr = cfg->pub; s32 params_size = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE + offsetof(struct brcmf_escan_params_le, params_v2_le); struct brcmf_escan_params_le *params; s32 err = 0; brcmf_dbg(SCAN, "E-SCAN START\n"); if (request != NULL) { /* Allocate space for populating ssids in struct */ params_size += sizeof(u32) * ((request->n_channels + 1) / 2); /* Allocate space for populating ssids in struct */ params_size += sizeof(struct brcmf_ssid_le) * request->n_ssids; } params = kzalloc(params_size, GFP_KERNEL); if (!params) { err = -ENOMEM; goto exit; } BUG_ON(params_size + sizeof("escan") >= BRCMF_DCMD_MEDLEN); brcmf_escan_prep(cfg, ¶ms->params_v2_le, request); params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V2); if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { struct brcmf_escan_params_le *params_v1; params_size -= BRCMF_SCAN_PARAMS_V2_FIXED_SIZE; params_size += BRCMF_SCAN_PARAMS_FIXED_SIZE; params_v1 = kzalloc(params_size, GFP_KERNEL); if (!params_v1) { err = -ENOMEM; goto exit_params; } params_v1->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION); brcmf_scan_params_v2_to_v1(¶ms->params_v2_le, ¶ms_v1->params_le); kfree(params); params = params_v1; } params->action = cpu_to_le16(WL_ESCAN_ACTION_START); params->sync_id = cpu_to_le16(0x1234); err = brcmf_fil_iovar_data_set(ifp, "escan", params, params_size); if (err) { if (err == -EBUSY) brcmf_dbg(INFO, "system busy : escan canceled\n"); else bphy_err(drvr, "error (%d)\n", err); } exit_params: kfree(params); exit: return err; } static s32 brcmf_do_escan(struct brcmf_if *ifp, struct cfg80211_scan_request *request) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; s32 err; struct brcmf_scan_results *results; struct escan_info *escan = &cfg->escan_info; brcmf_dbg(SCAN, "Enter\n"); escan->ifp = ifp; escan->wiphy = cfg->wiphy; escan->escan_state = WL_ESCAN_STATE_SCANNING; brcmf_scan_config_mpc(ifp, 0); results = (struct brcmf_scan_results *)cfg->escan_info.escan_buf; results->version = 0; results->count = 0; results->buflen = WL_ESCAN_RESULTS_FIXED_SIZE; err = escan->run(cfg, ifp, request); if (err) brcmf_scan_config_mpc(ifp, 1); return err; } static s32 brcmf_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_vif *vif; s32 err = 0; brcmf_dbg(TRACE, "Enter\n"); vif = container_of(request->wdev, struct brcmf_cfg80211_vif, wdev); if (!check_vif_up(vif)) return -EIO; if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) { bphy_err(drvr, "Scanning already: status (%lu)\n", cfg->scan_status); return -EAGAIN; } if (test_bit(BRCMF_SCAN_STATUS_ABORT, &cfg->scan_status)) { bphy_err(drvr, "Scanning being aborted: status (%lu)\n", cfg->scan_status); return -EAGAIN; } if (test_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status)) { bphy_err(drvr, "Scanning suppressed: status (%lu)\n", cfg->scan_status); return -EAGAIN; } if (test_bit(BRCMF_VIF_STATUS_CONNECTING, &vif->sme_state)) { bphy_err(drvr, "Connecting: status (%lu)\n", vif->sme_state); return -EAGAIN; } brcmf_dbg(SCAN, "START ESCAN\n"); cfg->scan_request = request; set_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); cfg->escan_info.run = brcmf_run_escan; err = brcmf_p2p_scan_prep(wiphy, request, vif); if (err) goto scan_out; err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_PRBREQ_FLAG, request->ie, request->ie_len); if (err) goto scan_out; /* If scan req comes for p2p0, send it over primary I/F */ if (vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif) vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; err = brcmf_do_escan(vif->ifp, request); if (err) goto scan_out; /* Arm scan timeout timer */ mod_timer(&cfg->escan_timeout, jiffies + msecs_to_jiffies(BRCMF_ESCAN_TIMER_INTERVAL_MS)); return 0; scan_out: bphy_err(drvr, "scan error (%d)\n", err); clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); cfg->scan_request = NULL; return err; } static s32 brcmf_set_rts(struct net_device *ndev, u32 rts_threshold) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = ifp->drvr; s32 err = 0; err = brcmf_fil_iovar_int_set(ifp, "rtsthresh", rts_threshold); if (err) bphy_err(drvr, "Error (%d)\n", err); return err; } static s32 brcmf_set_frag(struct net_device *ndev, u32 frag_threshold) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = ifp->drvr; s32 err = 0; err = brcmf_fil_iovar_int_set(ifp, "fragthresh", frag_threshold); if (err) bphy_err(drvr, "Error (%d)\n", err); return err; } static s32 brcmf_set_retry(struct net_device *ndev, u32 retry, bool l) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = ifp->drvr; s32 err = 0; u32 cmd = (l ? BRCMF_C_SET_LRL : BRCMF_C_SET_SRL); err = brcmf_fil_cmd_int_set(ifp, cmd, retry); if (err) { bphy_err(drvr, "cmd (%d) , error (%d)\n", cmd, err); return err; } return err; } static s32 brcmf_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, u32 changed) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = cfg_to_ndev(cfg); struct brcmf_if *ifp = netdev_priv(ndev); s32 err = 0; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) return -EIO; if (changed & WIPHY_PARAM_RTS_THRESHOLD && (cfg->conf->rts_threshold != wiphy->rts_threshold)) { cfg->conf->rts_threshold = wiphy->rts_threshold; err = brcmf_set_rts(ndev, cfg->conf->rts_threshold); if (!err) goto done; } if (changed & WIPHY_PARAM_FRAG_THRESHOLD && (cfg->conf->frag_threshold != wiphy->frag_threshold)) { cfg->conf->frag_threshold = wiphy->frag_threshold; err = brcmf_set_frag(ndev, cfg->conf->frag_threshold); if (!err) goto done; } if (changed & WIPHY_PARAM_RETRY_LONG && (cfg->conf->retry_long != wiphy->retry_long)) { cfg->conf->retry_long = wiphy->retry_long; err = brcmf_set_retry(ndev, cfg->conf->retry_long, true); if (!err) goto done; } if (changed & WIPHY_PARAM_RETRY_SHORT && (cfg->conf->retry_short != wiphy->retry_short)) { cfg->conf->retry_short = wiphy->retry_short; err = brcmf_set_retry(ndev, cfg->conf->retry_short, false); if (!err) goto done; } done: brcmf_dbg(TRACE, "Exit\n"); return err; } static void brcmf_init_prof(struct brcmf_cfg80211_profile *prof) { memset(prof, 0, sizeof(*prof)); } static u16 brcmf_map_fw_linkdown_reason(const struct brcmf_event_msg *e) { u16 reason; switch (e->event_code) { case BRCMF_E_DEAUTH: case BRCMF_E_DEAUTH_IND: case BRCMF_E_DISASSOC_IND: reason = e->reason; break; case BRCMF_E_LINK: default: reason = 0; break; } return reason; } int brcmf_set_wsec(struct brcmf_if *ifp, const u8 *key, u16 key_len, u16 flags) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_wsec_pmk_le pmk; int err; if (key_len > sizeof(pmk.key)) { bphy_err(drvr, "key must be less than %zu bytes\n", sizeof(pmk.key)); return -EINVAL; } memset(&pmk, 0, sizeof(pmk)); /* pass key material directly */ pmk.key_len = cpu_to_le16(key_len); pmk.flags = cpu_to_le16(flags); memcpy(pmk.key, key, key_len); /* store key material in firmware */ err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_WSEC_PMK, &pmk, sizeof(pmk)); if (err < 0) bphy_err(drvr, "failed to change PSK in firmware (len=%u)\n", key_len); return err; } BRCMF_EXPORT_SYMBOL_GPL(brcmf_set_wsec); static int brcmf_set_pmk(struct brcmf_if *ifp, const u8 *pmk_data, u16 pmk_len) { return brcmf_set_wsec(ifp, pmk_data, pmk_len, 0); } static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason, bool locally_generated) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(vif->wdev.wiphy); struct brcmf_pub *drvr = cfg->pub; bool bus_up = drvr->bus_if->state == BRCMF_BUS_UP; s32 err = 0; brcmf_dbg(TRACE, "Enter\n"); if (test_and_clear_bit(BRCMF_VIF_STATUS_CONNECTED, &vif->sme_state)) { if (bus_up) { brcmf_dbg(INFO, "Call WLC_DISASSOC to stop excess roaming\n"); err = brcmf_fil_cmd_data_set(vif->ifp, BRCMF_C_DISASSOC, NULL, 0); if (err) bphy_err(drvr, "WLC_DISASSOC failed (%d)\n", err); } if ((vif->wdev.iftype == NL80211_IFTYPE_STATION) || (vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)) cfg80211_disconnected(vif->wdev.netdev, reason, NULL, 0, locally_generated, GFP_KERNEL); } clear_bit(BRCMF_VIF_STATUS_CONNECTING, &vif->sme_state); clear_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); clear_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); brcmf_btcoex_set_mode(vif, BRCMF_BTCOEX_ENABLED, 0); if (vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { if (bus_up) brcmf_set_pmk(vif->ifp, NULL, 0); vif->profile.use_fwsup = BRCMF_PROFILE_FWSUP_NONE; } brcmf_dbg(TRACE, "Exit\n"); } static s32 brcmf_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_ibss_params *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct brcmf_pub *drvr = cfg->pub; struct brcmf_join_params join_params; size_t join_params_size = 0; s32 err = 0; s32 wsec = 0; s32 bcnprd; u16 chanspec; u32 ssid_len; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) return -EIO; if (params->ssid) brcmf_dbg(CONN, "SSID: %s\n", params->ssid); else { brcmf_dbg(CONN, "SSID: NULL, Not supported\n"); return -EOPNOTSUPP; } set_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); if (params->bssid) brcmf_dbg(CONN, "BSSID: %pM\n", params->bssid); else brcmf_dbg(CONN, "No BSSID specified\n"); if (params->chandef.chan) brcmf_dbg(CONN, "channel: %d\n", params->chandef.chan->center_freq); else brcmf_dbg(CONN, "no channel specified\n"); if (params->channel_fixed) brcmf_dbg(CONN, "fixed channel required\n"); else brcmf_dbg(CONN, "no fixed channel required\n"); if (params->ie && params->ie_len) brcmf_dbg(CONN, "ie len: %d\n", params->ie_len); else brcmf_dbg(CONN, "no ie specified\n"); if (params->beacon_interval) brcmf_dbg(CONN, "beacon interval: %d\n", params->beacon_interval); else brcmf_dbg(CONN, "no beacon interval specified\n"); if (params->basic_rates) brcmf_dbg(CONN, "basic rates: %08X\n", params->basic_rates); else brcmf_dbg(CONN, "no basic rates specified\n"); if (params->privacy) brcmf_dbg(CONN, "privacy required\n"); else brcmf_dbg(CONN, "no privacy required\n"); /* Configure Privacy for starter */ if (params->privacy) wsec |= WEP_ENABLED; err = brcmf_fil_iovar_int_set(ifp, "wsec", wsec); if (err) { bphy_err(drvr, "wsec failed (%d)\n", err); goto done; } /* Configure Beacon Interval for starter */ if (params->beacon_interval) bcnprd = params->beacon_interval; else bcnprd = 100; err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_BCNPRD, bcnprd); if (err) { bphy_err(drvr, "WLC_SET_BCNPRD failed (%d)\n", err); goto done; } /* Configure required join parameter */ memset(&join_params, 0, sizeof(struct brcmf_join_params)); /* SSID */ ssid_len = min_t(u32, params->ssid_len, IEEE80211_MAX_SSID_LEN); memcpy(join_params.ssid_le.SSID, params->ssid, ssid_len); join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len); join_params_size = sizeof(join_params.ssid_le); /* BSSID */ if (params->bssid) { memcpy(join_params.params_le.bssid, params->bssid, ETH_ALEN); join_params_size += BRCMF_ASSOC_PARAMS_FIXED_SIZE; memcpy(profile->bssid, params->bssid, ETH_ALEN); } else { eth_broadcast_addr(join_params.params_le.bssid); eth_zero_addr(profile->bssid); } /* Channel */ if (params->chandef.chan) { u32 target_channel; cfg->channel = ieee80211_frequency_to_channel( params->chandef.chan->center_freq); if (params->channel_fixed) { /* adding chanspec */ chanspec = chandef_to_chanspec(&cfg->d11inf, ¶ms->chandef); join_params.params_le.chanspec_list[0] = cpu_to_le16(chanspec); join_params.params_le.chanspec_num = cpu_to_le32(1); join_params_size += sizeof(join_params.params_le); } /* set channel for starter */ target_channel = cfg->channel; err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_CHANNEL, target_channel); if (err) { bphy_err(drvr, "WLC_SET_CHANNEL failed (%d)\n", err); goto done; } } else cfg->channel = 0; cfg->ibss_starter = false; err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, &join_params, join_params_size); if (err) { bphy_err(drvr, "WLC_SET_SSID failed (%d)\n", err); goto done; } done: if (err) clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *ndev) { struct brcmf_if *ifp = netdev_priv(ndev); brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) { /* When driver is being unloaded, it can end up here. If an * error is returned then later on a debug trace in the wireless * core module will be printed. To avoid this 0 is returned. */ return 0; } brcmf_link_down(ifp->vif, WLAN_REASON_DEAUTH_LEAVING, true); brcmf_net_setcarrier(ifp, false); brcmf_dbg(TRACE, "Exit\n"); return 0; } static s32 brcmf_set_wpa_version(struct net_device *ndev, struct cfg80211_connect_params *sme) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_security *sec; s32 val; s32 err; if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) { val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) { if (drvr->bus_if->fwvid == BRCMF_FWVENDOR_CYW && sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_SAE) val = WPA3_AUTH_SAE_PSK; else val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) { val = WPA3_AUTH_SAE_PSK; } else { val = WPA_AUTH_DISABLED; } brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", val); if (err) { bphy_err(drvr, "set wpa_auth failed (%d)\n", err); return err; } sec = &profile->sec; sec->wpa_versions = sme->crypto.wpa_versions; return err; } static s32 brcmf_set_auth_type(struct net_device *ndev, struct cfg80211_connect_params *sme) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_security *sec; s32 val = 0; s32 err = 0; switch (sme->auth_type) { case NL80211_AUTHTYPE_OPEN_SYSTEM: val = 0; brcmf_dbg(CONN, "open system\n"); break; case NL80211_AUTHTYPE_SHARED_KEY: val = 1; brcmf_dbg(CONN, "shared key\n"); break; case NL80211_AUTHTYPE_SAE: val = 3; brcmf_dbg(CONN, "SAE authentication\n"); break; default: val = 2; brcmf_dbg(CONN, "automatic, auth type (%d)\n", sme->auth_type); break; } err = brcmf_fil_bsscfg_int_set(ifp, "auth", val); if (err) { bphy_err(drvr, "set auth failed (%d)\n", err); return err; } sec = &profile->sec; sec->auth_type = sme->auth_type; return err; } static s32 brcmf_set_wsec_mode(struct net_device *ndev, struct cfg80211_connect_params *sme) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_security *sec; s32 pval = 0; s32 gval = 0; s32 wsec; s32 err = 0; if (sme->crypto.n_ciphers_pairwise) { switch (sme->crypto.ciphers_pairwise[0]) { case WLAN_CIPHER_SUITE_WEP40: case WLAN_CIPHER_SUITE_WEP104: pval = WEP_ENABLED; break; case WLAN_CIPHER_SUITE_TKIP: pval = TKIP_ENABLED; break; case WLAN_CIPHER_SUITE_CCMP: pval = AES_ENABLED; break; case WLAN_CIPHER_SUITE_AES_CMAC: pval = AES_ENABLED; break; default: bphy_err(drvr, "invalid cipher pairwise (%d)\n", sme->crypto.ciphers_pairwise[0]); return -EINVAL; } } if (sme->crypto.cipher_group) { switch (sme->crypto.cipher_group) { case WLAN_CIPHER_SUITE_WEP40: case WLAN_CIPHER_SUITE_WEP104: gval = WEP_ENABLED; break; case WLAN_CIPHER_SUITE_TKIP: gval = TKIP_ENABLED; break; case WLAN_CIPHER_SUITE_CCMP: gval = AES_ENABLED; break; case WLAN_CIPHER_SUITE_AES_CMAC: gval = AES_ENABLED; break; default: bphy_err(drvr, "invalid cipher group (%d)\n", sme->crypto.cipher_group); return -EINVAL; } } brcmf_dbg(CONN, "pval (%d) gval (%d)\n", pval, gval); /* In case of privacy, but no security and WPS then simulate */ /* setting AES. WPS-2.0 allows no security */ if (brcmf_find_wpsie(sme->ie, sme->ie_len) && !pval && !gval && sme->privacy) pval = AES_ENABLED; wsec = pval | gval; err = brcmf_fil_bsscfg_int_set(ifp, "wsec", wsec); if (err) { bphy_err(drvr, "error (%d)\n", err); return err; } sec = &profile->sec; sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0]; sec->cipher_group = sme->crypto.cipher_group; return err; } static s32 brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct brcmf_pub *drvr = ifp->drvr; s32 val; s32 err; const struct brcmf_tlv *rsn_ie; const u8 *ie; u32 ie_len; u32 offset; u16 rsn_cap; u32 mfp; u16 count; profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE; profile->is_ft = false; if (!sme->crypto.n_akm_suites) return 0; err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), "wpa_auth", &val); if (err) { bphy_err(drvr, "could not get wpa_auth (%d)\n", err); return err; } if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) { switch (sme->crypto.akm_suites[0]) { case WLAN_AKM_SUITE_8021X: val = WPA_AUTH_UNSPECIFIED; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; break; case WLAN_AKM_SUITE_PSK: val = WPA_AUTH_PSK; break; default: bphy_err(drvr, "invalid akm suite (%d)\n", sme->crypto.akm_suites[0]); return -EINVAL; } } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) { switch (sme->crypto.akm_suites[0]) { case WLAN_AKM_SUITE_8021X: val = WPA2_AUTH_UNSPECIFIED; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; break; case WLAN_AKM_SUITE_8021X_SHA256: val = WPA2_AUTH_1X_SHA256; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; break; case WLAN_AKM_SUITE_PSK_SHA256: val = WPA2_AUTH_PSK_SHA256; break; case WLAN_AKM_SUITE_PSK: val = WPA2_AUTH_PSK; break; case WLAN_AKM_SUITE_FT_8021X: val = WPA2_AUTH_UNSPECIFIED | WPA2_AUTH_FT; profile->is_ft = true; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; break; case WLAN_AKM_SUITE_FT_PSK: val = WPA2_AUTH_PSK | WPA2_AUTH_FT; profile->is_ft = true; break; default: bphy_err(drvr, "invalid akm suite (%d)\n", sme->crypto.akm_suites[0]); return -EINVAL; } } else if (val & WPA3_AUTH_SAE_PSK) { switch (sme->crypto.akm_suites[0]) { case WLAN_AKM_SUITE_SAE: val = WPA3_AUTH_SAE_PSK; break; case WLAN_AKM_SUITE_FT_OVER_SAE: val = WPA3_AUTH_SAE_PSK | WPA2_AUTH_FT; profile->is_ft = true; break; default: bphy_err(drvr, "invalid akm suite (%d)\n", sme->crypto.akm_suites[0]); return -EINVAL; } if (sme->crypto.sae_pwd) { profile->use_fwsup = BRCMF_PROFILE_FWSUP_SAE; } } if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X) brcmf_dbg(INFO, "using 1X offload\n"); if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) brcmf_dbg(INFO, "using SAE offload\n"); if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP)) goto skip_mfp_config; /* The MFP mode (1 or 2) needs to be determined, parse IEs. The * IE will not be verified, just a quick search for MFP config */ rsn_ie = brcmf_parse_tlvs((const u8 *)sme->ie, sme->ie_len, WLAN_EID_RSN); if (!rsn_ie) goto skip_mfp_config; ie = (const u8 *)rsn_ie; ie_len = rsn_ie->len + TLV_HDR_LEN; /* Skip unicast suite */ offset = TLV_HDR_LEN + WPA_IE_VERSION_LEN + WPA_IE_MIN_OUI_LEN; if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) goto skip_mfp_config; /* Skip multicast suite */ count = ie[offset] + (ie[offset + 1] << 8); offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN); if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) goto skip_mfp_config; /* Skip auth key management suite(s) */ count = ie[offset] + (ie[offset + 1] << 8); offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN); if (offset + WPA_IE_SUITE_COUNT_LEN > ie_len) goto skip_mfp_config; /* Ready to read capabilities */ mfp = BRCMF_MFP_NONE; rsn_cap = ie[offset] + (ie[offset + 1] << 8); if (rsn_cap & RSN_CAP_MFPR_MASK) mfp = BRCMF_MFP_REQUIRED; else if (rsn_cap & RSN_CAP_MFPC_MASK) mfp = BRCMF_MFP_CAPABLE; brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "mfp", mfp); skip_mfp_config: brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); if (err) { bphy_err(drvr, "could not set wpa_auth (%d)\n", err); return err; } return err; } static s32 brcmf_set_sharedkey(struct net_device *ndev, struct cfg80211_connect_params *sme) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); struct brcmf_cfg80211_security *sec; struct brcmf_wsec_key key; s32 val; s32 err = 0; brcmf_dbg(CONN, "key len (%d)\n", sme->key_len); if (sme->key_len == 0) return 0; sec = &profile->sec; brcmf_dbg(CONN, "wpa_versions 0x%x cipher_pairwise 0x%x\n", sec->wpa_versions, sec->cipher_pairwise); if (sec->wpa_versions & (NL80211_WPA_VERSION_1 | NL80211_WPA_VERSION_2 | NL80211_WPA_VERSION_3)) return 0; if (!(sec->cipher_pairwise & (WLAN_CIPHER_SUITE_WEP40 | WLAN_CIPHER_SUITE_WEP104))) return 0; memset(&key, 0, sizeof(key)); key.len = (u32) sme->key_len; key.index = (u32) sme->key_idx; if (key.len > sizeof(key.data)) { bphy_err(drvr, "Too long key length (%u)\n", key.len); return -EINVAL; } memcpy(key.data, sme->key, key.len); key.flags = BRCMF_PRIMARY_KEY; switch (sec->cipher_pairwise) { case WLAN_CIPHER_SUITE_WEP40: key.algo = CRYPTO_ALGO_WEP1; break; case WLAN_CIPHER_SUITE_WEP104: key.algo = CRYPTO_ALGO_WEP128; break; default: bphy_err(drvr, "Invalid algorithm (%d)\n", sme->crypto.ciphers_pairwise[0]); return -EINVAL; } /* Set the new key/index */ brcmf_dbg(CONN, "key length (%d) key index (%d) algo (%d)\n", key.len, key.index, key.algo); brcmf_dbg(CONN, "key \"%s\"\n", key.data); err = send_key_to_dongle(ifp, &key); if (err) return err; if (sec->auth_type == NL80211_AUTHTYPE_SHARED_KEY) { brcmf_dbg(CONN, "set auth_type to shared key\n"); val = WL_AUTH_SHARED_KEY; /* shared key */ err = brcmf_fil_bsscfg_int_set(ifp, "auth", val); if (err) bphy_err(drvr, "set auth failed (%d)\n", err); } return err; } static enum nl80211_auth_type brcmf_war_auth_type(struct brcmf_if *ifp, enum nl80211_auth_type type) { if (type == NL80211_AUTHTYPE_AUTOMATIC && brcmf_feat_is_quirk_enabled(ifp, BRCMF_FEAT_QUIRK_AUTO_AUTH)) { brcmf_dbg(CONN, "WAR: use OPEN instead of AUTO\n"); type = NL80211_AUTHTYPE_OPEN_SYSTEM; } return type; } static void brcmf_set_join_pref(struct brcmf_if *ifp, struct cfg80211_bss_selection *bss_select) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_join_pref_params join_pref_params[2]; enum nl80211_band band; int err, i = 0; join_pref_params[i].len = 2; join_pref_params[i].rssi_gain = 0; if (bss_select->behaviour != NL80211_BSS_SELECT_ATTR_BAND_PREF) brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_ASSOC_PREFER, WLC_BAND_AUTO); switch (bss_select->behaviour) { case __NL80211_BSS_SELECT_ATTR_INVALID: brcmf_c_set_joinpref_default(ifp); return; case NL80211_BSS_SELECT_ATTR_BAND_PREF: join_pref_params[i].type = BRCMF_JOIN_PREF_BAND; band = bss_select->param.band_pref; join_pref_params[i].band = nl80211_band_to_fwil(band); i++; break; case NL80211_BSS_SELECT_ATTR_RSSI_ADJUST: join_pref_params[i].type = BRCMF_JOIN_PREF_RSSI_DELTA; band = bss_select->param.adjust.band; join_pref_params[i].band = nl80211_band_to_fwil(band); join_pref_params[i].rssi_gain = bss_select->param.adjust.delta; i++; break; case NL80211_BSS_SELECT_ATTR_RSSI: default: break; } join_pref_params[i].type = BRCMF_JOIN_PREF_RSSI; join_pref_params[i].len = 2; join_pref_params[i].rssi_gain = 0; join_pref_params[i].band = 0; err = brcmf_fil_iovar_data_set(ifp, "join_pref", join_pref_params, sizeof(join_pref_params)); if (err) bphy_err(drvr, "Set join_pref error (%d)\n", err); } static s32 brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_connect_params *sme) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct ieee80211_channel *chan = sme->channel; struct brcmf_pub *drvr = ifp->drvr; struct brcmf_join_params join_params; size_t join_params_size; const struct brcmf_tlv *rsn_ie; const struct brcmf_vs_tlv *wpa_ie; const void *ie; u32 ie_len; struct brcmf_ext_join_params_le *ext_join_params; u16 chanspec; s32 err = 0; u32 ssid_len; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) return -EIO; if (!sme->ssid) { bphy_err(drvr, "Invalid ssid\n"); return -EOPNOTSUPP; } if (sme->channel_hint) chan = sme->channel_hint; if (sme->bssid_hint) sme->bssid = sme->bssid_hint; if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) { /* A normal (non P2P) connection request setup. */ ie = NULL; ie_len = 0; /* find the WPA_IE */ #if defined(__linux__) wpa_ie = brcmf_find_wpaie((u8 *)sme->ie, sme->ie_len); #elif defined(__FreeBSD__) wpa_ie = brcmf_find_wpaie(sme->ie, sme->ie_len); #endif if (wpa_ie) { ie = wpa_ie; ie_len = wpa_ie->len + TLV_HDR_LEN; } else { /* find the RSN_IE */ rsn_ie = brcmf_parse_tlvs((const u8 *)sme->ie, sme->ie_len, WLAN_EID_RSN); if (rsn_ie) { ie = rsn_ie; ie_len = rsn_ie->len + TLV_HDR_LEN; } } brcmf_fil_iovar_data_set(ifp, "wpaie", ie, ie_len); } err = brcmf_vif_set_mgmt_ie(ifp->vif, BRCMF_VNDR_IE_ASSOCREQ_FLAG, sme->ie, sme->ie_len); if (err) bphy_err(drvr, "Set Assoc REQ IE Failed\n"); else brcmf_dbg(TRACE, "Applied Vndr IEs for Assoc request\n"); set_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); if (chan) { cfg->channel = ieee80211_frequency_to_channel(chan->center_freq); chanspec = channel_to_chanspec(&cfg->d11inf, chan); brcmf_dbg(CONN, "channel=%d, center_req=%d, chanspec=0x%04x\n", cfg->channel, chan->center_freq, chanspec); } else { cfg->channel = 0; chanspec = 0; } #if defined(__linux__) brcmf_dbg(INFO, "ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len); #elif defined(__FreeBSD__) brcmf_dbg(INFO, "ie (%p), ie_len (%u)\n", sme->ie, sme->ie_len); #endif err = brcmf_set_wpa_version(ndev, sme); if (err) { bphy_err(drvr, "wl_set_wpa_version failed (%d)\n", err); goto done; } sme->auth_type = brcmf_war_auth_type(ifp, sme->auth_type); err = brcmf_set_auth_type(ndev, sme); if (err) { bphy_err(drvr, "wl_set_auth_type failed (%d)\n", err); goto done; } err = brcmf_set_wsec_mode(ndev, sme); if (err) { bphy_err(drvr, "wl_set_set_cipher failed (%d)\n", err); goto done; } err = brcmf_set_key_mgmt(ndev, sme); if (err) { bphy_err(drvr, "wl_set_key_mgmt failed (%d)\n", err); goto done; } err = brcmf_set_sharedkey(ndev, sme); if (err) { bphy_err(drvr, "brcmf_set_sharedkey failed (%d)\n", err); goto done; } if (sme->crypto.psk && profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) { err = -EINVAL; goto done; } brcmf_dbg(INFO, "using PSK offload\n"); profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; } if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { /* enable firmware supplicant for this interface */ err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); if (err < 0) { bphy_err(drvr, "failed to enable fw supplicant\n"); goto done; } } if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) err = brcmf_set_pmk(ifp, sme->crypto.psk, BRCMF_WSEC_MAX_PSK_LEN); else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { /* clean up user-space RSNE */ err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0); if (err) { bphy_err(drvr, "failed to clean up user-space RSNE\n"); goto done; } err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto); if (!err && sme->crypto.psk) err = brcmf_set_pmk(ifp, sme->crypto.psk, BRCMF_WSEC_MAX_PSK_LEN); } if (err) goto done; /* Join with specific BSSID and cached SSID * If SSID is zero join based on BSSID only */ join_params_size = offsetof(struct brcmf_ext_join_params_le, assoc_le) + offsetof(struct brcmf_assoc_params_le, chanspec_list); if (cfg->channel) join_params_size += sizeof(u16); ext_join_params = kzalloc(sizeof(*ext_join_params), GFP_KERNEL); if (ext_join_params == NULL) { err = -ENOMEM; goto done; } ssid_len = min_t(u32, sme->ssid_len, IEEE80211_MAX_SSID_LEN); ext_join_params->ssid_le.SSID_len = cpu_to_le32(ssid_len); memcpy(&ext_join_params->ssid_le.SSID, sme->ssid, ssid_len); if (ssid_len < IEEE80211_MAX_SSID_LEN) brcmf_dbg(CONN, "SSID \"%s\", len (%d)\n", ext_join_params->ssid_le.SSID, ssid_len); /* Set up join scan parameters */ ext_join_params->scan_le.scan_type = -1; ext_join_params->scan_le.home_time = cpu_to_le32(-1); if (sme->bssid) memcpy(&ext_join_params->assoc_le.bssid, sme->bssid, ETH_ALEN); else eth_broadcast_addr(ext_join_params->assoc_le.bssid); if (cfg->channel) { ext_join_params->assoc_le.chanspec_num = cpu_to_le32(1); ext_join_params->assoc_le.chanspec_list[0] = cpu_to_le16(chanspec); /* Increase dwell time to receive probe response or detect * beacon from target AP at a noisy air only during connect * command. */ ext_join_params->scan_le.active_time = cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS); ext_join_params->scan_le.passive_time = cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS); /* To sync with presence period of VSDB GO send probe request * more frequently. Probe request will be stopped when it gets * probe response from target AP/GO. */ ext_join_params->scan_le.nprobes = cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS / BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS); } else { ext_join_params->scan_le.active_time = cpu_to_le32(-1); ext_join_params->scan_le.passive_time = cpu_to_le32(-1); ext_join_params->scan_le.nprobes = cpu_to_le32(-1); } brcmf_set_join_pref(ifp, &sme->bss_select); err = brcmf_fil_bsscfg_data_set(ifp, "join", ext_join_params, join_params_size); kfree(ext_join_params); if (!err) /* This is it. join command worked, we are done */ goto done; /* join command failed, fallback to set ssid */ memset(&join_params, 0, sizeof(join_params)); join_params_size = sizeof(join_params.ssid_le); memcpy(&join_params.ssid_le.SSID, sme->ssid, ssid_len); join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len); if (sme->bssid) memcpy(join_params.params_le.bssid, sme->bssid, ETH_ALEN); else eth_broadcast_addr(join_params.params_le.bssid); if (cfg->channel) { join_params.params_le.chanspec_list[0] = cpu_to_le16(chanspec); join_params.params_le.chanspec_num = cpu_to_le32(1); join_params_size += sizeof(join_params.params_le); } err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, &join_params, join_params_size); if (err) bphy_err(drvr, "BRCMF_C_SET_SSID failed (%d)\n", err); done: if (err) clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev, u16 reason_code) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct brcmf_pub *drvr = cfg->pub; struct brcmf_scb_val_le scbval; s32 err = 0; brcmf_dbg(TRACE, "Enter. Reason code = %d\n", reason_code); if (!check_vif_up(ifp->vif)) return -EIO; clear_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); clear_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &ifp->vif->sme_state); clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &ifp->vif->sme_state); cfg80211_disconnected(ndev, reason_code, NULL, 0, true, GFP_KERNEL); memcpy(&scbval.ea, &profile->bssid, ETH_ALEN); scbval.val = cpu_to_le32(reason_code); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_DISASSOC, &scbval, sizeof(scbval)); if (err) bphy_err(drvr, "error (%d)\n", err); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, int radio_idx, enum nl80211_tx_power_setting type, s32 mbm) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = cfg_to_ndev(cfg); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; s32 err; s32 disable; u32 qdbm = 127; brcmf_dbg(TRACE, "Enter %d %d\n", type, mbm); if (!check_vif_up(ifp->vif)) return -EIO; switch (type) { case NL80211_TX_POWER_AUTOMATIC: break; case NL80211_TX_POWER_LIMITED: case NL80211_TX_POWER_FIXED: if (mbm < 0) { bphy_err(drvr, "TX_POWER_FIXED - dbm is negative\n"); err = -EINVAL; goto done; } qdbm = MBM_TO_DBM(4 * mbm); if (qdbm > 127) qdbm = 127; qdbm |= WL_TXPWR_OVERRIDE; break; default: bphy_err(drvr, "Unsupported type %d\n", type); err = -EINVAL; goto done; } /* Make sure radio is off or on as far as software is concerned */ disable = WL_RADIO_SW_DISABLE << 16; err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_RADIO, disable); if (err) bphy_err(drvr, "WLC_SET_RADIO error (%d)\n", err); err = brcmf_fil_iovar_int_set(ifp, "qtxpower", qdbm); if (err) bphy_err(drvr, "qtxpower error (%d)\n", err); done: brcmf_dbg(TRACE, "Exit %d (qdbm)\n", qdbm & ~WL_TXPWR_OVERRIDE); return err; } static s32 brcmf_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, int radio_idx, unsigned int link_id, s32 *dbm) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_cfg80211_vif *vif = wdev_to_vif(wdev); struct brcmf_pub *drvr = cfg->pub; s32 qdbm; s32 err; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(vif)) return -EIO; err = brcmf_fil_iovar_int_get(vif->ifp, "qtxpower", &qdbm); if (err) { bphy_err(drvr, "error (%d)\n", err); goto done; } *dbm = (qdbm & ~WL_TXPWR_OVERRIDE) / 4; done: brcmf_dbg(TRACE, "Exit (0x%x %d)\n", qdbm, *dbm); return err; } static s32 brcmf_cfg80211_config_default_key(struct wiphy *wiphy, struct net_device *ndev, int link_id, u8 key_idx, bool unicast, bool multicast) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = ifp->drvr; u32 index; u32 wsec; s32 err = 0; brcmf_dbg(TRACE, "Enter\n"); brcmf_dbg(CONN, "key index (%d)\n", key_idx); if (!check_vif_up(ifp->vif)) return -EIO; err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec); if (err) { bphy_err(drvr, "WLC_GET_WSEC error (%d)\n", err); goto done; } if (wsec & WEP_ENABLED) { /* Just select a new current key */ index = key_idx; err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_KEY_PRIMARY, index); if (err) bphy_err(drvr, "error (%d)\n", err); } done: brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev, int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_wsec_key *key; s32 err; brcmf_dbg(TRACE, "Enter\n"); brcmf_dbg(CONN, "key index (%d)\n", key_idx); if (!check_vif_up(ifp->vif)) return -EIO; if (key_idx >= BRCMF_MAX_DEFAULT_KEYS) { /* we ignore this key index in this case */ return -EINVAL; } key = &ifp->vif->profile.key[key_idx]; if (key->algo == CRYPTO_ALGO_OFF) { brcmf_dbg(CONN, "Ignore clearing of (never configured) key\n"); return -EINVAL; } memset(key, 0, sizeof(*key)); key->index = (u32)key_idx; key->flags = BRCMF_PRIMARY_KEY; /* Clear the key/index */ err = send_key_to_dongle(ifp, key); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr, struct key_params *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; struct brcmf_wsec_key *key; s32 val; s32 wsec; s32 err; u8 keybuf[8]; bool ext_key; brcmf_dbg(TRACE, "Enter\n"); brcmf_dbg(CONN, "key index (%d)\n", key_idx); if (!check_vif_up(ifp->vif)) return -EIO; if (key_idx >= BRCMF_MAX_DEFAULT_KEYS) { /* we ignore this key index in this case */ bphy_err(drvr, "invalid key index (%d)\n", key_idx); return -EINVAL; } if (params->key_len == 0) return brcmf_cfg80211_del_key(wiphy, ndev, -1, key_idx, pairwise, mac_addr); if (params->key_len > sizeof(key->data)) { bphy_err(drvr, "Too long key length (%u)\n", params->key_len); return -EINVAL; } ext_key = false; if (mac_addr && (params->cipher != WLAN_CIPHER_SUITE_WEP40) && (params->cipher != WLAN_CIPHER_SUITE_WEP104)) { brcmf_dbg(TRACE, "Ext key, mac %pM", mac_addr); ext_key = true; } key = &ifp->vif->profile.key[key_idx]; memset(key, 0, sizeof(*key)); if ((ext_key) && (!is_multicast_ether_addr(mac_addr))) #if defined(__linux__) memcpy((char *)&key->ea, (void *)mac_addr, ETH_ALEN); #elif defined(__FreeBSD__) memcpy((char *)&key->ea, mac_addr, ETH_ALEN); #endif key->len = params->key_len; key->index = key_idx; memcpy(key->data, params->key, key->len); if (!ext_key) key->flags = BRCMF_PRIMARY_KEY; if (params->seq && params->seq_len == 6) { /* rx iv */ #if defined(__linux__) u8 *ivptr; ivptr = (u8 *)params->seq; #elif defined(__FreeBSD__) const u8 *ivptr; ivptr = params->seq; #endif key->rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) | (ivptr[3] << 8) | ivptr[2]; key->rxiv.lo = (ivptr[1] << 8) | ivptr[0]; key->iv_initialized = true; } switch (params->cipher) { case WLAN_CIPHER_SUITE_WEP40: key->algo = CRYPTO_ALGO_WEP1; val = WEP_ENABLED; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n"); break; case WLAN_CIPHER_SUITE_WEP104: key->algo = CRYPTO_ALGO_WEP128; val = WEP_ENABLED; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n"); break; case WLAN_CIPHER_SUITE_TKIP: if (!brcmf_is_apmode(ifp->vif)) { brcmf_dbg(CONN, "Swapping RX/TX MIC key\n"); memcpy(keybuf, &key->data[24], sizeof(keybuf)); memcpy(&key->data[24], &key->data[16], sizeof(keybuf)); memcpy(&key->data[16], keybuf, sizeof(keybuf)); } key->algo = CRYPTO_ALGO_TKIP; val = TKIP_ENABLED; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n"); break; case WLAN_CIPHER_SUITE_AES_CMAC: key->algo = CRYPTO_ALGO_AES_CCM; val = AES_ENABLED; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n"); break; case WLAN_CIPHER_SUITE_CCMP: key->algo = CRYPTO_ALGO_AES_CCM; val = AES_ENABLED; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_CCMP\n"); break; default: bphy_err(drvr, "Invalid cipher (0x%x)\n", params->cipher); err = -EINVAL; goto done; } err = send_key_to_dongle(ifp, key); if (ext_key || err) goto done; err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec); if (err) { bphy_err(drvr, "get wsec error (%d)\n", err); goto done; } wsec |= val; err = brcmf_fil_bsscfg_int_set(ifp, "wsec", wsec); if (err) { bphy_err(drvr, "set wsec error (%d)\n", err); goto done; } done: brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev, int link_id, u8 key_idx, bool pairwise, const u8 *mac_addr, void *cookie, void (*callback)(void *cookie, struct key_params *params)) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct key_params params; struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_security *sec; s32 wsec; s32 err = 0; brcmf_dbg(TRACE, "Enter\n"); brcmf_dbg(CONN, "key index (%d)\n", key_idx); if (!check_vif_up(ifp->vif)) return -EIO; memset(¶ms, 0, sizeof(params)); err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec); if (err) { bphy_err(drvr, "WLC_GET_WSEC error (%d)\n", err); /* Ignore this error, may happen during DISASSOC */ err = -EAGAIN; goto done; } if (wsec & WEP_ENABLED) { sec = &profile->sec; if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP40) { params.cipher = WLAN_CIPHER_SUITE_WEP40; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n"); } else if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP104) { params.cipher = WLAN_CIPHER_SUITE_WEP104; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n"); } } else if (wsec & TKIP_ENABLED) { params.cipher = WLAN_CIPHER_SUITE_TKIP; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n"); } else if (wsec & AES_ENABLED) { params.cipher = WLAN_CIPHER_SUITE_AES_CMAC; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n"); } else { bphy_err(drvr, "Invalid algo (0x%x)\n", wsec); err = -EINVAL; goto done; } callback(cookie, ¶ms); done: brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_config_default_mgmt_key(struct wiphy *wiphy, struct net_device *ndev, int link_id, u8 key_idx) { struct brcmf_if *ifp = netdev_priv(ndev); brcmf_dbg(TRACE, "Enter key_idx %d\n", key_idx); if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP)) return 0; brcmf_dbg(INFO, "Not supported\n"); return -EOPNOTSUPP; } static void brcmf_cfg80211_reconfigure_wep(struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; s32 err; u8 key_idx; struct brcmf_wsec_key *key; s32 wsec; for (key_idx = 0; key_idx < BRCMF_MAX_DEFAULT_KEYS; key_idx++) { key = &ifp->vif->profile.key[key_idx]; if ((key->algo == CRYPTO_ALGO_WEP1) || (key->algo == CRYPTO_ALGO_WEP128)) break; } if (key_idx == BRCMF_MAX_DEFAULT_KEYS) return; err = send_key_to_dongle(ifp, key); if (err) { bphy_err(drvr, "Setting WEP key failed (%d)\n", err); return; } err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec); if (err) { bphy_err(drvr, "get wsec error (%d)\n", err); return; } wsec |= WEP_ENABLED; err = brcmf_fil_bsscfg_int_set(ifp, "wsec", wsec); if (err) bphy_err(drvr, "set wsec error (%d)\n", err); } static void brcmf_convert_sta_flags(u32 fw_sta_flags, struct station_info *si) { struct nl80211_sta_flag_update *sfu; brcmf_dbg(TRACE, "flags %08x\n", fw_sta_flags); si->filled |= BIT_ULL(NL80211_STA_INFO_STA_FLAGS); sfu = &si->sta_flags; sfu->mask = BIT(NL80211_STA_FLAG_WME) | BIT(NL80211_STA_FLAG_AUTHENTICATED) | BIT(NL80211_STA_FLAG_ASSOCIATED) | BIT(NL80211_STA_FLAG_AUTHORIZED); if (fw_sta_flags & BRCMF_STA_WME) sfu->set |= BIT(NL80211_STA_FLAG_WME); if (fw_sta_flags & BRCMF_STA_AUTHE) sfu->set |= BIT(NL80211_STA_FLAG_AUTHENTICATED); if (fw_sta_flags & BRCMF_STA_ASSOC) sfu->set |= BIT(NL80211_STA_FLAG_ASSOCIATED); if (fw_sta_flags & BRCMF_STA_AUTHO) sfu->set |= BIT(NL80211_STA_FLAG_AUTHORIZED); } static void brcmf_fill_bss_param(struct brcmf_if *ifp, struct station_info *si) { struct brcmf_pub *drvr = ifp->drvr; struct { __le32 len; struct brcmf_bss_info_le bss_le; } *buf; u16 capability; int err; buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); if (!buf) return; buf->len = cpu_to_le32(WL_BSS_INFO_MAX); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, buf, WL_BSS_INFO_MAX); if (err) { bphy_err(drvr, "Failed to get bss info (%d)\n", err); goto out_kfree; } si->filled |= BIT_ULL(NL80211_STA_INFO_BSS_PARAM); si->bss_param.beacon_interval = le16_to_cpu(buf->bss_le.beacon_period); si->bss_param.dtim_period = buf->bss_le.dtim_period; capability = le16_to_cpu(buf->bss_le.capability); if (capability & IEEE80211_HT_STBC_PARAM_DUAL_CTS_PROT) si->bss_param.flags |= BSS_PARAM_FLAGS_CTS_PROT; if (capability & WLAN_CAPABILITY_SHORT_PREAMBLE) si->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_PREAMBLE; if (capability & WLAN_CAPABILITY_SHORT_SLOT_TIME) si->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_SLOT_TIME; out_kfree: kfree(buf); } static s32 brcmf_cfg80211_get_station_ibss(struct brcmf_if *ifp, struct station_info *sinfo) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_scb_val_le scbval; struct brcmf_pktcnt_le pktcnt; s32 err; u32 rate; u32 rssi; /* Get the current tx rate */ err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_RATE, &rate); if (err < 0) { bphy_err(drvr, "BRCMF_C_GET_RATE error (%d)\n", err); return err; } sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); sinfo->txrate.legacy = rate * 5; memset(&scbval, 0, sizeof(scbval)); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_RSSI, &scbval, sizeof(scbval)); if (err) { bphy_err(drvr, "BRCMF_C_GET_RSSI error (%d)\n", err); return err; } rssi = le32_to_cpu(scbval.val); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL); sinfo->signal = rssi; err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_GET_PKTCNTS, &pktcnt, sizeof(pktcnt)); if (err) { bphy_err(drvr, "BRCMF_C_GET_GET_PKTCNTS error (%d)\n", err); return err; } sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS) | BIT_ULL(NL80211_STA_INFO_RX_DROP_MISC) | BIT_ULL(NL80211_STA_INFO_TX_PACKETS) | BIT_ULL(NL80211_STA_INFO_TX_FAILED); sinfo->rx_packets = le32_to_cpu(pktcnt.rx_good_pkt); sinfo->rx_dropped_misc = le32_to_cpu(pktcnt.rx_bad_pkt); sinfo->tx_packets = le32_to_cpu(pktcnt.tx_good_pkt); sinfo->tx_failed = le32_to_cpu(pktcnt.tx_bad_pkt); return 0; } static s32 brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, const u8 *mac, struct station_info *sinfo) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; struct brcmf_scb_val_le scb_val; s32 err = 0; struct brcmf_sta_info_le sta_info_le; u32 sta_flags; u32 is_tdls_peer; s32 total_rssi_avg = 0; s32 total_rssi = 0; s32 count_rssi = 0; int rssi; u32 i; brcmf_dbg(TRACE, "Enter, MAC %pM\n", mac); if (!check_vif_up(ifp->vif)) return -EIO; if (brcmf_is_ibssmode(ifp->vif)) return brcmf_cfg80211_get_station_ibss(ifp, sinfo); memset(&sta_info_le, 0, sizeof(sta_info_le)); memcpy(&sta_info_le, mac, ETH_ALEN); err = brcmf_fil_iovar_data_get(ifp, "tdls_sta_info", &sta_info_le, sizeof(sta_info_le)); is_tdls_peer = !err; if (err) { err = brcmf_fil_iovar_data_get(ifp, "sta_info", &sta_info_le, sizeof(sta_info_le)); if (err < 0) { bphy_err(drvr, "GET STA INFO failed, %d\n", err); goto done; } } brcmf_dbg(TRACE, "version %d\n", le16_to_cpu(sta_info_le.ver)); sinfo->filled = BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME); sinfo->inactive_time = le32_to_cpu(sta_info_le.idle) * 1000; sta_flags = le32_to_cpu(sta_info_le.flags); brcmf_convert_sta_flags(sta_flags, sinfo); sinfo->sta_flags.mask |= BIT(NL80211_STA_FLAG_TDLS_PEER); if (is_tdls_peer) sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_TDLS_PEER); else sinfo->sta_flags.set &= ~BIT(NL80211_STA_FLAG_TDLS_PEER); if (sta_flags & BRCMF_STA_ASSOC) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CONNECTED_TIME); sinfo->connected_time = le32_to_cpu(sta_info_le.in); brcmf_fill_bss_param(ifp, sinfo); } if (sta_flags & BRCMF_STA_SCBSTATS) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED); sinfo->tx_failed = le32_to_cpu(sta_info_le.tx_failures); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS); sinfo->tx_packets = le32_to_cpu(sta_info_le.tx_pkts); sinfo->tx_packets += le32_to_cpu(sta_info_le.tx_mcast_pkts); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS); sinfo->rx_packets = le32_to_cpu(sta_info_le.rx_ucast_pkts); sinfo->rx_packets += le32_to_cpu(sta_info_le.rx_mcast_pkts); if (sinfo->tx_packets) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); sinfo->txrate.legacy = le32_to_cpu(sta_info_le.tx_rate) / 100; } if (sinfo->rx_packets) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BITRATE); sinfo->rxrate.legacy = le32_to_cpu(sta_info_le.rx_rate) / 100; } if (le16_to_cpu(sta_info_le.ver) >= 4) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES); sinfo->tx_bytes = le64_to_cpu(sta_info_le.tx_tot_bytes); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES); sinfo->rx_bytes = le64_to_cpu(sta_info_le.rx_tot_bytes); } for (i = 0; i < BRCMF_ANT_MAX; i++) { if (sta_info_le.rssi[i] == 0 || sta_info_le.rx_lastpkt_rssi[i] == 0) continue; sinfo->chains |= BIT(count_rssi); sinfo->chain_signal[count_rssi] = sta_info_le.rx_lastpkt_rssi[i]; sinfo->chain_signal_avg[count_rssi] = sta_info_le.rssi[i]; total_rssi += sta_info_le.rx_lastpkt_rssi[i]; total_rssi_avg += sta_info_le.rssi[i]; count_rssi++; } if (count_rssi) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG); sinfo->signal = total_rssi / count_rssi; sinfo->signal_avg = total_rssi_avg / count_rssi; } else if (test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state)) { memset(&scb_val, 0, sizeof(scb_val)); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_RSSI, &scb_val, sizeof(scb_val)); if (err) { bphy_err(drvr, "Could not get rssi (%d)\n", err); goto done; } else { rssi = le32_to_cpu(scb_val.val); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL); sinfo->signal = rssi; brcmf_dbg(CONN, "RSSI %d dBm\n", rssi); } } } done: brcmf_dbg(TRACE, "Exit\n"); return err; } static int brcmf_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *ndev, int idx, u8 *mac, struct station_info *sinfo) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; s32 err; brcmf_dbg(TRACE, "Enter, idx %d\n", idx); if (idx == 0) { cfg->assoclist.count = cpu_to_le32(BRCMF_MAX_ASSOCLIST); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_ASSOCLIST, &cfg->assoclist, sizeof(cfg->assoclist)); if (err) { /* GET_ASSOCLIST unsupported by firmware of older chips */ if (err == -EBADE) bphy_info_once(drvr, "BRCMF_C_GET_ASSOCLIST unsupported\n"); else bphy_err(drvr, "BRCMF_C_GET_ASSOCLIST failed, err=%d\n", err); cfg->assoclist.count = 0; return -EOPNOTSUPP; } } if (idx < le32_to_cpu(cfg->assoclist.count)) { memcpy(mac, cfg->assoclist.mac[idx], ETH_ALEN); return brcmf_cfg80211_get_station(wiphy, ndev, mac, sinfo); } return -ENOENT; } static s32 brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev, bool enabled, s32 timeout) { s32 pm; s32 err = 0; struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; brcmf_dbg(TRACE, "Enter\n"); /* * Powersave enable/disable request is coming from the * cfg80211 even before the interface is up. In that * scenario, driver will be storing the power save * preference in cfg struct to apply this to * FW later while initializing the dongle */ cfg->pwr_save = enabled; if (!check_vif_up(ifp->vif)) { brcmf_dbg(INFO, "Device is not ready, storing the value in cfg_info struct\n"); goto done; } pm = enabled ? PM_FAST : PM_OFF; /* Do not enable the power save after assoc if it is a p2p interface */ if (ifp->vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) { brcmf_dbg(INFO, "Do not enable power save for P2P clients\n"); pm = PM_OFF; } brcmf_dbg(INFO, "power save %s\n", (pm ? "enabled" : "disabled")); err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, pm); if (err) { if (err == -ENODEV) bphy_err(drvr, "net_device is not ready yet\n"); else bphy_err(drvr, "error (%d)\n", err); } err = brcmf_fil_iovar_int_set(ifp, "pm2_sleep_ret", min_t(u32, timeout, BRCMF_PS_MAX_TIMEOUT_MS)); if (err) bphy_err(drvr, "Unable to set pm timeout, (%d)\n", err); done: brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_info *cfg, struct brcmf_bss_info_le *bi) { struct wiphy *wiphy = cfg_to_wiphy(cfg); struct brcmf_pub *drvr = cfg->pub; struct cfg80211_bss *bss; enum nl80211_band band; struct brcmu_chan ch; u16 channel; u32 freq; u16 notify_capability; u16 notify_interval; u8 *notify_ie; size_t notify_ielen; struct cfg80211_inform_bss bss_data = {}; if (le32_to_cpu(bi->length) > WL_BSS_INFO_MAX) { bphy_err(drvr, "Bss info is larger than buffer. Discarding\n"); return -EINVAL; } if (!bi->ctl_ch) { ch.chspec = le16_to_cpu(bi->chanspec); cfg->d11inf.decchspec(&ch); bi->ctl_ch = ch.control_ch_num; } channel = bi->ctl_ch; if (channel <= CH_MAX_2G_CHANNEL) band = NL80211_BAND_2GHZ; else band = NL80211_BAND_5GHZ; freq = ieee80211_channel_to_frequency(channel, band); bss_data.chan = ieee80211_get_channel(wiphy, freq); bss_data.boottime_ns = ktime_to_ns(ktime_get_boottime()); notify_capability = le16_to_cpu(bi->capability); notify_interval = le16_to_cpu(bi->beacon_period); notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); notify_ielen = le32_to_cpu(bi->ie_length); bss_data.signal = (s16)le16_to_cpu(bi->RSSI) * 100; brcmf_dbg(CONN, "bssid: %pM\n", bi->BSSID); brcmf_dbg(CONN, "Channel: %d(%d)\n", channel, freq); brcmf_dbg(CONN, "Capability: %X\n", notify_capability); brcmf_dbg(CONN, "Beacon interval: %d\n", notify_interval); brcmf_dbg(CONN, "Signal: %d\n", bss_data.signal); bss = cfg80211_inform_bss_data(wiphy, &bss_data, CFG80211_BSS_FTYPE_UNKNOWN, (const u8 *)bi->BSSID, 0, notify_capability, notify_interval, notify_ie, notify_ielen, GFP_KERNEL); if (!bss) return -ENOMEM; cfg80211_put_bss(wiphy, bss); return 0; } static struct brcmf_bss_info_le * next_bss_le(struct brcmf_scan_results *list, struct brcmf_bss_info_le *bss) { if (bss == NULL) return list->bss_info_le; return (struct brcmf_bss_info_le *)((unsigned long)bss + le32_to_cpu(bss->length)); } static s32 brcmf_inform_bss(struct brcmf_cfg80211_info *cfg) { struct brcmf_pub *drvr = cfg->pub; struct brcmf_scan_results *bss_list; struct brcmf_bss_info_le *bi = NULL; /* must be initialized */ s32 err = 0; int i; bss_list = (struct brcmf_scan_results *)cfg->escan_info.escan_buf; if (bss_list->count != 0 && bss_list->version != BRCMF_BSS_INFO_VERSION) { bphy_err(drvr, "Version %d != WL_BSS_INFO_VERSION\n", bss_list->version); return -EOPNOTSUPP; } brcmf_dbg(SCAN, "scanned AP count (%d)\n", bss_list->count); for (i = 0; i < bss_list->count; i++) { bi = next_bss_le(bss_list, bi); err = brcmf_inform_single_bss(cfg, bi); if (err) break; } return err; } static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, const u8 *bssid) { struct wiphy *wiphy = cfg_to_wiphy(cfg); struct brcmf_pub *drvr = cfg->pub; struct ieee80211_channel *notify_channel; struct brcmf_bss_info_le *bi = NULL; struct ieee80211_supported_band *band; struct cfg80211_bss *bss; struct brcmu_chan ch; u8 *buf = NULL; s32 err = 0; u32 freq; u16 notify_capability; u16 notify_interval; u8 *notify_ie; size_t notify_ielen; s32 notify_signal; brcmf_dbg(TRACE, "Enter\n"); buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); if (buf == NULL) { err = -ENOMEM; goto CleanUp; } *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); err = brcmf_fil_cmd_data_get(netdev_priv(ndev), BRCMF_C_GET_BSS_INFO, buf, WL_BSS_INFO_MAX); if (err) { bphy_err(drvr, "WLC_GET_BSS_INFO failed: %d\n", err); goto CleanUp; } bi = (struct brcmf_bss_info_le *)(buf + 4); ch.chspec = le16_to_cpu(bi->chanspec); cfg->d11inf.decchspec(&ch); if (ch.band == BRCMU_CHAN_BAND_2G) band = wiphy->bands[NL80211_BAND_2GHZ]; else band = wiphy->bands[NL80211_BAND_5GHZ]; freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band); cfg->channel = freq; notify_channel = ieee80211_get_channel(wiphy, freq); notify_capability = le16_to_cpu(bi->capability); notify_interval = le16_to_cpu(bi->beacon_period); notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); notify_ielen = le32_to_cpu(bi->ie_length); notify_signal = (s16)le16_to_cpu(bi->RSSI) * 100; brcmf_dbg(CONN, "channel: %d(%d)\n", ch.control_ch_num, freq); brcmf_dbg(CONN, "capability: %X\n", notify_capability); brcmf_dbg(CONN, "beacon interval: %d\n", notify_interval); brcmf_dbg(CONN, "signal: %d\n", notify_signal); bss = cfg80211_inform_bss(wiphy, notify_channel, CFG80211_BSS_FTYPE_UNKNOWN, bssid, 0, notify_capability, notify_interval, notify_ie, notify_ielen, notify_signal, GFP_KERNEL); if (!bss) { err = -ENOMEM; goto CleanUp; } cfg80211_put_bss(wiphy, bss); CleanUp: kfree(buf); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_update_bss_info(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp) { struct brcmf_pub *drvr = cfg->pub; struct brcmf_bss_info_le *bi = NULL; s32 err = 0; brcmf_dbg(TRACE, "Enter\n"); if (brcmf_is_ibssmode(ifp->vif)) return err; *(__le32 *)cfg->extra_buf = cpu_to_le32(WL_EXTRA_BUF_MAX); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, cfg->extra_buf, WL_EXTRA_BUF_MAX); if (err) { bphy_err(drvr, "Could not get bss info %d\n", err); goto update_bss_info_out; } bi = (struct brcmf_bss_info_le *)(cfg->extra_buf + 4); err = brcmf_inform_single_bss(cfg, bi); update_bss_info_out: brcmf_dbg(TRACE, "Exit"); return err; } void brcmf_abort_scanning(struct brcmf_cfg80211_info *cfg) { struct escan_info *escan = &cfg->escan_info; set_bit(BRCMF_SCAN_STATUS_ABORT, &cfg->scan_status); if (cfg->int_escan_map || cfg->scan_request) { escan->escan_state = WL_ESCAN_STATE_IDLE; brcmf_notify_escan_complete(cfg, escan->ifp, true, true); } clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); clear_bit(BRCMF_SCAN_STATUS_ABORT, &cfg->scan_status); } static void brcmf_cfg80211_escan_timeout_worker(struct work_struct *work) { struct brcmf_cfg80211_info *cfg = container_of(work, struct brcmf_cfg80211_info, escan_timeout_work); brcmf_inform_bss(cfg); brcmf_notify_escan_complete(cfg, cfg->escan_info.ifp, true, true); } static void brcmf_escan_timeout(struct timer_list *t) { struct brcmf_cfg80211_info *cfg = timer_container_of(cfg, t, escan_timeout); struct brcmf_pub *drvr = cfg->pub; if (cfg->int_escan_map || cfg->scan_request) { bphy_err(drvr, "timer expired\n"); schedule_work(&cfg->escan_timeout_work); } } static s32 brcmf_compare_update_same_bss(struct brcmf_cfg80211_info *cfg, struct brcmf_bss_info_le *bss, struct brcmf_bss_info_le *bss_info_le) { struct brcmu_chan ch_bss, ch_bss_info_le; ch_bss.chspec = le16_to_cpu(bss->chanspec); cfg->d11inf.decchspec(&ch_bss); ch_bss_info_le.chspec = le16_to_cpu(bss_info_le->chanspec); cfg->d11inf.decchspec(&ch_bss_info_le); if (!memcmp(&bss_info_le->BSSID, &bss->BSSID, ETH_ALEN) && ch_bss.band == ch_bss_info_le.band && bss_info_le->SSID_len == bss->SSID_len && !memcmp(bss_info_le->SSID, bss->SSID, bss_info_le->SSID_len)) { if ((bss->flags & BRCMF_BSS_RSSI_ON_CHANNEL) == (bss_info_le->flags & BRCMF_BSS_RSSI_ON_CHANNEL)) { s16 bss_rssi = le16_to_cpu(bss->RSSI); s16 bss_info_rssi = le16_to_cpu(bss_info_le->RSSI); /* preserve max RSSI if the measurements are * both on-channel or both off-channel */ if (bss_info_rssi > bss_rssi) bss->RSSI = bss_info_le->RSSI; } else if ((bss->flags & BRCMF_BSS_RSSI_ON_CHANNEL) && (bss_info_le->flags & BRCMF_BSS_RSSI_ON_CHANNEL) == 0) { /* preserve the on-channel rssi measurement * if the new measurement is off channel */ bss->RSSI = bss_info_le->RSSI; bss->flags |= BRCMF_BSS_RSSI_ON_CHANNEL; } return 1; } return 0; } static s32 brcmf_cfg80211_escan_handler(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_info *cfg = drvr->config; s32 status; struct brcmf_escan_result_le *escan_result_le; u32 escan_buflen; struct brcmf_bss_info_le *bss_info_le; struct brcmf_bss_info_le *bss = NULL; u32 bi_length; struct brcmf_scan_results *list; u32 i; bool aborted; status = e->status; if (status == BRCMF_E_STATUS_ABORT) goto exit; if (!test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) { bphy_err(drvr, "scan not ready, bsscfgidx=%d\n", ifp->bsscfgidx); return -EPERM; } if (status == BRCMF_E_STATUS_PARTIAL) { brcmf_dbg(SCAN, "ESCAN Partial result\n"); if (e->datalen < sizeof(*escan_result_le)) { bphy_err(drvr, "invalid event data length\n"); goto exit; } escan_result_le = (struct brcmf_escan_result_le *) data; if (!escan_result_le) { bphy_err(drvr, "Invalid escan result (NULL pointer)\n"); goto exit; } escan_buflen = le32_to_cpu(escan_result_le->buflen); if (escan_buflen > BRCMF_ESCAN_BUF_SIZE || escan_buflen > e->datalen || escan_buflen < sizeof(*escan_result_le)) { bphy_err(drvr, "Invalid escan buffer length: %d\n", escan_buflen); goto exit; } if (le16_to_cpu(escan_result_le->bss_count) != 1) { bphy_err(drvr, "Invalid bss_count %d: ignoring\n", escan_result_le->bss_count); goto exit; } bss_info_le = &escan_result_le->bss_info_le; if (brcmf_p2p_scan_finding_common_channel(cfg, bss_info_le)) goto exit; if (!cfg->int_escan_map && !cfg->scan_request) { brcmf_dbg(SCAN, "result without cfg80211 request\n"); goto exit; } bi_length = le32_to_cpu(bss_info_le->length); if (bi_length != escan_buflen - WL_ESCAN_RESULTS_FIXED_SIZE) { bphy_err(drvr, "Ignoring invalid bss_info length: %d\n", bi_length); goto exit; } if (!(cfg_to_wiphy(cfg)->interface_modes & BIT(NL80211_IFTYPE_ADHOC))) { if (le16_to_cpu(bss_info_le->capability) & WLAN_CAPABILITY_IBSS) { bphy_err(drvr, "Ignoring IBSS result\n"); goto exit; } } list = (struct brcmf_scan_results *) cfg->escan_info.escan_buf; if (bi_length > BRCMF_ESCAN_BUF_SIZE - list->buflen) { bphy_err(drvr, "Buffer is too small: ignoring\n"); goto exit; } for (i = 0; i < list->count; i++) { bss = bss ? (struct brcmf_bss_info_le *) ((unsigned char *)bss + le32_to_cpu(bss->length)) : list->bss_info_le; if (brcmf_compare_update_same_bss(cfg, bss, bss_info_le)) goto exit; } memcpy(&cfg->escan_info.escan_buf[list->buflen], bss_info_le, bi_length); list->version = le32_to_cpu(bss_info_le->version); list->buflen += bi_length; list->count++; } else { cfg->escan_info.escan_state = WL_ESCAN_STATE_IDLE; if (brcmf_p2p_scan_finding_common_channel(cfg, NULL)) goto exit; if (cfg->int_escan_map || cfg->scan_request) { brcmf_inform_bss(cfg); aborted = status != BRCMF_E_STATUS_SUCCESS; brcmf_notify_escan_complete(cfg, ifp, aborted, false); } else brcmf_dbg(SCAN, "Ignored scan complete result 0x%x\n", status); } exit: return 0; } static void brcmf_init_escan(struct brcmf_cfg80211_info *cfg) { brcmf_fweh_register(cfg->pub, BRCMF_E_ESCAN_RESULT, brcmf_cfg80211_escan_handler); cfg->escan_info.escan_state = WL_ESCAN_STATE_IDLE; /* Init scan_timeout timer */ timer_setup(&cfg->escan_timeout, brcmf_escan_timeout, 0); INIT_WORK(&cfg->escan_timeout_work, brcmf_cfg80211_escan_timeout_worker); } static struct cfg80211_scan_request * brcmf_alloc_internal_escan_request(struct wiphy *wiphy, u32 n_netinfo) { struct cfg80211_scan_request *req; size_t req_size; req_size = sizeof(*req) + n_netinfo * sizeof(req->channels[0]) + n_netinfo * sizeof(*req->ssids); req = kzalloc(req_size, GFP_KERNEL); if (req) { req->wiphy = wiphy; #if defined(__linux__) req->ssids = (void *)(&req->channels[0]) + n_netinfo * sizeof(req->channels[0]); #elif defined(__FreeBSD__) req->ssids = (void *)((&req->channels[0]) + n_netinfo * sizeof(req->channels[0])); #endif } return req; } static int brcmf_internal_escan_add_info(struct cfg80211_scan_request *req, u8 *ssid, u8 ssid_len, u8 channel) { struct ieee80211_channel *chan; enum nl80211_band band; int freq, i; if (channel <= CH_MAX_2G_CHANNEL) band = NL80211_BAND_2GHZ; else band = NL80211_BAND_5GHZ; freq = ieee80211_channel_to_frequency(channel, band); if (!freq) return -EINVAL; chan = ieee80211_get_channel(req->wiphy, freq); if (!chan) return -EINVAL; for (i = 0; i < req->n_channels; i++) { if (req->channels[i] == chan) break; } if (i == req->n_channels) { req->n_channels++; req->channels[i] = chan; } for (i = 0; i < req->n_ssids; i++) { if (req->ssids[i].ssid_len == ssid_len && !memcmp(req->ssids[i].ssid, ssid, ssid_len)) break; } if (i == req->n_ssids) { memcpy(req->ssids[req->n_ssids].ssid, ssid, ssid_len); req->ssids[req->n_ssids++].ssid_len = ssid_len; } return 0; } static int brcmf_start_internal_escan(struct brcmf_if *ifp, u32 fwmap, struct cfg80211_scan_request *request) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; int err; if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) { if (cfg->int_escan_map) brcmf_dbg(SCAN, "aborting internal scan: map=%u\n", cfg->int_escan_map); /* Abort any on-going scan */ brcmf_abort_scanning(cfg); } brcmf_dbg(SCAN, "start internal scan: map=%u\n", fwmap); set_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); cfg->escan_info.run = brcmf_run_escan; err = brcmf_do_escan(ifp, request); if (err) { clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); return err; } cfg->int_escan_map = fwmap; return 0; } static struct brcmf_pno_net_info_le * brcmf_get_netinfo_array(struct brcmf_pno_scanresults_le *pfn_v1) { struct brcmf_pno_scanresults_v2_le *pfn_v2; struct brcmf_pno_net_info_le *netinfo; switch (pfn_v1->version) { default: WARN_ON(1); fallthrough; case cpu_to_le32(1): netinfo = (struct brcmf_pno_net_info_le *)(pfn_v1 + 1); break; case cpu_to_le32(2): pfn_v2 = (struct brcmf_pno_scanresults_v2_le *)pfn_v1; netinfo = (struct brcmf_pno_net_info_le *)(pfn_v2 + 1); break; } return netinfo; } /* PFN result doesn't have all the info which are required by the supplicant * (For e.g IEs) Do a target Escan so that sched scan results are reported * via wl_inform_single_bss in the required format. Escan does require the * scan request in the form of cfg80211_scan_request. For timebeing, create * cfg80211_scan_request one out of the received PNO event. */ static s32 brcmf_notify_sched_scan_results(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_info *cfg = drvr->config; struct brcmf_pno_net_info_le *netinfo, *netinfo_start; struct cfg80211_scan_request *request = NULL; struct wiphy *wiphy = cfg_to_wiphy(cfg); int i, err = 0; struct brcmf_pno_scanresults_le *pfn_result; u32 bucket_map; u32 result_count; u32 status; u32 datalen; brcmf_dbg(SCAN, "Enter\n"); if (e->datalen < (sizeof(*pfn_result) + sizeof(*netinfo))) { brcmf_dbg(SCAN, "Event data too small. Ignore\n"); return 0; } if (e->event_code == BRCMF_E_PFN_NET_LOST) { brcmf_dbg(SCAN, "PFN NET LOST event. Do Nothing\n"); return 0; } pfn_result = (struct brcmf_pno_scanresults_le *)data; result_count = le32_to_cpu(pfn_result->count); status = le32_to_cpu(pfn_result->status); /* PFN event is limited to fit 512 bytes so we may get * multiple NET_FOUND events. For now place a warning here. */ WARN_ON(status != BRCMF_PNO_SCAN_COMPLETE); brcmf_dbg(SCAN, "PFN NET FOUND event. count: %d\n", result_count); if (!result_count) { bphy_err(drvr, "FALSE PNO Event. (pfn_count == 0)\n"); goto out_err; } netinfo_start = brcmf_get_netinfo_array(pfn_result); #if defined(__linux__) datalen = e->datalen - ((void *)netinfo_start - (void *)pfn_result); #elif defined(__FreeBSD__) datalen = e->datalen - ((u8 *)netinfo_start - (u8 *)pfn_result); #endif if (datalen < result_count * sizeof(*netinfo)) { bphy_err(drvr, "insufficient event data\n"); goto out_err; } request = brcmf_alloc_internal_escan_request(wiphy, result_count); if (!request) { err = -ENOMEM; goto out_err; } bucket_map = 0; for (i = 0; i < result_count; i++) { netinfo = &netinfo_start[i]; if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN) netinfo->SSID_len = IEEE80211_MAX_SSID_LEN; brcmf_dbg(SCAN, "SSID:%.32s Channel:%d\n", netinfo->SSID, netinfo->channel); bucket_map |= brcmf_pno_get_bucket_map(cfg->pno, netinfo); err = brcmf_internal_escan_add_info(request, netinfo->SSID, netinfo->SSID_len, netinfo->channel); if (err) goto out_err; } if (!bucket_map) goto free_req; err = brcmf_start_internal_escan(ifp, bucket_map, request); if (!err) goto free_req; out_err: cfg80211_sched_scan_stopped(wiphy, 0); free_req: kfree(request); return err; } static int brcmf_cfg80211_sched_scan_start(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_sched_scan_request *req) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; brcmf_dbg(SCAN, "Enter: n_match_sets=%d n_ssids=%d\n", req->n_match_sets, req->n_ssids); if (test_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status)) { bphy_err(drvr, "Scanning suppressed: status=%lu\n", cfg->scan_status); return -EAGAIN; } if (req->n_match_sets <= 0) { brcmf_dbg(SCAN, "invalid number of matchsets specified: %d\n", req->n_match_sets); return -EINVAL; } return brcmf_pno_start_sched_scan(ifp, req); } static int brcmf_cfg80211_sched_scan_stop(struct wiphy *wiphy, struct net_device *ndev, u64 reqid) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); brcmf_dbg(SCAN, "enter\n"); brcmf_pno_stop_sched_scan(ifp, reqid); if (cfg->int_escan_map) brcmf_notify_escan_complete(cfg, ifp, true, true); return 0; } static __always_inline void brcmf_delay(u32 ms) { if (ms < 1000 / HZ) { cond_resched(); mdelay(ms); } else { #if defined(__linux__) msleep(ms); #elif defined(__FreeBSD__) linux_msleep(ms); #endif } } static s32 brcmf_config_wowl_pattern(struct brcmf_if *ifp, u8 cmd[4], u8 *pattern, u32 patternsize, u8 *mask, u32 packet_offset) { struct brcmf_fil_wowl_pattern_le *filter; u32 masksize; u32 patternoffset; u8 *buf; u32 bufsize; s32 ret; masksize = (patternsize + 7) / 8; patternoffset = sizeof(*filter) - sizeof(filter->cmd) + masksize; bufsize = sizeof(*filter) + patternsize + masksize; buf = kzalloc(bufsize, GFP_KERNEL); if (!buf) return -ENOMEM; filter = (struct brcmf_fil_wowl_pattern_le *)buf; memcpy(filter->cmd, cmd, 4); filter->masksize = cpu_to_le32(masksize); filter->offset = cpu_to_le32(packet_offset); filter->patternoffset = cpu_to_le32(patternoffset); filter->patternsize = cpu_to_le32(patternsize); filter->type = cpu_to_le32(BRCMF_WOWL_PATTERN_TYPE_BITMAP); if ((mask) && (masksize)) memcpy(buf + sizeof(*filter), mask, masksize); if ((pattern) && (patternsize)) memcpy(buf + sizeof(*filter) + masksize, pattern, patternsize); ret = brcmf_fil_iovar_data_set(ifp, "wowl_pattern", buf, bufsize); kfree(buf); return ret; } static s32 brcmf_wowl_nd_results(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_info *cfg = drvr->config; struct brcmf_pno_scanresults_le *pfn_result; struct brcmf_pno_net_info_le *netinfo; brcmf_dbg(SCAN, "Enter\n"); if (e->datalen < (sizeof(*pfn_result) + sizeof(*netinfo))) { brcmf_dbg(SCAN, "Event data too small. Ignore\n"); return 0; } pfn_result = (struct brcmf_pno_scanresults_le *)data; if (e->event_code == BRCMF_E_PFN_NET_LOST) { brcmf_dbg(SCAN, "PFN NET LOST event. Ignore\n"); return 0; } if (le32_to_cpu(pfn_result->count) < 1) { bphy_err(drvr, "Invalid result count, expected 1 (%d)\n", le32_to_cpu(pfn_result->count)); return -EINVAL; } netinfo = brcmf_get_netinfo_array(pfn_result); if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN) netinfo->SSID_len = IEEE80211_MAX_SSID_LEN; memcpy(cfg->wowl.nd->ssid.ssid, netinfo->SSID, netinfo->SSID_len); cfg->wowl.nd->ssid.ssid_len = netinfo->SSID_len; cfg->wowl.nd->n_channels = 1; cfg->wowl.nd->channels[0] = ieee80211_channel_to_frequency(netinfo->channel, netinfo->channel <= CH_MAX_2G_CHANNEL ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); cfg->wowl.nd_info->n_matches = 1; cfg->wowl.nd_info->matches[0] = cfg->wowl.nd; /* Inform (the resume task) that the net detect information was recvd */ cfg->wowl.nd_data_completed = true; wake_up(&cfg->wowl.nd_data_wait); return 0; } #ifdef CONFIG_PM static void brcmf_report_wowl_wakeind(struct wiphy *wiphy, struct brcmf_if *ifp) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct brcmf_wowl_wakeind_le wake_ind_le; struct cfg80211_wowlan_wakeup wakeup_data; struct cfg80211_wowlan_wakeup *wakeup; u32 wakeind; s32 err; long time_left; err = brcmf_fil_iovar_data_get(ifp, "wowl_wakeind", &wake_ind_le, sizeof(wake_ind_le)); if (err) { bphy_err(drvr, "Get wowl_wakeind failed, err = %d\n", err); return; } wakeind = le32_to_cpu(wake_ind_le.ucode_wakeind); if (wakeind & (BRCMF_WOWL_MAGIC | BRCMF_WOWL_DIS | BRCMF_WOWL_BCN | BRCMF_WOWL_RETR | BRCMF_WOWL_NET | BRCMF_WOWL_PFN_FOUND)) { wakeup = &wakeup_data; memset(&wakeup_data, 0, sizeof(wakeup_data)); wakeup_data.pattern_idx = -1; if (wakeind & BRCMF_WOWL_MAGIC) { brcmf_dbg(INFO, "WOWL Wake indicator: BRCMF_WOWL_MAGIC\n"); wakeup_data.magic_pkt = true; } if (wakeind & BRCMF_WOWL_DIS) { brcmf_dbg(INFO, "WOWL Wake indicator: BRCMF_WOWL_DIS\n"); wakeup_data.disconnect = true; } if (wakeind & BRCMF_WOWL_BCN) { brcmf_dbg(INFO, "WOWL Wake indicator: BRCMF_WOWL_BCN\n"); wakeup_data.disconnect = true; } if (wakeind & BRCMF_WOWL_RETR) { brcmf_dbg(INFO, "WOWL Wake indicator: BRCMF_WOWL_RETR\n"); wakeup_data.disconnect = true; } if (wakeind & BRCMF_WOWL_NET) { brcmf_dbg(INFO, "WOWL Wake indicator: BRCMF_WOWL_NET\n"); /* For now always map to pattern 0, no API to get * correct information available at the moment. */ wakeup_data.pattern_idx = 0; } if (wakeind & BRCMF_WOWL_PFN_FOUND) { brcmf_dbg(INFO, "WOWL Wake indicator: BRCMF_WOWL_PFN_FOUND\n"); time_left = wait_event_timeout(cfg->wowl.nd_data_wait, cfg->wowl.nd_data_completed, BRCMF_ND_INFO_TIMEOUT); if (!time_left) bphy_err(drvr, "No result for wowl net detect\n"); else wakeup_data.net_detect = cfg->wowl.nd_info; } if (wakeind & BRCMF_WOWL_GTK_FAILURE) { brcmf_dbg(INFO, "WOWL Wake indicator: BRCMF_WOWL_GTK_FAILURE\n"); wakeup_data.gtk_rekey_failure = true; } } else { wakeup = NULL; } cfg80211_report_wowlan_wakeup(&ifp->vif->wdev, wakeup, GFP_KERNEL); } #else static void brcmf_report_wowl_wakeind(struct wiphy *wiphy, struct brcmf_if *ifp) { } #endif /* CONFIG_PM */ static s32 brcmf_cfg80211_resume(struct wiphy *wiphy) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = cfg_to_ndev(cfg); struct brcmf_if *ifp = netdev_priv(ndev); brcmf_dbg(TRACE, "Enter\n"); if (cfg->wowl.active) { brcmf_report_wowl_wakeind(wiphy, ifp); brcmf_fil_iovar_int_set(ifp, "wowl_clear", 0); brcmf_config_wowl_pattern(ifp, "clr", NULL, 0, NULL, 0); if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_ARP_ND)) brcmf_configure_arp_nd_offload(ifp, true); brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, cfg->wowl.pre_pmmode); cfg->wowl.active = false; if (cfg->wowl.nd_enabled) { brcmf_cfg80211_sched_scan_stop(cfg->wiphy, ifp->ndev, 0); brcmf_fweh_unregister(cfg->pub, BRCMF_E_PFN_NET_FOUND); brcmf_fweh_register(cfg->pub, BRCMF_E_PFN_NET_FOUND, brcmf_notify_sched_scan_results); cfg->wowl.nd_enabled = false; } } return 0; } static void brcmf_configure_wowl(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, struct cfg80211_wowlan *wowl) { u32 wowl_config; struct brcmf_wowl_wakeind_le wowl_wakeind; u32 i; brcmf_dbg(TRACE, "Suspend, wowl config.\n"); if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_ARP_ND)) brcmf_configure_arp_nd_offload(ifp, false); brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_PM, &cfg->wowl.pre_pmmode); brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, PM_MAX); wowl_config = 0; if (wowl->disconnect) wowl_config = BRCMF_WOWL_DIS | BRCMF_WOWL_BCN | BRCMF_WOWL_RETR; if (wowl->magic_pkt) wowl_config |= BRCMF_WOWL_MAGIC; if ((wowl->patterns) && (wowl->n_patterns)) { wowl_config |= BRCMF_WOWL_NET; for (i = 0; i < wowl->n_patterns; i++) { brcmf_config_wowl_pattern(ifp, "add", (u8 *)wowl->patterns[i].pattern, wowl->patterns[i].pattern_len, (u8 *)wowl->patterns[i].mask, wowl->patterns[i].pkt_offset); } } if (wowl->nd_config) { brcmf_cfg80211_sched_scan_start(cfg->wiphy, ifp->ndev, wowl->nd_config); wowl_config |= BRCMF_WOWL_PFN_FOUND; cfg->wowl.nd_data_completed = false; cfg->wowl.nd_enabled = true; /* Now reroute the event for PFN to the wowl function. */ brcmf_fweh_unregister(cfg->pub, BRCMF_E_PFN_NET_FOUND); brcmf_fweh_register(cfg->pub, BRCMF_E_PFN_NET_FOUND, brcmf_wowl_nd_results); } if (wowl->gtk_rekey_failure) wowl_config |= BRCMF_WOWL_GTK_FAILURE; if (!test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state)) wowl_config |= BRCMF_WOWL_UNASSOC; memcpy(&wowl_wakeind, "clear", 6); brcmf_fil_iovar_data_set(ifp, "wowl_wakeind", &wowl_wakeind, sizeof(wowl_wakeind)); brcmf_fil_iovar_int_set(ifp, "wowl", wowl_config); brcmf_fil_iovar_int_set(ifp, "wowl_activate", 1); brcmf_bus_wowl_config(cfg->pub->bus_if, true); cfg->wowl.active = true; } static int brcmf_keepalive_start(struct brcmf_if *ifp, unsigned int interval) { struct brcmf_mkeep_alive_pkt_le kalive = {0}; int ret = 0; /* Configure Null function/data keepalive */ kalive.version = cpu_to_le16(1); kalive.period_msec = cpu_to_le32(interval * MSEC_PER_SEC); kalive.len_bytes = cpu_to_le16(0); kalive.keep_alive_id = 0; ret = brcmf_fil_iovar_data_set(ifp, "mkeep_alive", &kalive, sizeof(kalive)); if (ret) brcmf_err("keep-alive packet config failed, ret=%d\n", ret); return ret; } static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wowl) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = cfg_to_ndev(cfg); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_vif *vif; brcmf_dbg(TRACE, "Enter\n"); /* if the primary net_device is not READY there is nothing * we can do but pray resume goes smoothly. */ if (!check_vif_up(ifp->vif)) goto exit; /* Stop scheduled scan */ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) brcmf_cfg80211_sched_scan_stop(wiphy, ndev, 0); /* end any scanning */ if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) brcmf_abort_scanning(cfg); if (wowl == NULL) { brcmf_bus_wowl_config(cfg->pub->bus_if, false); list_for_each_entry(vif, &cfg->vif_list, list) { if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) continue; /* While going to suspend if associated with AP * disassociate from AP to save power while system is * in suspended state */ brcmf_link_down(vif, WLAN_REASON_UNSPECIFIED, true); /* Make sure WPA_Supplicant receives all the event * generated due to DISASSOC call to the fw to keep * the state fw and WPA_Supplicant state consistent */ brcmf_delay(500); } /* Configure MPC */ brcmf_set_mpc(ifp, 1); } else { /* Configure WOWL parameters */ brcmf_configure_wowl(cfg, ifp, wowl); /* Prevent disassociation due to inactivity with keep-alive */ brcmf_keepalive_start(ifp, 30); } exit: brcmf_dbg(TRACE, "Exit\n"); /* clear any scanning activity */ cfg->scan_status = 0; return 0; } static s32 brcmf_pmksa_v3_op(struct brcmf_if *ifp, struct cfg80211_pmksa *pmksa, bool alive) { struct brcmf_pmk_op_v3_le *pmk_op; int length = offsetof(struct brcmf_pmk_op_v3_le, pmk); int ret; pmk_op = kzalloc(sizeof(*pmk_op), GFP_KERNEL); if (!pmk_op) return -ENOMEM; pmk_op->version = cpu_to_le16(BRCMF_PMKSA_VER_3); if (!pmksa) { /* Flush operation, operate on entire list */ pmk_op->count = cpu_to_le16(0); } else { /* Single PMK operation */ pmk_op->count = cpu_to_le16(1); length += sizeof(struct brcmf_pmksa_v3); if (pmksa->bssid) memcpy(pmk_op->pmk[0].bssid, pmksa->bssid, ETH_ALEN); if (pmksa->pmkid) { memcpy(pmk_op->pmk[0].pmkid, pmksa->pmkid, WLAN_PMKID_LEN); pmk_op->pmk[0].pmkid_len = WLAN_PMKID_LEN; } if (pmksa->ssid && pmksa->ssid_len) { memcpy(pmk_op->pmk[0].ssid.SSID, pmksa->ssid, pmksa->ssid_len); pmk_op->pmk[0].ssid.SSID_len = pmksa->ssid_len; } pmk_op->pmk[0].time_left = cpu_to_le32(alive ? BRCMF_PMKSA_NO_EXPIRY : 0); } pmk_op->length = cpu_to_le16(length); ret = brcmf_fil_iovar_data_set(ifp, "pmkid_info", pmk_op, sizeof(*pmk_op)); kfree(pmk_op); return ret; } static __used s32 brcmf_update_pmklist(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp) { struct brcmf_pmk_list_le *pmk_list; int i; u32 npmk; pmk_list = &cfg->pmk_list; npmk = le32_to_cpu(pmk_list->npmk); brcmf_dbg(CONN, "No of elements %d\n", npmk); for (i = 0; i < npmk; i++) brcmf_dbg(CONN, "PMK[%d]: %pM\n", i, &pmk_list->pmk[i].bssid); return brcmf_fil_iovar_data_set(ifp, "pmkid_info", pmk_list, sizeof(*pmk_list)); } static s32 brcmf_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_pmksa *pmksa) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pmksa *pmk = &cfg->pmk_list.pmk[0]; struct brcmf_pub *drvr = cfg->pub; s32 err; u32 npmk, i; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) return -EIO; brcmf_dbg(CONN, "set_pmksa - PMK bssid: %pM =\n", pmksa->bssid); brcmf_dbg(CONN, "%*ph\n", WLAN_PMKID_LEN, pmksa->pmkid); if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3)) return brcmf_pmksa_v3_op(ifp, pmksa, true); /* TODO: implement PMKID_V2 */ npmk = le32_to_cpu(cfg->pmk_list.npmk); for (i = 0; i < npmk; i++) if (!memcmp(pmksa->bssid, pmk[i].bssid, ETH_ALEN)) break; if (i < BRCMF_MAXPMKID) { memcpy(pmk[i].bssid, pmksa->bssid, ETH_ALEN); memcpy(pmk[i].pmkid, pmksa->pmkid, WLAN_PMKID_LEN); if (i == npmk) { npmk++; cfg->pmk_list.npmk = cpu_to_le32(npmk); } } else { bphy_err(drvr, "Too many PMKSA entries cached %d\n", npmk); return -EINVAL; } err = brcmf_update_pmklist(cfg, ifp); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_pmksa *pmksa) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pmksa *pmk = &cfg->pmk_list.pmk[0]; struct brcmf_pub *drvr = cfg->pub; s32 err; u32 npmk, i; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) return -EIO; brcmf_dbg(CONN, "del_pmksa - PMK bssid = %pM\n", pmksa->bssid); if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3)) return brcmf_pmksa_v3_op(ifp, pmksa, false); /* TODO: implement PMKID_V2 */ npmk = le32_to_cpu(cfg->pmk_list.npmk); for (i = 0; i < npmk; i++) if (!memcmp(pmksa->bssid, pmk[i].bssid, ETH_ALEN)) break; if ((npmk > 0) && (i < npmk)) { for (; i < (npmk - 1); i++) { memcpy(&pmk[i].bssid, &pmk[i + 1].bssid, ETH_ALEN); memcpy(&pmk[i].pmkid, &pmk[i + 1].pmkid, WLAN_PMKID_LEN); } memset(&pmk[i], 0, sizeof(*pmk)); cfg->pmk_list.npmk = cpu_to_le32(npmk - 1); } else { bphy_err(drvr, "Cache entry not found\n"); return -EINVAL; } err = brcmf_update_pmklist(cfg, ifp); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *ndev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); s32 err; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) return -EIO; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3)) return brcmf_pmksa_v3_op(ifp, NULL, false); /* TODO: implement PMKID_V2 */ memset(&cfg->pmk_list, 0, sizeof(cfg->pmk_list)); err = brcmf_update_pmklist(cfg, ifp); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_configure_opensecurity(struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; s32 err; s32 wpa_val; /* set auth */ err = brcmf_fil_bsscfg_int_set(ifp, "auth", 0); if (err < 0) { bphy_err(drvr, "auth error %d\n", err); return err; } /* set wsec */ err = brcmf_fil_bsscfg_int_set(ifp, "wsec", 0); if (err < 0) { bphy_err(drvr, "wsec error %d\n", err); return err; } /* set upper-layer auth */ if (brcmf_is_ibssmode(ifp->vif)) wpa_val = WPA_AUTH_NONE; else wpa_val = WPA_AUTH_DISABLED; err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", wpa_val); if (err < 0) { bphy_err(drvr, "wpa_auth error %d\n", err); return err; } return 0; } #if defined(__linux__) static bool brcmf_valid_wpa_oui(u8 *oui, bool is_rsn_ie) #elif defined(__FreeBSD__) static bool brcmf_valid_wpa_oui(const u8 *oui, bool is_rsn_ie) #endif { if (is_rsn_ie) return (memcmp(oui, RSN_OUI, TLV_OUI_LEN) == 0); return (memcmp(oui, WPA_OUI, TLV_OUI_LEN) == 0); } static s32 brcmf_configure_wpaie(struct brcmf_if *ifp, const struct brcmf_vs_tlv *wpa_ie, bool is_rsn_ie) { struct brcmf_pub *drvr = ifp->drvr; u32 auth = 0; /* d11 open authentication */ u16 count; s32 err = 0; s32 len; u32 i; u32 wsec; u32 pval = 0; u32 gval = 0; u32 wpa_auth = 0; u32 offset; #if defined(__linux__) u8 *data; #elif defined(__FreeBSD__) const u8 *data; #endif u16 rsn_cap; u32 wme_bss_disable; u32 mfp; brcmf_dbg(TRACE, "Enter\n"); if (wpa_ie == NULL) goto exit; len = wpa_ie->len + TLV_HDR_LEN; #if defined(__linux__) data = (u8 *)wpa_ie; #elif defined(__FreeBSD__) data = (const u8 *)wpa_ie; #endif offset = TLV_HDR_LEN; if (!is_rsn_ie) offset += VS_IE_FIXED_HDR_LEN; else offset += WPA_IE_VERSION_LEN; /* check for multicast cipher suite */ if (offset + WPA_IE_MIN_OUI_LEN > len) { err = -EINVAL; bphy_err(drvr, "no multicast cipher suite\n"); goto exit; } if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { err = -EINVAL; bphy_err(drvr, "invalid OUI\n"); goto exit; } offset += TLV_OUI_LEN; /* pick up multicast cipher */ switch (data[offset]) { case WPA_CIPHER_NONE: gval = 0; break; case WPA_CIPHER_WEP_40: case WPA_CIPHER_WEP_104: gval = WEP_ENABLED; break; case WPA_CIPHER_TKIP: gval = TKIP_ENABLED; break; case WPA_CIPHER_AES_CCM: gval = AES_ENABLED; break; default: err = -EINVAL; bphy_err(drvr, "Invalid multi cast cipher info\n"); goto exit; } offset++; /* walk thru unicast cipher list and pick up what we recognize */ count = data[offset] + (data[offset + 1] << 8); offset += WPA_IE_SUITE_COUNT_LEN; /* Check for unicast suite(s) */ if (offset + (WPA_IE_MIN_OUI_LEN * count) > len) { err = -EINVAL; bphy_err(drvr, "no unicast cipher suite\n"); goto exit; } for (i = 0; i < count; i++) { if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { err = -EINVAL; bphy_err(drvr, "invalid OUI\n"); goto exit; } offset += TLV_OUI_LEN; switch (data[offset]) { case WPA_CIPHER_NONE: break; case WPA_CIPHER_WEP_40: case WPA_CIPHER_WEP_104: pval |= WEP_ENABLED; break; case WPA_CIPHER_TKIP: pval |= TKIP_ENABLED; break; case WPA_CIPHER_AES_CCM: pval |= AES_ENABLED; break; default: bphy_err(drvr, "Invalid unicast security info\n"); } offset++; } /* walk thru auth management suite list and pick up what we recognize */ count = data[offset] + (data[offset + 1] << 8); offset += WPA_IE_SUITE_COUNT_LEN; /* Check for auth key management suite(s) */ if (offset + (WPA_IE_MIN_OUI_LEN * count) > len) { err = -EINVAL; bphy_err(drvr, "no auth key mgmt suite\n"); goto exit; } for (i = 0; i < count; i++) { if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { err = -EINVAL; bphy_err(drvr, "invalid OUI\n"); goto exit; } offset += TLV_OUI_LEN; switch (data[offset]) { case RSN_AKM_NONE: brcmf_dbg(TRACE, "RSN_AKM_NONE\n"); wpa_auth |= WPA_AUTH_NONE; break; case RSN_AKM_UNSPECIFIED: brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n"); is_rsn_ie ? (wpa_auth |= WPA2_AUTH_UNSPECIFIED) : (wpa_auth |= WPA_AUTH_UNSPECIFIED); break; case RSN_AKM_PSK: brcmf_dbg(TRACE, "RSN_AKM_PSK\n"); is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) : (wpa_auth |= WPA_AUTH_PSK); break; case RSN_AKM_SHA256_PSK: brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n"); wpa_auth |= WPA2_AUTH_PSK_SHA256; break; case RSN_AKM_SHA256_1X: brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n"); wpa_auth |= WPA2_AUTH_1X_SHA256; break; case RSN_AKM_SAE: brcmf_dbg(TRACE, "RSN_AKM_SAE\n"); wpa_auth |= WPA3_AUTH_SAE_PSK; break; default: bphy_err(drvr, "Invalid key mgmt info\n"); } offset++; } mfp = BRCMF_MFP_NONE; if (is_rsn_ie) { wme_bss_disable = 1; if ((offset + RSN_CAP_LEN) <= len) { rsn_cap = data[offset] + (data[offset + 1] << 8); if (rsn_cap & RSN_CAP_PTK_REPLAY_CNTR_MASK) wme_bss_disable = 0; if (rsn_cap & RSN_CAP_MFPR_MASK) { brcmf_dbg(TRACE, "MFP Required\n"); mfp = BRCMF_MFP_REQUIRED; /* Firmware only supports mfp required in * combination with WPA2_AUTH_PSK_SHA256, * WPA2_AUTH_1X_SHA256, or WPA3_AUTH_SAE_PSK. */ if (!(wpa_auth & (WPA2_AUTH_PSK_SHA256 | WPA2_AUTH_1X_SHA256 | WPA3_AUTH_SAE_PSK))) { err = -EINVAL; goto exit; } /* Firmware has requirement that WPA2_AUTH_PSK/ * WPA2_AUTH_UNSPECIFIED be set, if SHA256 OUI * is to be included in the rsn ie. */ if (wpa_auth & WPA2_AUTH_PSK_SHA256) wpa_auth |= WPA2_AUTH_PSK; else if (wpa_auth & WPA2_AUTH_1X_SHA256) wpa_auth |= WPA2_AUTH_UNSPECIFIED; } else if (rsn_cap & RSN_CAP_MFPC_MASK) { brcmf_dbg(TRACE, "MFP Capable\n"); mfp = BRCMF_MFP_CAPABLE; } } offset += RSN_CAP_LEN; /* set wme_bss_disable to sync RSN Capabilities */ err = brcmf_fil_bsscfg_int_set(ifp, "wme_bss_disable", wme_bss_disable); if (err < 0) { bphy_err(drvr, "wme_bss_disable error %d\n", err); goto exit; } /* Skip PMKID cnt as it is know to be 0 for AP. */ offset += RSN_PMKID_COUNT_LEN; /* See if there is BIP wpa suite left for MFP */ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP) && ((offset + WPA_IE_MIN_OUI_LEN) <= len)) { err = brcmf_fil_bsscfg_data_set(ifp, "bip", &data[offset], WPA_IE_MIN_OUI_LEN); if (err < 0) { bphy_err(drvr, "bip error %d\n", err); goto exit; } } } /* FOR WPS , set SES_OW_ENABLED */ wsec = (pval | gval | SES_OW_ENABLED); /* set auth */ err = brcmf_fil_bsscfg_int_set(ifp, "auth", auth); if (err < 0) { bphy_err(drvr, "auth error %d\n", err); goto exit; } /* set wsec */ err = brcmf_fil_bsscfg_int_set(ifp, "wsec", wsec); if (err < 0) { bphy_err(drvr, "wsec error %d\n", err); goto exit; } /* Configure MFP, this needs to go after wsec otherwise the wsec command * will overwrite the values set by MFP */ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP)) { err = brcmf_fil_bsscfg_int_set(ifp, "mfp", mfp); if (err < 0) { bphy_err(drvr, "mfp error %d\n", err); goto exit; } } /* set upper-layer auth */ err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", wpa_auth); if (err < 0) { bphy_err(drvr, "wpa_auth error %d\n", err); goto exit; } exit: return err; } static s32 brcmf_parse_vndr_ies(const u8 *vndr_ie_buf, u32 vndr_ie_len, struct parsed_vndr_ies *vndr_ies) { #if defined(__linux__) struct brcmf_vs_tlv *vndrie; struct brcmf_tlv *ie; #elif defined(__FreeBSD__) const struct brcmf_vs_tlv *vndrie; const struct brcmf_tlv *ie; #endif struct parsed_vndr_ie_info *parsed_info; s32 remaining_len; remaining_len = (s32)vndr_ie_len; memset(vndr_ies, 0, sizeof(*vndr_ies)); #if defined(__linux__) ie = (struct brcmf_tlv *)vndr_ie_buf; #elif defined(__FreeBSD__) ie = (const struct brcmf_tlv *)vndr_ie_buf; #endif while (ie) { if (ie->id != WLAN_EID_VENDOR_SPECIFIC) goto next; #if defined(__linux__) vndrie = (struct brcmf_vs_tlv *)ie; #elif defined(__FreeBSD__) vndrie = (const struct brcmf_vs_tlv *)ie; #endif /* len should be bigger than OUI length + one */ if (vndrie->len < (VS_IE_FIXED_HDR_LEN - TLV_HDR_LEN + 1)) { brcmf_err("invalid vndr ie. length is too small %d\n", vndrie->len); goto next; } /* if wpa or wme ie, do not add ie */ if (!memcmp(vndrie->oui, (u8 *)WPA_OUI, TLV_OUI_LEN) && ((vndrie->oui_type == WPA_OUI_TYPE) || (vndrie->oui_type == WME_OUI_TYPE))) { brcmf_dbg(TRACE, "Found WPA/WME oui. Do not add it\n"); goto next; } parsed_info = &vndr_ies->ie_info[vndr_ies->count]; /* save vndr ie information */ #if defined(__linux__) parsed_info->ie_ptr = (char *)vndrie; #elif defined(__FreeBSD__) parsed_info->ie_ptr = (const char *)vndrie; #endif parsed_info->ie_len = vndrie->len + TLV_HDR_LEN; memcpy(&parsed_info->vndrie, vndrie, sizeof(*vndrie)); vndr_ies->count++; brcmf_dbg(TRACE, "** OUI %3ph, type 0x%02x\n", parsed_info->vndrie.oui, parsed_info->vndrie.oui_type); if (vndr_ies->count >= VNDR_IE_PARSE_LIMIT) break; next: remaining_len -= (ie->len + TLV_HDR_LEN); if (remaining_len <= TLV_HDR_LEN) ie = NULL; else #if defined(__linux__) ie = (struct brcmf_tlv *)(((u8 *)ie) + ie->len + #elif defined(__FreeBSD__) ie = (const struct brcmf_tlv *)(((const u8 *)ie) + ie->len + #endif TLV_HDR_LEN); } return 0; } static u32 #if defined(__linux__) brcmf_vndr_ie(u8 *iebuf, s32 pktflag, u8 *ie_ptr, u32 ie_len, s8 *add_del_cmd) #elif defined(__FreeBSD__) brcmf_vndr_ie(u8 *iebuf, s32 pktflag, const u8 *ie_ptr, u32 ie_len, s8 *add_del_cmd) #endif { strscpy(iebuf, add_del_cmd, VNDR_IE_CMD_LEN); put_unaligned_le32(1, &iebuf[VNDR_IE_COUNT_OFFSET]); put_unaligned_le32(pktflag, &iebuf[VNDR_IE_PKTFLAG_OFFSET]); memcpy(&iebuf[VNDR_IE_VSIE_OFFSET], ie_ptr, ie_len); return ie_len + VNDR_IE_HDR_SIZE; } s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, const u8 *vndr_ie_buf, u32 vndr_ie_len) { struct brcmf_pub *drvr; struct brcmf_if *ifp; struct vif_saved_ie *saved_ie; s32 err = 0; u8 *iovar_ie_buf; u8 *curr_ie_buf; u8 *mgmt_ie_buf = NULL; int mgmt_ie_buf_len; u32 *mgmt_ie_len; u32 del_add_ie_buf_len = 0; u32 total_ie_buf_len = 0; u32 parsed_ie_buf_len = 0; struct parsed_vndr_ies old_vndr_ies; struct parsed_vndr_ies new_vndr_ies; struct parsed_vndr_ie_info *vndrie_info; s32 i; u8 *ptr; int remained_buf_len; if (!vif) return -ENODEV; ifp = vif->ifp; drvr = ifp->drvr; saved_ie = &vif->saved_ie; brcmf_dbg(TRACE, "bsscfgidx %d, pktflag : 0x%02X\n", ifp->bsscfgidx, pktflag); iovar_ie_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL); if (!iovar_ie_buf) return -ENOMEM; curr_ie_buf = iovar_ie_buf; switch (pktflag) { case BRCMF_VNDR_IE_PRBREQ_FLAG: mgmt_ie_buf = saved_ie->probe_req_ie; mgmt_ie_len = &saved_ie->probe_req_ie_len; mgmt_ie_buf_len = sizeof(saved_ie->probe_req_ie); break; case BRCMF_VNDR_IE_PRBRSP_FLAG: mgmt_ie_buf = saved_ie->probe_res_ie; mgmt_ie_len = &saved_ie->probe_res_ie_len; mgmt_ie_buf_len = sizeof(saved_ie->probe_res_ie); break; case BRCMF_VNDR_IE_BEACON_FLAG: mgmt_ie_buf = saved_ie->beacon_ie; mgmt_ie_len = &saved_ie->beacon_ie_len; mgmt_ie_buf_len = sizeof(saved_ie->beacon_ie); break; case BRCMF_VNDR_IE_ASSOCREQ_FLAG: mgmt_ie_buf = saved_ie->assoc_req_ie; mgmt_ie_len = &saved_ie->assoc_req_ie_len; mgmt_ie_buf_len = sizeof(saved_ie->assoc_req_ie); break; case BRCMF_VNDR_IE_ASSOCRSP_FLAG: mgmt_ie_buf = saved_ie->assoc_res_ie; mgmt_ie_len = &saved_ie->assoc_res_ie_len; mgmt_ie_buf_len = sizeof(saved_ie->assoc_res_ie); break; default: err = -EPERM; bphy_err(drvr, "not suitable type\n"); goto exit; } if (vndr_ie_len > mgmt_ie_buf_len) { err = -ENOMEM; bphy_err(drvr, "extra IE size too big\n"); goto exit; } /* parse and save new vndr_ie in curr_ie_buff before comparing it */ if (vndr_ie_buf && vndr_ie_len && curr_ie_buf) { ptr = curr_ie_buf; brcmf_parse_vndr_ies(vndr_ie_buf, vndr_ie_len, &new_vndr_ies); for (i = 0; i < new_vndr_ies.count; i++) { vndrie_info = &new_vndr_ies.ie_info[i]; memcpy(ptr + parsed_ie_buf_len, vndrie_info->ie_ptr, vndrie_info->ie_len); parsed_ie_buf_len += vndrie_info->ie_len; } } if (mgmt_ie_buf && *mgmt_ie_len) { if (parsed_ie_buf_len && (parsed_ie_buf_len == *mgmt_ie_len) && (memcmp(mgmt_ie_buf, curr_ie_buf, parsed_ie_buf_len) == 0)) { brcmf_dbg(TRACE, "Previous mgmt IE equals to current IE\n"); goto exit; } /* parse old vndr_ie */ brcmf_parse_vndr_ies(mgmt_ie_buf, *mgmt_ie_len, &old_vndr_ies); /* make a command to delete old ie */ for (i = 0; i < old_vndr_ies.count; i++) { vndrie_info = &old_vndr_ies.ie_info[i]; brcmf_dbg(TRACE, "DEL ID : %d, Len: %d , OUI:%3ph\n", vndrie_info->vndrie.id, vndrie_info->vndrie.len, vndrie_info->vndrie.oui); del_add_ie_buf_len = brcmf_vndr_ie(curr_ie_buf, pktflag, vndrie_info->ie_ptr, vndrie_info->ie_len, "del"); curr_ie_buf += del_add_ie_buf_len; total_ie_buf_len += del_add_ie_buf_len; } } *mgmt_ie_len = 0; /* Add if there is any extra IE */ if (mgmt_ie_buf && parsed_ie_buf_len) { ptr = mgmt_ie_buf; remained_buf_len = mgmt_ie_buf_len; /* make a command to add new ie */ for (i = 0; i < new_vndr_ies.count; i++) { vndrie_info = &new_vndr_ies.ie_info[i]; /* verify remained buf size before copy data */ if (remained_buf_len < (vndrie_info->vndrie.len + VNDR_IE_VSIE_OFFSET)) { bphy_err(drvr, "no space in mgmt_ie_buf: len left %d", remained_buf_len); break; } remained_buf_len -= (vndrie_info->ie_len + VNDR_IE_VSIE_OFFSET); brcmf_dbg(TRACE, "ADDED ID : %d, Len: %d, OUI:%3ph\n", vndrie_info->vndrie.id, vndrie_info->vndrie.len, vndrie_info->vndrie.oui); del_add_ie_buf_len = brcmf_vndr_ie(curr_ie_buf, pktflag, vndrie_info->ie_ptr, vndrie_info->ie_len, "add"); /* save the parsed IE in wl struct */ memcpy(ptr + (*mgmt_ie_len), vndrie_info->ie_ptr, vndrie_info->ie_len); *mgmt_ie_len += vndrie_info->ie_len; curr_ie_buf += del_add_ie_buf_len; total_ie_buf_len += del_add_ie_buf_len; } } if (total_ie_buf_len) { err = brcmf_fil_bsscfg_data_set(ifp, "vndr_ie", iovar_ie_buf, total_ie_buf_len); if (err) bphy_err(drvr, "vndr ie set error : %d\n", err); } exit: kfree(iovar_ie_buf); return err; } s32 brcmf_vif_clear_mgmt_ies(struct brcmf_cfg80211_vif *vif) { static const s32 pktflags[] = { BRCMF_VNDR_IE_PRBRSP_FLAG, BRCMF_VNDR_IE_BEACON_FLAG }; int i; if (vif->wdev.iftype == NL80211_IFTYPE_AP) brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_ASSOCRSP_FLAG, NULL, 0); else brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_PRBREQ_FLAG, NULL, 0); for (i = 0; i < ARRAY_SIZE(pktflags); i++) brcmf_vif_set_mgmt_ie(vif, pktflags[i], NULL, 0); memset(&vif->saved_ie, 0, sizeof(vif->saved_ie)); return 0; } static s32 brcmf_config_ap_mgmt_ie(struct brcmf_cfg80211_vif *vif, struct cfg80211_beacon_data *beacon) { struct brcmf_pub *drvr = vif->ifp->drvr; s32 err; /* Set Beacon IEs to FW */ err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_BEACON_FLAG, beacon->tail, beacon->tail_len); if (err) { bphy_err(drvr, "Set Beacon IE Failed\n"); return err; } brcmf_dbg(TRACE, "Applied Vndr IEs for Beacon\n"); /* Set Probe Response IEs to FW */ err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_PRBRSP_FLAG, beacon->proberesp_ies, beacon->proberesp_ies_len); if (err) bphy_err(drvr, "Set Probe Resp IE Failed\n"); else brcmf_dbg(TRACE, "Applied Vndr IEs for Probe Resp\n"); /* Set Assoc Response IEs to FW */ err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_ASSOCRSP_FLAG, beacon->assocresp_ies, beacon->assocresp_ies_len); if (err) brcmf_err("Set Assoc Resp IE Failed\n"); else brcmf_dbg(TRACE, "Applied Vndr IEs for Assoc Resp\n"); return err; } static s32 brcmf_parse_configure_security(struct brcmf_if *ifp, struct cfg80211_ap_settings *settings, enum nl80211_iftype dev_role) { const struct brcmf_tlv *rsn_ie; const struct brcmf_vs_tlv *wpa_ie; s32 err = 0; /* find the RSN_IE */ #if defined(__linux__) rsn_ie = brcmf_parse_tlvs((u8 *)settings->beacon.tail, #elif defined(__FreeBSD__) rsn_ie = brcmf_parse_tlvs(settings->beacon.tail, #endif settings->beacon.tail_len, WLAN_EID_RSN); /* find the WPA_IE */ #if defined(__linux__) wpa_ie = brcmf_find_wpaie((u8 *)settings->beacon.tail, #elif defined(__FreeBSD__) wpa_ie = brcmf_find_wpaie(settings->beacon.tail, #endif settings->beacon.tail_len); if (wpa_ie || rsn_ie) { brcmf_dbg(TRACE, "WPA(2) IE is found\n"); if (wpa_ie) { /* WPA IE */ err = brcmf_configure_wpaie(ifp, wpa_ie, false); if (err < 0) return err; } else { #if defined(__linux__) struct brcmf_vs_tlv *tmp_ie; tmp_ie = (struct brcmf_vs_tlv *)rsn_ie; #elif defined(__FreeBSD__) const struct brcmf_vs_tlv *tmp_ie; tmp_ie = (const struct brcmf_vs_tlv *)rsn_ie; #endif /* RSN IE */ err = brcmf_configure_wpaie(ifp, tmp_ie, true); if (err < 0) return err; } } else { brcmf_dbg(TRACE, "No WPA(2) IEs found\n"); brcmf_configure_opensecurity(ifp); } return err; } static s32 brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_ap_settings *settings) { s32 ie_offset; struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct cfg80211_crypto_settings *crypto = &settings->crypto; const struct brcmf_tlv *ssid_ie; const struct brcmf_tlv *country_ie; struct brcmf_ssid_le ssid_le; s32 err = -EPERM; struct brcmf_join_params join_params; enum nl80211_iftype dev_role; struct brcmf_fil_bss_enable_le bss_enable; u16 chanspec = chandef_to_chanspec(&cfg->d11inf, &settings->chandef); bool mbss; int is_11d; bool supports_11d; bool closednet; brcmf_dbg(TRACE, "ctrlchn=%d, center=%d, bw=%d, beacon_interval=%d, dtim_period=%d,\n", settings->chandef.chan->hw_value, settings->chandef.center_freq1, settings->chandef.width, settings->beacon_interval, settings->dtim_period); brcmf_dbg(TRACE, "ssid=%s(%zu), auth_type=%d, inactivity_timeout=%d\n", settings->ssid, settings->ssid_len, settings->auth_type, settings->inactivity_timeout); dev_role = ifp->vif->wdev.iftype; mbss = ifp->vif->mbss; /* store current 11d setting */ if (brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_REGULATORY, &ifp->vif->is_11d)) { is_11d = supports_11d = false; } else { #if defined(__linux__) country_ie = brcmf_parse_tlvs((u8 *)settings->beacon.tail, #elif defined(__FreeBSD__) country_ie = brcmf_parse_tlvs(settings->beacon.tail, #endif settings->beacon.tail_len, WLAN_EID_COUNTRY); is_11d = country_ie ? 1 : 0; supports_11d = true; } memset(&ssid_le, 0, sizeof(ssid_le)); if (settings->ssid == NULL || settings->ssid_len == 0) { ie_offset = DOT11_MGMT_HDR_LEN + DOT11_BCN_PRB_FIXED_LEN; ssid_ie = brcmf_parse_tlvs( #if defined(__linux__) (u8 *)&settings->beacon.head[ie_offset], #elif defined(__FreeBSD__) &settings->beacon.head[ie_offset], #endif settings->beacon.head_len - ie_offset, WLAN_EID_SSID); if (!ssid_ie || ssid_ie->len > IEEE80211_MAX_SSID_LEN) return -EINVAL; memcpy(ssid_le.SSID, ssid_ie->data, ssid_ie->len); ssid_le.SSID_len = cpu_to_le32(ssid_ie->len); brcmf_dbg(TRACE, "SSID is (%s) in Head\n", ssid_le.SSID); } else { memcpy(ssid_le.SSID, settings->ssid, settings->ssid_len); ssid_le.SSID_len = cpu_to_le32((u32)settings->ssid_len); } if (!mbss) { brcmf_set_mpc(ifp, 0); brcmf_configure_arp_nd_offload(ifp, false); } /* Parameters shared by all radio interfaces */ if (!mbss) { if ((supports_11d) && (is_11d != ifp->vif->is_11d)) { err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_REGULATORY, is_11d); if (err < 0) { bphy_err(drvr, "Regulatory Set Error, %d\n", err); goto exit; } } if (settings->beacon_interval) { err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_BCNPRD, settings->beacon_interval); if (err < 0) { bphy_err(drvr, "Beacon Interval Set Error, %d\n", err); goto exit; } } if (settings->dtim_period) { err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_DTIMPRD, settings->dtim_period); if (err < 0) { bphy_err(drvr, "DTIM Interval Set Error, %d\n", err); goto exit; } } if ((dev_role == NL80211_IFTYPE_AP) && ((ifp->ifidx == 0) || (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_RSDB) && !brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MCHAN)))) { err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); if (err < 0) { bphy_err(drvr, "BRCMF_C_DOWN error %d\n", err); goto exit; } brcmf_fil_iovar_int_set(ifp, "apsta", 0); } err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_INFRA, 1); if (err < 0) { bphy_err(drvr, "SET INFRA error %d\n", err); goto exit; } } else if (WARN_ON(supports_11d && (is_11d != ifp->vif->is_11d))) { /* Multiple-BSS should use same 11d configuration */ err = -EINVAL; goto exit; } /* Interface specific setup */ if (dev_role == NL80211_IFTYPE_AP) { if ((brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MBSS)) && (!mbss)) brcmf_fil_iovar_int_set(ifp, "mbss", 1); err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_AP, 1); if (err < 0) { bphy_err(drvr, "setting AP mode failed %d\n", err); goto exit; } if (!mbss) { /* Firmware 10.x requires setting channel after enabling * AP and before bringing interface up. */ err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec); if (err < 0) { bphy_err(drvr, "Set Channel failed: chspec=%d, %d\n", chanspec, err); goto exit; } } err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); if (err < 0) { bphy_err(drvr, "BRCMF_C_UP error (%d)\n", err); goto exit; } if (crypto->psk) { brcmf_dbg(INFO, "using PSK offload\n"); profile->use_fwauth |= BIT(BRCMF_PROFILE_FWAUTH_PSK); err = brcmf_set_pmk(ifp, crypto->psk, BRCMF_WSEC_MAX_PSK_LEN); if (err < 0) goto exit; } if (crypto->sae_pwd) { brcmf_dbg(INFO, "using SAE offload\n"); profile->use_fwauth |= BIT(BRCMF_PROFILE_FWAUTH_SAE); err = brcmf_fwvid_set_sae_password(ifp, crypto); if (err < 0) goto exit; } if (profile->use_fwauth == 0) profile->use_fwauth = BIT(BRCMF_PROFILE_FWAUTH_NONE); err = brcmf_parse_configure_security(ifp, settings, NL80211_IFTYPE_AP); if (err < 0) { bphy_err(drvr, "brcmf_parse_configure_security error\n"); goto exit; } /* On DOWN the firmware removes the WEP keys, reconfigure * them if they were set. */ brcmf_cfg80211_reconfigure_wep(ifp); memset(&join_params, 0, sizeof(join_params)); /* join parameters starts with ssid */ memcpy(&join_params.ssid_le, &ssid_le, sizeof(ssid_le)); /* create softap */ err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, &join_params, sizeof(join_params)); if (err < 0) { bphy_err(drvr, "SET SSID error (%d)\n", err); goto exit; } closednet = (settings->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE); err = brcmf_fil_iovar_int_set(ifp, "closednet", closednet); if (err) { bphy_err(drvr, "%s closednet error (%d)\n", (closednet ? "enabled" : "disabled"), err); goto exit; } brcmf_dbg(TRACE, "AP mode configuration complete\n"); } else if (dev_role == NL80211_IFTYPE_P2P_GO) { err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec); if (err < 0) { bphy_err(drvr, "Set Channel failed: chspec=%d, %d\n", chanspec, err); goto exit; } err = brcmf_parse_configure_security(ifp, settings, NL80211_IFTYPE_P2P_GO); if (err < 0) { brcmf_err("brcmf_parse_configure_security error\n"); goto exit; } err = brcmf_fil_bsscfg_data_set(ifp, "ssid", &ssid_le, sizeof(ssid_le)); if (err < 0) { bphy_err(drvr, "setting ssid failed %d\n", err); goto exit; } bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx); bss_enable.enable = cpu_to_le32(1); err = brcmf_fil_iovar_data_set(ifp, "bss", &bss_enable, sizeof(bss_enable)); if (err < 0) { bphy_err(drvr, "bss_enable config failed %d\n", err); goto exit; } brcmf_dbg(TRACE, "GO mode configuration complete\n"); } else { WARN_ON(1); } brcmf_config_ap_mgmt_ie(ifp->vif, &settings->beacon); set_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state); brcmf_net_setcarrier(ifp, true); exit: if ((err) && (!mbss)) { brcmf_set_mpc(ifp, 1); brcmf_configure_arp_nd_offload(ifp, true); } return err; } static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev, unsigned int link_id) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; s32 err; struct brcmf_fil_bss_enable_le bss_enable; struct brcmf_join_params join_params; brcmf_dbg(TRACE, "Enter\n"); if (ifp->vif->wdev.iftype == NL80211_IFTYPE_AP) { /* Due to most likely deauths outstanding we sleep */ /* first to make sure they get processed by fw. */ #if defined(__linux__) msleep(400); #elif defined(__FreeBSD__) linux_msleep(400); #endif if (profile->use_fwauth != BIT(BRCMF_PROFILE_FWAUTH_NONE)) { struct cfg80211_crypto_settings crypto = {}; if (profile->use_fwauth & BIT(BRCMF_PROFILE_FWAUTH_PSK)) brcmf_set_pmk(ifp, NULL, 0); if (profile->use_fwauth & BIT(BRCMF_PROFILE_FWAUTH_SAE)) brcmf_fwvid_set_sae_password(ifp, &crypto); profile->use_fwauth = BIT(BRCMF_PROFILE_FWAUTH_NONE); } if (ifp->vif->mbss) { err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); return err; } /* First BSS doesn't get a full reset */ if (ifp->bsscfgidx == 0) brcmf_fil_iovar_int_set(ifp, "closednet", 0); memset(&join_params, 0, sizeof(join_params)); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, &join_params, sizeof(join_params)); if (err < 0) bphy_err(drvr, "SET SSID error (%d)\n", err); err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); if (err < 0) bphy_err(drvr, "BRCMF_C_DOWN error %d\n", err); err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_AP, 0); if (err < 0) bphy_err(drvr, "setting AP mode failed %d\n", err); if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MBSS)) brcmf_fil_iovar_int_set(ifp, "mbss", 0); brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_REGULATORY, ifp->vif->is_11d); /* Bring device back up so it can be used again */ err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); if (err < 0) bphy_err(drvr, "BRCMF_C_UP error %d\n", err); brcmf_vif_clear_mgmt_ies(ifp->vif); } else { bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx); bss_enable.enable = cpu_to_le32(0); err = brcmf_fil_iovar_data_set(ifp, "bss", &bss_enable, sizeof(bss_enable)); if (err < 0) bphy_err(drvr, "bss_enable config failed %d\n", err); } brcmf_set_mpc(ifp, 1); clear_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state); brcmf_configure_arp_nd_offload(ifp, true); brcmf_net_setcarrier(ifp, false); return err; } static s32 brcmf_cfg80211_change_beacon(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_ap_update *info) { struct brcmf_if *ifp = netdev_priv(ndev); brcmf_dbg(TRACE, "Enter\n"); return brcmf_config_ap_mgmt_ie(ifp->vif, &info->beacon); } static int brcmf_cfg80211_del_station(struct wiphy *wiphy, struct net_device *ndev, struct station_del_parameters *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct brcmf_scb_val_le scbval; struct brcmf_if *ifp = netdev_priv(ndev); s32 err; if (!params->mac) return -EFAULT; brcmf_dbg(TRACE, "Enter %pM\n", params->mac); if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif) ifp = cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; if (!check_vif_up(ifp->vif)) return -EIO; memcpy(&scbval.ea, params->mac, ETH_ALEN); scbval.val = cpu_to_le32(params->reason_code); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCB_DEAUTHENTICATE_FOR_REASON, &scbval, sizeof(scbval)); if (err) bphy_err(drvr, "SCB_DEAUTHENTICATE_FOR_REASON failed %d\n", err); brcmf_dbg(TRACE, "Exit\n"); return err; } static int brcmf_cfg80211_change_station(struct wiphy *wiphy, struct net_device *ndev, const u8 *mac, struct station_parameters *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct brcmf_if *ifp = netdev_priv(ndev); s32 err; brcmf_dbg(TRACE, "Enter, MAC %pM, mask 0x%04x set 0x%04x\n", mac, params->sta_flags_mask, params->sta_flags_set); /* Ignore all 00 MAC */ if (is_zero_ether_addr(mac)) return 0; if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))) return 0; if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SCB_AUTHORIZE, #if defined(__linux__) (void *)mac, ETH_ALEN); #elif defined(__FreeBSD__) __DECONST(u8 *, mac), ETH_ALEN); #endif else err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SCB_DEAUTHORIZE, #if defined(__linux__) (void *)mac, ETH_ALEN); #elif defined(__FreeBSD__) __DECONST(u8 *, mac), ETH_ALEN); #endif if (err < 0) bphy_err(drvr, "Setting SCB (de-)authorize failed, %d\n", err); return err; } static void brcmf_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, struct wireless_dev *wdev, struct mgmt_frame_regs *upd) { struct brcmf_cfg80211_vif *vif; vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); vif->mgmt_rx_reg = upd->interface_stypes; } int brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, u64 *cookie) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct ieee80211_channel *chan = params->chan; struct brcmf_pub *drvr = cfg->pub; const u8 *buf = params->buf; size_t len = params->len; const struct ieee80211_mgmt *mgmt; struct brcmf_cfg80211_vif *vif; s32 err = 0; s32 ie_offset; s32 ie_len; struct brcmf_fil_action_frame_le *action_frame; struct brcmf_fil_af_params_le *af_params; bool ack; __le32 hw_ch; brcmf_dbg(TRACE, "Enter\n"); *cookie = 0; mgmt = (const struct ieee80211_mgmt *)buf; if (!ieee80211_is_mgmt(mgmt->frame_control)) { bphy_err(drvr, "Driver only allows MGMT packet type\n"); return -EPERM; } vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); if (ieee80211_is_probe_resp(mgmt->frame_control)) { /* Right now the only reason to get a probe response */ /* is for p2p listen response or for p2p GO from */ /* wpa_supplicant. Unfortunately the probe is send */ /* on primary ndev, while dongle wants it on the p2p */ /* vif. Since this is only reason for a probe */ /* response to be sent, the vif is taken from cfg. */ /* If ever desired to send proberesp for non p2p */ /* response then data should be checked for */ /* "DIRECT-". Note in future supplicant will take */ /* dedicated p2p wdev to do this and then this 'hack'*/ /* is not needed anymore. */ ie_offset = DOT11_MGMT_HDR_LEN + DOT11_BCN_PRB_FIXED_LEN; ie_len = len - ie_offset; if (vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif; err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_PRBRSP_FLAG, &buf[ie_offset], ie_len); cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, GFP_KERNEL); } else if (ieee80211_is_action(mgmt->frame_control)) { if (len > BRCMF_FIL_ACTION_FRAME_SIZE + DOT11_MGMT_HDR_LEN) { bphy_err(drvr, "invalid action frame length\n"); err = -EINVAL; goto exit; } af_params = kzalloc(sizeof(*af_params), GFP_KERNEL); if (af_params == NULL) { bphy_err(drvr, "unable to allocate frame\n"); err = -ENOMEM; goto exit; } action_frame = &af_params->action_frame; /* Add the packet Id */ action_frame->packet_id = cpu_to_le32(*cookie); /* Add BSSID */ memcpy(&action_frame->da[0], &mgmt->da[0], ETH_ALEN); memcpy(&af_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); /* Add the length exepted for 802.11 header */ action_frame->len = cpu_to_le16(len - DOT11_MGMT_HDR_LEN); /* Add the channel. Use the one specified as parameter if any or * the current one (got from the firmware) otherwise */ if (chan) { hw_ch = cpu_to_le32(chan->hw_value); } else { err = brcmf_fil_cmd_data_get(vif->ifp, BRCMF_C_GET_CHANNEL, &hw_ch, sizeof(hw_ch)); if (err) { bphy_err(drvr, "unable to get current hw channel\n"); goto free; } } af_params->channel = hw_ch; af_params->dwell_time = cpu_to_le32(params->wait); memcpy(action_frame->data, &buf[DOT11_MGMT_HDR_LEN], le16_to_cpu(action_frame->len)); #if defined(__linux__) brcmf_dbg(TRACE, "Action frame, cookie=%lld, len=%d, channel=%d\n", *cookie, le16_to_cpu(action_frame->len), #elif defined(__FreeBSD__) brcmf_dbg(TRACE, "Action frame, cookie=%ju, len=%d, channel=%d\n", (uintmax_t)*cookie, le16_to_cpu(action_frame->len), #endif le32_to_cpu(af_params->channel)); - ack = brcmf_p2p_send_action_frame(cfg, cfg_to_ndev(cfg), - af_params); + ack = brcmf_p2p_send_action_frame(vif->ifp, af_params); cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, GFP_KERNEL); free: kfree(af_params); } else { brcmf_dbg(TRACE, "Unhandled, fc=%04x!!\n", mgmt->frame_control); brcmf_dbg_hex_dump(true, buf, len, "payload, len=%zu\n", len); } exit: return err; } BRCMF_EXPORT_SYMBOL_GPL(brcmf_cfg80211_mgmt_tx); static int brcmf_cfg80211_set_cqm_rssi_range_config(struct wiphy *wiphy, struct net_device *ndev, s32 rssi_low, s32 rssi_high) { struct brcmf_cfg80211_vif *vif; struct brcmf_if *ifp; int err = 0; brcmf_dbg(TRACE, "low=%d high=%d", rssi_low, rssi_high); ifp = netdev_priv(ndev); vif = ifp->vif; if (rssi_low != vif->cqm_rssi_low || rssi_high != vif->cqm_rssi_high) { /* The firmware will send an event when the RSSI is less than or * equal to a configured level and the previous RSSI event was * less than or equal to a different level. Set a third level * so that we also detect the transition from rssi <= rssi_high * to rssi > rssi_high. */ struct brcmf_rssi_event_le config = { .rate_limit_msec = cpu_to_le32(0), .rssi_level_num = 3, .rssi_levels = { clamp_val(rssi_low, S8_MIN, S8_MAX - 2), clamp_val(rssi_high, S8_MIN + 1, S8_MAX - 1), S8_MAX, }, }; err = brcmf_fil_iovar_data_set(ifp, "rssi_event", &config, sizeof(config)); if (err) { err = -EINVAL; } else { vif->cqm_rssi_low = rssi_low; vif->cqm_rssi_high = rssi_high; } } return err; } static int brcmf_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_vif *vif; int err = 0; brcmf_dbg(TRACE, "Enter p2p listen cancel\n"); vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif; if (vif == NULL) { bphy_err(drvr, "No p2p device available for probe response\n"); err = -ENODEV; goto exit; } brcmf_p2p_cancel_remain_on_channel(vif->ifp); exit: return err; } static int brcmf_cfg80211_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev, unsigned int link_id, struct cfg80211_chan_def *chandef) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = wdev->netdev; struct brcmf_pub *drvr = cfg->pub; struct brcmu_chan ch; enum nl80211_band band = 0; enum nl80211_chan_width width = 0; u32 chanspec; int freq, err; if (!ndev || drvr->bus_if->state != BRCMF_BUS_UP) return -ENODEV; err = brcmf_fil_iovar_int_get(netdev_priv(ndev), "chanspec", &chanspec); if (err) { bphy_err(drvr, "chanspec failed (%d)\n", err); return err; } ch.chspec = chanspec; cfg->d11inf.decchspec(&ch); switch (ch.band) { case BRCMU_CHAN_BAND_2G: band = NL80211_BAND_2GHZ; break; case BRCMU_CHAN_BAND_5G: band = NL80211_BAND_5GHZ; break; } switch (ch.bw) { case BRCMU_CHAN_BW_80: width = NL80211_CHAN_WIDTH_80; break; case BRCMU_CHAN_BW_40: width = NL80211_CHAN_WIDTH_40; break; case BRCMU_CHAN_BW_20: width = NL80211_CHAN_WIDTH_20; break; case BRCMU_CHAN_BW_80P80: width = NL80211_CHAN_WIDTH_80P80; break; case BRCMU_CHAN_BW_160: width = NL80211_CHAN_WIDTH_160; break; } freq = ieee80211_channel_to_frequency(ch.control_ch_num, band); chandef->chan = ieee80211_get_channel(wiphy, freq); chandef->width = width; chandef->center_freq1 = ieee80211_channel_to_frequency(ch.chnum, band); chandef->center_freq2 = 0; return 0; } static int brcmf_cfg80211_crit_proto_start(struct wiphy *wiphy, struct wireless_dev *wdev, enum nl80211_crit_proto_id proto, u16 duration) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_cfg80211_vif *vif; vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); /* only DHCP support for now */ if (proto != NL80211_CRIT_PROTO_DHCP) return -EINVAL; /* suppress and abort scanning */ set_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); brcmf_abort_scanning(cfg); return brcmf_btcoex_set_mode(vif, BRCMF_BTCOEX_DISABLED, duration); } static void brcmf_cfg80211_crit_proto_stop(struct wiphy *wiphy, struct wireless_dev *wdev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_cfg80211_vif *vif; vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); brcmf_btcoex_set_mode(vif, BRCMF_BTCOEX_ENABLED, 0); clear_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); } static s32 brcmf_notify_tdls_peer_event(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { switch (e->reason) { case BRCMF_E_REASON_TDLS_PEER_DISCOVERED: brcmf_dbg(TRACE, "TDLS Peer Discovered\n"); break; case BRCMF_E_REASON_TDLS_PEER_CONNECTED: brcmf_dbg(TRACE, "TDLS Peer Connected\n"); #if defined(__linux__) brcmf_proto_add_tdls_peer(ifp->drvr, ifp->ifidx, (u8 *)e->addr); #elif defined(__FreeBSD__) brcmf_proto_add_tdls_peer(ifp->drvr, ifp->ifidx, e->addr); #endif break; case BRCMF_E_REASON_TDLS_PEER_DISCONNECTED: brcmf_dbg(TRACE, "TDLS Peer Disconnected\n"); #if defined(__linux__) brcmf_proto_delete_peer(ifp->drvr, ifp->ifidx, (u8 *)e->addr); #elif defined(__FreeBSD__) brcmf_proto_delete_peer(ifp->drvr, ifp->ifidx, e->addr); #endif break; } return 0; } static int brcmf_convert_nl80211_tdls_oper(enum nl80211_tdls_operation oper) { int ret; switch (oper) { case NL80211_TDLS_DISCOVERY_REQ: ret = BRCMF_TDLS_MANUAL_EP_DISCOVERY; break; case NL80211_TDLS_SETUP: ret = BRCMF_TDLS_MANUAL_EP_CREATE; break; case NL80211_TDLS_TEARDOWN: ret = BRCMF_TDLS_MANUAL_EP_DELETE; break; default: brcmf_err("unsupported operation: %d\n", oper); ret = -EOPNOTSUPP; } return ret; } static int brcmf_cfg80211_tdls_oper(struct wiphy *wiphy, struct net_device *ndev, const u8 *peer, enum nl80211_tdls_operation oper) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct brcmf_if *ifp; struct brcmf_tdls_iovar_le info; int ret = 0; ret = brcmf_convert_nl80211_tdls_oper(oper); if (ret < 0) return ret; ifp = netdev_priv(ndev); memset(&info, 0, sizeof(info)); info.mode = (u8)ret; if (peer) memcpy(info.ea, peer, ETH_ALEN); ret = brcmf_fil_iovar_data_set(ifp, "tdls_endpoint", &info, sizeof(info)); if (ret < 0) bphy_err(drvr, "tdls_endpoint iovar failed: ret=%d\n", ret); return ret; } static int brcmf_cfg80211_update_conn_params(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_connect_params *sme, u32 changed) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct brcmf_if *ifp; int err; if (!(changed & UPDATE_ASSOC_IES)) return 0; ifp = netdev_priv(ndev); err = brcmf_vif_set_mgmt_ie(ifp->vif, BRCMF_VNDR_IE_ASSOCREQ_FLAG, sme->ie, sme->ie_len); if (err) bphy_err(drvr, "Set Assoc REQ IE Failed\n"); else brcmf_dbg(TRACE, "Applied Vndr IEs for Assoc request\n"); return err; } #ifdef CONFIG_PM static int brcmf_cfg80211_set_rekey_data(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_gtk_rekey_data *gtk) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_gtk_keyinfo_le gtk_le; int ret; brcmf_dbg(TRACE, "Enter, bssidx=%d\n", ifp->bsscfgidx); memcpy(gtk_le.kck, gtk->kck, sizeof(gtk_le.kck)); memcpy(gtk_le.kek, gtk->kek, sizeof(gtk_le.kek)); memcpy(gtk_le.replay_counter, gtk->replay_ctr, sizeof(gtk_le.replay_counter)); ret = brcmf_fil_iovar_data_set(ifp, "gtk_key_info", >k_le, sizeof(gtk_le)); if (ret < 0) bphy_err(drvr, "gtk_key_info iovar failed: ret=%d\n", ret); return ret; } #endif static int brcmf_cfg80211_set_pmk(struct wiphy *wiphy, struct net_device *dev, const struct cfg80211_pmk_conf *conf) { struct brcmf_if *ifp; brcmf_dbg(TRACE, "enter\n"); /* expect using firmware supplicant for 1X */ ifp = netdev_priv(dev); if (WARN_ON(ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X)) return -EINVAL; if (conf->pmk_len > BRCMF_WSEC_MAX_PSK_LEN) return -ERANGE; return brcmf_set_pmk(ifp, conf->pmk, conf->pmk_len); } static int brcmf_cfg80211_del_pmk(struct wiphy *wiphy, struct net_device *dev, const u8 *aa) { struct brcmf_if *ifp; brcmf_dbg(TRACE, "enter\n"); ifp = netdev_priv(dev); if (WARN_ON(ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X)) return -EINVAL; return brcmf_set_pmk(ifp, NULL, 0); } +static int brcmf_cfg80211_change_bss(struct wiphy *wiphy, struct net_device *dev, + struct bss_parameters *params) +{ + struct brcmf_if *ifp = netdev_priv(dev); + int ret = 0; + + /* In AP mode, the "ap_isolate" value represents + * 0 = allow low-level bridging of frames between associated stations + * 1 = restrict low-level bridging of frames to isolate associated stations + * -1 = do not change existing setting + */ + if (params->ap_isolate >= 0) { + ret = brcmf_fil_iovar_int_set(ifp, "ap_isolate", params->ap_isolate); + if (ret < 0) + brcmf_err("ap_isolate iovar failed: ret=%d\n", ret); + } + + return ret; +} + static struct cfg80211_ops brcmf_cfg80211_ops = { .add_virtual_intf = brcmf_cfg80211_add_iface, .del_virtual_intf = brcmf_cfg80211_del_iface, .change_virtual_intf = brcmf_cfg80211_change_iface, .scan = brcmf_cfg80211_scan, .set_wiphy_params = brcmf_cfg80211_set_wiphy_params, .join_ibss = brcmf_cfg80211_join_ibss, .leave_ibss = brcmf_cfg80211_leave_ibss, .get_station = brcmf_cfg80211_get_station, .dump_station = brcmf_cfg80211_dump_station, .set_tx_power = brcmf_cfg80211_set_tx_power, .get_tx_power = brcmf_cfg80211_get_tx_power, .add_key = brcmf_cfg80211_add_key, .del_key = brcmf_cfg80211_del_key, .get_key = brcmf_cfg80211_get_key, .set_default_key = brcmf_cfg80211_config_default_key, .set_default_mgmt_key = brcmf_cfg80211_config_default_mgmt_key, .set_power_mgmt = brcmf_cfg80211_set_power_mgmt, .connect = brcmf_cfg80211_connect, .disconnect = brcmf_cfg80211_disconnect, .suspend = brcmf_cfg80211_suspend, .resume = brcmf_cfg80211_resume, .set_pmksa = brcmf_cfg80211_set_pmksa, .del_pmksa = brcmf_cfg80211_del_pmksa, .flush_pmksa = brcmf_cfg80211_flush_pmksa, .start_ap = brcmf_cfg80211_start_ap, .stop_ap = brcmf_cfg80211_stop_ap, .change_beacon = brcmf_cfg80211_change_beacon, .del_station = brcmf_cfg80211_del_station, .change_station = brcmf_cfg80211_change_station, .sched_scan_start = brcmf_cfg80211_sched_scan_start, .sched_scan_stop = brcmf_cfg80211_sched_scan_stop, .update_mgmt_frame_registrations = brcmf_cfg80211_update_mgmt_frame_registrations, .mgmt_tx = brcmf_cfg80211_mgmt_tx, .set_cqm_rssi_range_config = brcmf_cfg80211_set_cqm_rssi_range_config, .remain_on_channel = brcmf_p2p_remain_on_channel, .cancel_remain_on_channel = brcmf_cfg80211_cancel_remain_on_channel, .get_channel = brcmf_cfg80211_get_channel, .start_p2p_device = brcmf_p2p_start_device, .stop_p2p_device = brcmf_p2p_stop_device, .crit_proto_start = brcmf_cfg80211_crit_proto_start, .crit_proto_stop = brcmf_cfg80211_crit_proto_stop, .tdls_oper = brcmf_cfg80211_tdls_oper, .update_connect_params = brcmf_cfg80211_update_conn_params, .set_pmk = brcmf_cfg80211_set_pmk, .del_pmk = brcmf_cfg80211_del_pmk, + .change_bss = brcmf_cfg80211_change_bss, }; struct cfg80211_ops *brcmf_cfg80211_get_ops(struct brcmf_mp_device *settings) { struct cfg80211_ops *ops; ops = kmemdup(&brcmf_cfg80211_ops, sizeof(brcmf_cfg80211_ops), GFP_KERNEL); if (ops && settings->roamoff) ops->update_connect_params = NULL; return ops; } struct brcmf_cfg80211_vif *brcmf_alloc_vif(struct brcmf_cfg80211_info *cfg, enum nl80211_iftype type) { struct brcmf_cfg80211_vif *vif_walk; struct brcmf_cfg80211_vif *vif; bool mbss; struct brcmf_if *ifp = brcmf_get_ifp(cfg->pub, 0); brcmf_dbg(TRACE, "allocating virtual interface (size=%zu)\n", sizeof(*vif)); vif = kzalloc(sizeof(*vif), GFP_KERNEL); if (!vif) return ERR_PTR(-ENOMEM); vif->wdev.wiphy = cfg->wiphy; vif->wdev.iftype = type; init_completion(&vif->mgmt_tx); brcmf_init_prof(&vif->profile); if (type == NL80211_IFTYPE_AP && brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MBSS)) { mbss = false; list_for_each_entry(vif_walk, &cfg->vif_list, list) { if (vif_walk->wdev.iftype == NL80211_IFTYPE_AP) { mbss = true; break; } } vif->mbss = mbss; } list_add_tail(&vif->list, &cfg->vif_list); return vif; } void brcmf_free_vif(struct brcmf_cfg80211_vif *vif) { list_del(&vif->list); kfree(vif); } void brcmf_cfg80211_free_netdev(struct net_device *ndev) { struct brcmf_cfg80211_vif *vif; struct brcmf_if *ifp; ifp = netdev_priv(ndev); vif = ifp->vif; if (vif) brcmf_free_vif(vif); } static bool brcmf_is_linkup(struct brcmf_cfg80211_vif *vif, const struct brcmf_event_msg *e) { u32 event = e->event_code; u32 status = e->status; if ((vif->profile.use_fwsup == BRCMF_PROFILE_FWSUP_PSK || vif->profile.use_fwsup == BRCMF_PROFILE_FWSUP_SAE) && event == BRCMF_E_PSK_SUP && status == BRCMF_E_STATUS_FWSUP_COMPLETED) set_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); if (event == BRCMF_E_SET_SSID && status == BRCMF_E_STATUS_SUCCESS) { brcmf_dbg(CONN, "Processing set ssid\n"); memcpy(vif->profile.bssid, e->addr, ETH_ALEN); if (vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_PSK && vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_SAE) return true; set_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); } if (test_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state) && test_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state)) { clear_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); return true; } return false; } static bool brcmf_is_linkdown(struct brcmf_cfg80211_vif *vif, const struct brcmf_event_msg *e) { u32 event = e->event_code; u16 flags = e->flags; if ((event == BRCMF_E_DEAUTH) || (event == BRCMF_E_DEAUTH_IND) || (event == BRCMF_E_DISASSOC_IND) || ((event == BRCMF_E_LINK) && (!(flags & BRCMF_EVENT_MSG_LINK)))) { brcmf_dbg(CONN, "Processing link down\n"); clear_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); return true; } return false; } static bool brcmf_is_nonetwork(struct brcmf_cfg80211_info *cfg, const struct brcmf_event_msg *e) { u32 event = e->event_code; u32 status = e->status; if (event == BRCMF_E_LINK && status == BRCMF_E_STATUS_NO_NETWORKS) { brcmf_dbg(CONN, "Processing Link %s & no network found\n", e->flags & BRCMF_EVENT_MSG_LINK ? "up" : "down"); return true; } if (event == BRCMF_E_SET_SSID && status != BRCMF_E_STATUS_SUCCESS) { brcmf_dbg(CONN, "Processing connecting & no network found\n"); return true; } if (event == BRCMF_E_PSK_SUP && status != BRCMF_E_STATUS_FWSUP_COMPLETED) { brcmf_dbg(CONN, "Processing failed supplicant state: %u\n", status); return true; } return false; } static void brcmf_clear_assoc_ies(struct brcmf_cfg80211_info *cfg) { struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); kfree(conn_info->req_ie); conn_info->req_ie = NULL; conn_info->req_ie_len = 0; kfree(conn_info->resp_ie); conn_info->resp_ie = NULL; conn_info->resp_ie_len = 0; } u8 brcmf_map_prio_to_prec(void *config, u8 prio) { struct brcmf_cfg80211_info *cfg = (struct brcmf_cfg80211_info *)config; if (!cfg) return (prio == PRIO_8021D_NONE || prio == PRIO_8021D_BE) ? (prio ^ 2) : prio; /* For those AC(s) with ACM flag set to 1, convert its 4-level priority * to an 8-level precedence which is the same as BE's */ if (prio > PRIO_8021D_EE && cfg->ac_priority[prio] == cfg->ac_priority[PRIO_8021D_BE]) return cfg->ac_priority[prio] * 2; /* Conversion of 4-level priority to 8-level precedence */ if (prio == PRIO_8021D_BE || prio == PRIO_8021D_BK || prio == PRIO_8021D_CL || prio == PRIO_8021D_VO) return cfg->ac_priority[prio] * 2; else return cfg->ac_priority[prio] * 2 + 1; } u8 brcmf_map_prio_to_aci(void *config, u8 prio) { /* Prio here refers to the 802.1d priority in range of 0 to 7. * ACI here refers to the WLAN AC Index in range of 0 to 3. * This function will return ACI corresponding to input prio. */ struct brcmf_cfg80211_info *cfg = (struct brcmf_cfg80211_info *)config; if (cfg) return cfg->ac_priority[prio]; return prio; } static void brcmf_init_wmm_prio(u8 *priority) { /* Initialize AC priority array to default * 802.1d priority as per following table: * 802.1d prio 0,3 maps to BE * 802.1d prio 1,2 maps to BK * 802.1d prio 4,5 maps to VI * 802.1d prio 6,7 maps to VO */ priority[0] = BRCMF_FWS_FIFO_AC_BE; priority[3] = BRCMF_FWS_FIFO_AC_BE; priority[1] = BRCMF_FWS_FIFO_AC_BK; priority[2] = BRCMF_FWS_FIFO_AC_BK; priority[4] = BRCMF_FWS_FIFO_AC_VI; priority[5] = BRCMF_FWS_FIFO_AC_VI; priority[6] = BRCMF_FWS_FIFO_AC_VO; priority[7] = BRCMF_FWS_FIFO_AC_VO; } static void brcmf_wifi_prioritize_acparams(const struct brcmf_cfg80211_edcf_acparam *acp, u8 *priority) { u8 aci; u8 aifsn; u8 ecwmin; u8 ecwmax; u8 acm; u8 ranking_basis[EDCF_AC_COUNT]; u8 aci_prio[EDCF_AC_COUNT]; /* AC_BE, AC_BK, AC_VI, AC_VO */ u8 index; for (aci = 0; aci < EDCF_AC_COUNT; aci++, acp++) { aifsn = acp->ACI & EDCF_AIFSN_MASK; acm = (acp->ACI & EDCF_ACM_MASK) ? 1 : 0; ecwmin = acp->ECW & EDCF_ECWMIN_MASK; ecwmax = (acp->ECW & EDCF_ECWMAX_MASK) >> EDCF_ECWMAX_SHIFT; brcmf_dbg(CONN, "ACI %d aifsn %d acm %d ecwmin %d ecwmax %d\n", aci, aifsn, acm, ecwmin, ecwmax); /* Default AC_VO will be the lowest ranking value */ ranking_basis[aci] = aifsn + ecwmin + ecwmax; /* Initialise priority starting at 0 (AC_BE) */ aci_prio[aci] = 0; /* If ACM is set, STA can't use this AC as per 802.11. * Change the ranking to BE */ if (aci != AC_BE && aci != AC_BK && acm == 1) ranking_basis[aci] = ranking_basis[AC_BE]; } /* Ranking method which works for AC priority * swapping when values for cwmin, cwmax and aifsn are varied * Compare each aci_prio against each other aci_prio */ for (aci = 0; aci < EDCF_AC_COUNT; aci++) { for (index = 0; index < EDCF_AC_COUNT; index++) { if (index != aci) { /* Smaller ranking value has higher priority, * so increment priority for each ACI which has * a higher ranking value */ if (ranking_basis[aci] < ranking_basis[index]) aci_prio[aci]++; } } } /* By now, aci_prio[] will be in range of 0 to 3. * Use ACI prio to get the new priority value for * each 802.1d traffic type, in this range. */ if (!(aci_prio[AC_BE] == aci_prio[AC_BK] && aci_prio[AC_BK] == aci_prio[AC_VI] && aci_prio[AC_VI] == aci_prio[AC_VO])) { /* 802.1d 0,3 maps to BE */ priority[0] = aci_prio[AC_BE]; priority[3] = aci_prio[AC_BE]; /* 802.1d 1,2 maps to BK */ priority[1] = aci_prio[AC_BK]; priority[2] = aci_prio[AC_BK]; /* 802.1d 4,5 maps to VO */ priority[4] = aci_prio[AC_VI]; priority[5] = aci_prio[AC_VI]; /* 802.1d 6,7 maps to VO */ priority[6] = aci_prio[AC_VO]; priority[7] = aci_prio[AC_VO]; } else { /* Initialize to default priority */ brcmf_init_wmm_prio(priority); } brcmf_dbg(CONN, "Adj prio BE 0->%d, BK 1->%d, BK 2->%d, BE 3->%d\n", priority[0], priority[1], priority[2], priority[3]); brcmf_dbg(CONN, "Adj prio VI 4->%d, VI 5->%d, VO 6->%d, VO 7->%d\n", priority[4], priority[5], priority[6], priority[7]); } static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp) { struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_assoc_ielen_le *assoc_info; struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); struct brcmf_cfg80211_edcf_acparam edcf_acparam_info[EDCF_AC_COUNT]; u32 req_len; u32 resp_len; s32 err = 0; brcmf_clear_assoc_ies(cfg); err = brcmf_fil_iovar_data_get(ifp, "assoc_info", cfg->extra_buf, WL_ASSOC_INFO_MAX); if (err) { bphy_err(drvr, "could not get assoc info (%d)\n", err); return err; } assoc_info = (struct brcmf_cfg80211_assoc_ielen_le *)cfg->extra_buf; req_len = le32_to_cpu(assoc_info->req_len); resp_len = le32_to_cpu(assoc_info->resp_len); if (req_len > WL_EXTRA_BUF_MAX || resp_len > WL_EXTRA_BUF_MAX) { bphy_err(drvr, "invalid lengths in assoc info: req %u resp %u\n", req_len, resp_len); return -EINVAL; } if (req_len) { err = brcmf_fil_iovar_data_get(ifp, "assoc_req_ies", cfg->extra_buf, WL_ASSOC_INFO_MAX); if (err) { bphy_err(drvr, "could not get assoc req (%d)\n", err); return err; } conn_info->req_ie_len = req_len; conn_info->req_ie = kmemdup(cfg->extra_buf, conn_info->req_ie_len, GFP_KERNEL); if (!conn_info->req_ie) conn_info->req_ie_len = 0; } else { conn_info->req_ie_len = 0; conn_info->req_ie = NULL; } if (resp_len) { err = brcmf_fil_iovar_data_get(ifp, "assoc_resp_ies", cfg->extra_buf, WL_ASSOC_INFO_MAX); if (err) { bphy_err(drvr, "could not get assoc resp (%d)\n", err); return err; } conn_info->resp_ie_len = resp_len; conn_info->resp_ie = kmemdup(cfg->extra_buf, conn_info->resp_ie_len, GFP_KERNEL); if (!conn_info->resp_ie) conn_info->resp_ie_len = 0; err = brcmf_fil_iovar_data_get(ifp, "wme_ac_sta", edcf_acparam_info, sizeof(edcf_acparam_info)); if (err) { brcmf_err("could not get wme_ac_sta (%d)\n", err); return err; } brcmf_wifi_prioritize_acparams(edcf_acparam_info, cfg->ac_priority); } else { conn_info->resp_ie_len = 0; conn_info->resp_ie = NULL; } brcmf_dbg(CONN, "req len (%d) resp len (%d)\n", conn_info->req_ie_len, conn_info->resp_ie_len); return err; } static s32 brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, const struct brcmf_event_msg *e) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); struct wiphy *wiphy = cfg_to_wiphy(cfg); struct ieee80211_channel *notify_channel = NULL; struct ieee80211_supported_band *band; struct brcmf_bss_info_le *bi; struct brcmu_chan ch; struct cfg80211_roam_info roam_info = {}; u32 freq; s32 err = 0; u8 *buf; brcmf_dbg(TRACE, "Enter\n"); brcmf_get_assoc_ies(cfg, ifp); memcpy(profile->bssid, e->addr, ETH_ALEN); brcmf_update_bss_info(cfg, ifp); buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); if (buf == NULL) { err = -ENOMEM; goto done; } /* data sent to dongle has to be little endian */ *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, buf, WL_BSS_INFO_MAX); if (err) goto done; bi = (struct brcmf_bss_info_le *)(buf + 4); ch.chspec = le16_to_cpu(bi->chanspec); cfg->d11inf.decchspec(&ch); if (ch.band == BRCMU_CHAN_BAND_2G) band = wiphy->bands[NL80211_BAND_2GHZ]; else band = wiphy->bands[NL80211_BAND_5GHZ]; freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band); notify_channel = ieee80211_get_channel(wiphy, freq); done: kfree(buf); roam_info.links[0].channel = notify_channel; roam_info.links[0].bssid = profile->bssid; roam_info.req_ie = conn_info->req_ie; roam_info.req_ie_len = conn_info->req_ie_len; roam_info.resp_ie = conn_info->resp_ie; roam_info.resp_ie_len = conn_info->resp_ie_len; cfg80211_roamed(ndev, &roam_info, GFP_KERNEL); brcmf_dbg(CONN, "Report roaming result\n"); if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X && profile->is_ft) { cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, GFP_KERNEL); brcmf_dbg(CONN, "Report port authorized\n"); } set_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); brcmf_dbg(TRACE, "Exit\n"); return err; } static s32 brcmf_bss_connect_done(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, const struct brcmf_event_msg *e, bool completed) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); struct cfg80211_connect_resp_params conn_params; brcmf_dbg(TRACE, "Enter\n"); if (test_and_clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state)) { memset(&conn_params, 0, sizeof(conn_params)); if (completed) { brcmf_get_assoc_ies(cfg, ifp); brcmf_update_bss_info(cfg, ifp); set_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); conn_params.status = WLAN_STATUS_SUCCESS; } else { clear_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &ifp->vif->sme_state); clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &ifp->vif->sme_state); conn_params.status = WLAN_STATUS_AUTH_TIMEOUT; } conn_params.links[0].bssid = profile->bssid; conn_params.req_ie = conn_info->req_ie; conn_params.req_ie_len = conn_info->req_ie_len; conn_params.resp_ie = conn_info->resp_ie; conn_params.resp_ie_len = conn_info->resp_ie_len; cfg80211_connect_done(ndev, &conn_params, GFP_KERNEL); brcmf_dbg(CONN, "Report connect result - connection %s\n", completed ? "succeeded" : "failed"); } brcmf_dbg(TRACE, "Exit\n"); return 0; } static s32 brcmf_notify_connect_status_ap(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, const struct brcmf_event_msg *e, void *data) { struct brcmf_pub *drvr = cfg->pub; static int generation; u32 event = e->event_code; u32 reason = e->reason; struct station_info *sinfo; brcmf_dbg(CONN, "event %s (%u), reason %d\n", brcmf_fweh_event_name(event), event, reason); if (event == BRCMF_E_LINK && reason == BRCMF_E_REASON_LINK_BSSCFG_DIS && ndev != cfg_to_ndev(cfg)) { brcmf_dbg(CONN, "AP mode link down\n"); complete(&cfg->vif_disabled); return 0; } if (((event == BRCMF_E_ASSOC_IND) || (event == BRCMF_E_REASSOC_IND)) && (reason == BRCMF_E_STATUS_SUCCESS)) { if (!data) { bphy_err(drvr, "No IEs present in ASSOC/REASSOC_IND\n"); return -EINVAL; } sinfo = kzalloc(sizeof(*sinfo), GFP_KERNEL); if (!sinfo) return -ENOMEM; sinfo->assoc_req_ies = data; sinfo->assoc_req_ies_len = e->datalen; generation++; sinfo->generation = generation; cfg80211_new_sta(ndev, e->addr, sinfo, GFP_KERNEL); kfree(sinfo); } else if ((event == BRCMF_E_DISASSOC_IND) || (event == BRCMF_E_DEAUTH_IND) || (event == BRCMF_E_DEAUTH)) { cfg80211_del_sta(ndev, e->addr, GFP_KERNEL); } return 0; } static s32 brcmf_notify_connect_status(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; struct net_device *ndev = ifp->ndev; struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct ieee80211_channel *chan; s32 err = 0; if ((e->event_code == BRCMF_E_DEAUTH) || (e->event_code == BRCMF_E_DEAUTH_IND) || (e->event_code == BRCMF_E_DISASSOC_IND) || ((e->event_code == BRCMF_E_LINK) && (!e->flags))) { #if defined(__linux__) brcmf_proto_delete_peer(ifp->drvr, ifp->ifidx, (u8 *)e->addr); #elif defined(__FreeBSD__) brcmf_proto_delete_peer(ifp->drvr, ifp->ifidx, e->addr); #endif } if (brcmf_is_apmode(ifp->vif)) { err = brcmf_notify_connect_status_ap(cfg, ndev, e, data); } else if (brcmf_is_linkup(ifp->vif, e)) { brcmf_dbg(CONN, "Linkup\n"); if (brcmf_is_ibssmode(ifp->vif)) { brcmf_inform_ibss(cfg, ndev, e->addr); chan = ieee80211_get_channel(cfg->wiphy, cfg->channel); memcpy(profile->bssid, e->addr, ETH_ALEN); cfg80211_ibss_joined(ndev, e->addr, chan, GFP_KERNEL); clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); set_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); } else brcmf_bss_connect_done(cfg, ndev, e, true); brcmf_net_setcarrier(ifp, true); } else if (brcmf_is_linkdown(ifp->vif, e)) { brcmf_dbg(CONN, "Linkdown\n"); if (!brcmf_is_ibssmode(ifp->vif) && (test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state) || test_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state))) { if (test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state) && memcmp(profile->bssid, e->addr, ETH_ALEN)) return err; brcmf_bss_connect_done(cfg, ndev, e, false); brcmf_link_down(ifp->vif, brcmf_map_fw_linkdown_reason(e), e->event_code & (BRCMF_E_DEAUTH_IND | BRCMF_E_DISASSOC_IND) ? false : true); brcmf_init_prof(ndev_to_prof(ndev)); if (ndev != cfg_to_ndev(cfg)) complete(&cfg->vif_disabled); brcmf_net_setcarrier(ifp, false); } } else if (brcmf_is_nonetwork(cfg, e)) { if (brcmf_is_ibssmode(ifp->vif)) clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); else brcmf_bss_connect_done(cfg, ndev, e, false); } return err; } static s32 brcmf_notify_roaming_status(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; u32 event = e->event_code; u32 status = e->status; if (event == BRCMF_E_ROAM && status == BRCMF_E_STATUS_SUCCESS) { if (test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state)) { brcmf_bss_roaming_done(cfg, ifp->ndev, e); } else { brcmf_bss_connect_done(cfg, ifp->ndev, e, true); brcmf_net_setcarrier(ifp, true); } } return 0; } static s32 brcmf_notify_mic_status(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { u16 flags = e->flags; enum nl80211_key_type key_type; if (flags & BRCMF_EVENT_MSG_GROUP) key_type = NL80211_KEYTYPE_GROUP; else key_type = NL80211_KEYTYPE_PAIRWISE; #if defined(__linux__) cfg80211_michael_mic_failure(ifp->ndev, (u8 *)&e->addr, key_type, -1, #elif defined(__FreeBSD__) cfg80211_michael_mic_failure(ifp->ndev, e->addr, key_type, -1, #endif NULL, GFP_KERNEL); return 0; } static s32 brcmf_notify_rssi(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_cfg80211_vif *vif = ifp->vif; struct brcmf_rssi_be *info = data; s32 rssi, snr = 0, noise = 0; s32 low, high, last; if (e->datalen >= sizeof(*info)) { rssi = be32_to_cpu(info->rssi); snr = be32_to_cpu(info->snr); noise = be32_to_cpu(info->noise); } else if (e->datalen >= sizeof(rssi)) { rssi = be32_to_cpu(*(__be32 *)data); } else { brcmf_err("insufficient RSSI event data\n"); return 0; } low = vif->cqm_rssi_low; high = vif->cqm_rssi_high; last = vif->cqm_rssi_last; brcmf_dbg(TRACE, "rssi=%d snr=%d noise=%d low=%d high=%d last=%d\n", rssi, snr, noise, low, high, last); vif->cqm_rssi_last = rssi; if (rssi <= low || rssi == 0) { brcmf_dbg(INFO, "LOW rssi=%d\n", rssi); cfg80211_cqm_rssi_notify(ifp->ndev, NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, rssi, GFP_KERNEL); } else if (rssi > high) { brcmf_dbg(INFO, "HIGH rssi=%d\n", rssi); cfg80211_cqm_rssi_notify(ifp->ndev, NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, rssi, GFP_KERNEL); } return 0; } static s32 brcmf_notify_vif_event(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; struct brcmf_if_event *ifevent = (struct brcmf_if_event *)data; struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; struct brcmf_cfg80211_vif *vif; brcmf_dbg(TRACE, "Enter: action %u flags %u ifidx %u bsscfgidx %u\n", ifevent->action, ifevent->flags, ifevent->ifidx, ifevent->bsscfgidx); spin_lock(&event->vif_event_lock); event->action = ifevent->action; vif = event->vif; switch (ifevent->action) { case BRCMF_E_IF_ADD: /* waiting process may have timed out */ if (!cfg->vif_event.vif) { spin_unlock(&event->vif_event_lock); return -EBADF; } ifp->vif = vif; vif->ifp = ifp; if (ifp->ndev) { vif->wdev.netdev = ifp->ndev; ifp->ndev->ieee80211_ptr = &vif->wdev; SET_NETDEV_DEV(ifp->ndev, wiphy_dev(cfg->wiphy)); } spin_unlock(&event->vif_event_lock); wake_up(&event->vif_wq); return 0; case BRCMF_E_IF_DEL: spin_unlock(&event->vif_event_lock); /* event may not be upon user request */ if (brcmf_cfg80211_vif_event_armed(cfg)) wake_up(&event->vif_wq); return 0; case BRCMF_E_IF_CHANGE: spin_unlock(&event->vif_event_lock); wake_up(&event->vif_wq); return 0; default: spin_unlock(&event->vif_event_lock); break; } return -EINVAL; } static void brcmf_init_conf(struct brcmf_cfg80211_conf *conf) { conf->frag_threshold = (u32)-1; conf->rts_threshold = (u32)-1; conf->retry_short = (u32)-1; conf->retry_long = (u32)-1; } static void brcmf_register_event_handlers(struct brcmf_cfg80211_info *cfg) { brcmf_fweh_register(cfg->pub, BRCMF_E_LINK, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_DEAUTH_IND, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_DEAUTH, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_DISASSOC_IND, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_ASSOC_IND, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_REASSOC_IND, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_ROAM, brcmf_notify_roaming_status); brcmf_fweh_register(cfg->pub, BRCMF_E_MIC_ERROR, brcmf_notify_mic_status); brcmf_fweh_register(cfg->pub, BRCMF_E_SET_SSID, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_PFN_NET_FOUND, brcmf_notify_sched_scan_results); brcmf_fweh_register(cfg->pub, BRCMF_E_IF, brcmf_notify_vif_event); brcmf_fweh_register(cfg->pub, BRCMF_E_P2P_PROBEREQ_MSG, brcmf_p2p_notify_rx_mgmt_p2p_probereq); brcmf_fweh_register(cfg->pub, BRCMF_E_P2P_DISC_LISTEN_COMPLETE, brcmf_p2p_notify_listen_complete); brcmf_fweh_register(cfg->pub, BRCMF_E_ACTION_FRAME_RX, brcmf_p2p_notify_action_frame_rx); brcmf_fweh_register(cfg->pub, BRCMF_E_ACTION_FRAME_COMPLETE, brcmf_p2p_notify_action_tx_complete); brcmf_fweh_register(cfg->pub, BRCMF_E_ACTION_FRAME_OFF_CHAN_COMPLETE, brcmf_p2p_notify_action_tx_complete); brcmf_fweh_register(cfg->pub, BRCMF_E_PSK_SUP, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_RSSI, brcmf_notify_rssi); brcmf_fwvid_register_event_handlers(cfg->pub); } static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_info *cfg) { kfree(cfg->conf); cfg->conf = NULL; kfree(cfg->extra_buf); cfg->extra_buf = NULL; kfree(cfg->wowl.nd); cfg->wowl.nd = NULL; kfree(cfg->wowl.nd_info); cfg->wowl.nd_info = NULL; kfree(cfg->escan_info.escan_buf); cfg->escan_info.escan_buf = NULL; } static s32 brcmf_init_priv_mem(struct brcmf_cfg80211_info *cfg) { cfg->conf = kzalloc(sizeof(*cfg->conf), GFP_KERNEL); if (!cfg->conf) goto init_priv_mem_out; cfg->extra_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL); if (!cfg->extra_buf) goto init_priv_mem_out; cfg->wowl.nd = kzalloc(sizeof(*cfg->wowl.nd) + sizeof(u32), GFP_KERNEL); if (!cfg->wowl.nd) goto init_priv_mem_out; cfg->wowl.nd_info = kzalloc(sizeof(*cfg->wowl.nd_info) + sizeof(struct cfg80211_wowlan_nd_match *), GFP_KERNEL); if (!cfg->wowl.nd_info) goto init_priv_mem_out; cfg->escan_info.escan_buf = kzalloc(BRCMF_ESCAN_BUF_SIZE, GFP_KERNEL); if (!cfg->escan_info.escan_buf) goto init_priv_mem_out; return 0; init_priv_mem_out: brcmf_deinit_priv_mem(cfg); return -ENOMEM; } static s32 wl_init_priv(struct brcmf_cfg80211_info *cfg) { s32 err = 0; cfg->scan_request = NULL; cfg->pwr_save = true; cfg->dongle_up = false; /* dongle is not up yet */ err = brcmf_init_priv_mem(cfg); if (err) return err; brcmf_register_event_handlers(cfg); mutex_init(&cfg->usr_sync); brcmf_init_escan(cfg); brcmf_init_conf(cfg->conf); brcmf_init_wmm_prio(cfg->ac_priority); init_completion(&cfg->vif_disabled); return err; } static void wl_deinit_priv(struct brcmf_cfg80211_info *cfg) { cfg->dongle_up = false; /* dongle down */ brcmf_abort_scanning(cfg); brcmf_deinit_priv_mem(cfg); brcmf_clear_assoc_ies(cfg); } static void init_vif_event(struct brcmf_cfg80211_vif_event *event) { init_waitqueue_head(&event->vif_wq); spin_lock_init(&event->vif_event_lock); } static s32 brcmf_dongle_roam(struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; s32 err; u32 bcn_timeout; __le32 roamtrigger[2]; __le32 roam_delta[2]; /* Configure beacon timeout value based upon roaming setting */ if (ifp->drvr->settings->roamoff) bcn_timeout = BRCMF_DEFAULT_BCN_TIMEOUT_ROAM_OFF; else bcn_timeout = BRCMF_DEFAULT_BCN_TIMEOUT_ROAM_ON; err = brcmf_fil_iovar_int_set(ifp, "bcn_timeout", bcn_timeout); if (err) { bphy_err(drvr, "bcn_timeout error (%d)\n", err); goto roam_setup_done; } /* Enable/Disable built-in roaming to allow supplicant to take care of * roaming. */ brcmf_dbg(INFO, "Internal Roaming = %s\n", ifp->drvr->settings->roamoff ? "Off" : "On"); err = brcmf_fil_iovar_int_set(ifp, "roam_off", ifp->drvr->settings->roamoff); if (err) { bphy_err(drvr, "roam_off error (%d)\n", err); goto roam_setup_done; } roamtrigger[0] = cpu_to_le32(WL_ROAM_TRIGGER_LEVEL); roamtrigger[1] = cpu_to_le32(BRCM_BAND_ALL); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_ROAM_TRIGGER, (void *)roamtrigger, sizeof(roamtrigger)); if (err) bphy_err(drvr, "WLC_SET_ROAM_TRIGGER error (%d)\n", err); roam_delta[0] = cpu_to_le32(WL_ROAM_DELTA); roam_delta[1] = cpu_to_le32(BRCM_BAND_ALL); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_ROAM_DELTA, (void *)roam_delta, sizeof(roam_delta)); if (err) bphy_err(drvr, "WLC_SET_ROAM_DELTA error (%d)\n", err); return 0; roam_setup_done: return err; } static s32 brcmf_dongle_scantime(struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; s32 err = 0; err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME, BRCMF_SCAN_CHANNEL_TIME); if (err) { bphy_err(drvr, "Scan assoc time error (%d)\n", err); goto dongle_scantime_out; } err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_UNASSOC_TIME, BRCMF_SCAN_UNASSOC_TIME); if (err) { bphy_err(drvr, "Scan unassoc time error (%d)\n", err); goto dongle_scantime_out; } err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_PASSIVE_TIME, BRCMF_SCAN_PASSIVE_TIME); if (err) { bphy_err(drvr, "Scan passive time error (%d)\n", err); goto dongle_scantime_out; } dongle_scantime_out: return err; } static void brcmf_update_bw40_channel_flag(struct ieee80211_channel *channel, struct brcmu_chan *ch) { u32 ht40_flag; ht40_flag = channel->flags & IEEE80211_CHAN_NO_HT40; if (ch->sb == BRCMU_CHAN_SB_U) { if (ht40_flag == IEEE80211_CHAN_NO_HT40) channel->flags &= ~IEEE80211_CHAN_NO_HT40; channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; } else { /* It should be one of * IEEE80211_CHAN_NO_HT40 or * IEEE80211_CHAN_NO_HT40PLUS */ channel->flags &= ~IEEE80211_CHAN_NO_HT40; if (ht40_flag == IEEE80211_CHAN_NO_HT40) channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; } } static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, u32 bw_cap[]) { struct wiphy *wiphy = cfg_to_wiphy(cfg); struct brcmf_pub *drvr = cfg->pub; struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); struct ieee80211_supported_band *band; struct ieee80211_channel *channel; struct brcmf_chanspec_list *list; struct brcmu_chan ch; int err; u8 *pbuf; u32 i, j; u32 total; u32 chaninfo; pbuf = kzalloc(BRCMF_DCMD_MEDLEN, GFP_KERNEL); if (pbuf == NULL) return -ENOMEM; list = (struct brcmf_chanspec_list *)pbuf; err = brcmf_fil_iovar_data_get(ifp, "chanspecs", pbuf, BRCMF_DCMD_MEDLEN); if (err) { bphy_err(drvr, "get chanspecs error (%d)\n", err); goto fail_pbuf; } band = wiphy->bands[NL80211_BAND_2GHZ]; if (band) for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; band = wiphy->bands[NL80211_BAND_5GHZ]; if (band) for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; total = le32_to_cpu(list->count); if (total > BRCMF_MAX_CHANSPEC_LIST) { bphy_err(drvr, "Invalid count of channel Spec. (%u)\n", total); err = -EINVAL; goto fail_pbuf; } for (i = 0; i < total; i++) { ch.chspec = (u16)le32_to_cpu(list->element[i]); cfg->d11inf.decchspec(&ch); if (ch.band == BRCMU_CHAN_BAND_2G) { band = wiphy->bands[NL80211_BAND_2GHZ]; } else if (ch.band == BRCMU_CHAN_BAND_5G) { band = wiphy->bands[NL80211_BAND_5GHZ]; } else { bphy_err(drvr, "Invalid channel Spec. 0x%x.\n", ch.chspec); continue; } if (!band) continue; if (!(bw_cap[band->band] & WLC_BW_40MHZ_BIT) && ch.bw == BRCMU_CHAN_BW_40) continue; if (!(bw_cap[band->band] & WLC_BW_80MHZ_BIT) && ch.bw == BRCMU_CHAN_BW_80) continue; channel = NULL; for (j = 0; j < band->n_channels; j++) { if (band->channels[j].hw_value == ch.control_ch_num) { channel = &band->channels[j]; break; } } if (!channel) { /* It seems firmware supports some channel we never * considered. Something new in IEEE standard? */ bphy_err(drvr, "Ignoring unexpected firmware channel %d\n", ch.control_ch_num); continue; } if (channel->orig_flags & IEEE80211_CHAN_DISABLED) continue; /* assuming the chanspecs order is HT20, * HT40 upper, HT40 lower, and VHT80. */ switch (ch.bw) { case BRCMU_CHAN_BW_160: channel->flags &= ~IEEE80211_CHAN_NO_160MHZ; break; case BRCMU_CHAN_BW_80: channel->flags &= ~IEEE80211_CHAN_NO_80MHZ; break; case BRCMU_CHAN_BW_40: brcmf_update_bw40_channel_flag(channel, &ch); break; default: wiphy_warn(wiphy, "Firmware reported unsupported bandwidth %d\n", ch.bw); fallthrough; case BRCMU_CHAN_BW_20: /* enable the channel and disable other bandwidths * for now as mentioned order assure they are enabled * for subsequent chanspecs. */ channel->flags = IEEE80211_CHAN_NO_HT40 | IEEE80211_CHAN_NO_80MHZ | IEEE80211_CHAN_NO_160MHZ; ch.bw = BRCMU_CHAN_BW_20; cfg->d11inf.encchspec(&ch); chaninfo = ch.chspec; err = brcmf_fil_bsscfg_int_query(ifp, "per_chan_info", &chaninfo); if (!err) { if (chaninfo & WL_CHAN_RADAR) channel->flags |= (IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IR); if (chaninfo & WL_CHAN_PASSIVE) channel->flags |= IEEE80211_CHAN_NO_IR; } } } fail_pbuf: kfree(pbuf); return err; } static int brcmf_enable_bw40_2g(struct brcmf_cfg80211_info *cfg) { struct brcmf_pub *drvr = cfg->pub; struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); struct ieee80211_supported_band *band; struct brcmf_fil_bwcap_le band_bwcap; struct brcmf_chanspec_list *list; u8 *pbuf; u32 val; int err; struct brcmu_chan ch; u32 num_chan; int i, j; /* verify support for bw_cap command */ val = WLC_BAND_5G; err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &val); if (!err) { /* only set 2G bandwidth using bw_cap command */ band_bwcap.band = cpu_to_le32(WLC_BAND_2G); band_bwcap.bw_cap = cpu_to_le32(WLC_BW_CAP_40MHZ); err = brcmf_fil_iovar_data_set(ifp, "bw_cap", &band_bwcap, sizeof(band_bwcap)); } else { brcmf_dbg(INFO, "fallback to mimo_bw_cap\n"); val = WLC_N_BW_40ALL; err = brcmf_fil_iovar_int_set(ifp, "mimo_bw_cap", val); } if (!err) { /* update channel info in 2G band */ pbuf = kzalloc(BRCMF_DCMD_MEDLEN, GFP_KERNEL); if (pbuf == NULL) return -ENOMEM; ch.band = BRCMU_CHAN_BAND_2G; ch.bw = BRCMU_CHAN_BW_40; ch.sb = BRCMU_CHAN_SB_NONE; ch.chnum = 0; cfg->d11inf.encchspec(&ch); /* pass encoded chanspec in query */ *(__le16 *)pbuf = cpu_to_le16(ch.chspec); err = brcmf_fil_iovar_data_get(ifp, "chanspecs", pbuf, BRCMF_DCMD_MEDLEN); if (err) { bphy_err(drvr, "get chanspecs error (%d)\n", err); kfree(pbuf); return err; } band = cfg_to_wiphy(cfg)->bands[NL80211_BAND_2GHZ]; list = (struct brcmf_chanspec_list *)pbuf; num_chan = le32_to_cpu(list->count); if (num_chan > BRCMF_MAX_CHANSPEC_LIST) { bphy_err(drvr, "Invalid count of channel Spec. (%u)\n", num_chan); kfree(pbuf); return -EINVAL; } for (i = 0; i < num_chan; i++) { ch.chspec = (u16)le32_to_cpu(list->element[i]); cfg->d11inf.decchspec(&ch); if (WARN_ON(ch.band != BRCMU_CHAN_BAND_2G)) continue; if (WARN_ON(ch.bw != BRCMU_CHAN_BW_40)) continue; for (j = 0; j < band->n_channels; j++) { if (band->channels[j].hw_value == ch.control_ch_num) break; } if (WARN_ON(j == band->n_channels)) continue; brcmf_update_bw40_channel_flag(&band->channels[j], &ch); } kfree(pbuf); } return err; } static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[]) { struct brcmf_pub *drvr = ifp->drvr; u32 band, mimo_bwcap; int err; band = WLC_BAND_2G; err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &band); if (!err) { bw_cap[NL80211_BAND_2GHZ] = band; band = WLC_BAND_5G; err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &band); if (!err) { bw_cap[NL80211_BAND_5GHZ] = band; return; } WARN_ON(1); return; } brcmf_dbg(INFO, "fallback to mimo_bw_cap info\n"); err = brcmf_fil_iovar_int_get(ifp, "mimo_bw_cap", &mimo_bwcap); if (err) /* assume 20MHz if firmware does not give a clue */ mimo_bwcap = WLC_N_BW_20ALL; switch (mimo_bwcap) { case WLC_N_BW_40ALL: bw_cap[NL80211_BAND_2GHZ] |= WLC_BW_40MHZ_BIT; fallthrough; case WLC_N_BW_20IN2G_40IN5G: bw_cap[NL80211_BAND_5GHZ] |= WLC_BW_40MHZ_BIT; fallthrough; case WLC_N_BW_20ALL: bw_cap[NL80211_BAND_2GHZ] |= WLC_BW_20MHZ_BIT; bw_cap[NL80211_BAND_5GHZ] |= WLC_BW_20MHZ_BIT; break; default: bphy_err(drvr, "invalid mimo_bw_cap value\n"); } } static void brcmf_update_ht_cap(struct ieee80211_supported_band *band, u32 bw_cap[2], u32 nchain) { band->ht_cap.ht_supported = true; if (bw_cap[band->band] & WLC_BW_40MHZ_BIT) { band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; } band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; band->ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40; band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16; memset(band->ht_cap.mcs.rx_mask, 0xff, nchain); band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; } static __le16 brcmf_get_mcs_map(u32 nchain, enum ieee80211_vht_mcs_support supp) { u16 mcs_map; int i; for (i = 0, mcs_map = 0xFFFF; i < nchain; i++) mcs_map = (mcs_map << 2) | supp; return cpu_to_le16(mcs_map); } static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, u32 bw_cap[2], u32 nchain, u32 txstreams, u32 txbf_bfe_cap, u32 txbf_bfr_cap) { __le16 mcs_map; /* not allowed in 2.4G band */ if (band->band == NL80211_BAND_2GHZ) return; band->vht_cap.vht_supported = true; /* 80MHz is mandatory */ band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; if (bw_cap[band->band] & WLC_BW_160MHZ_BIT) { band->vht_cap.cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_160; } /* all support 256-QAM */ mcs_map = brcmf_get_mcs_map(nchain, IEEE80211_VHT_MCS_SUPPORT_0_9); band->vht_cap.vht_mcs.rx_mcs_map = mcs_map; band->vht_cap.vht_mcs.tx_mcs_map = mcs_map; /* Beamforming support information */ if (txbf_bfe_cap & BRCMF_TXBF_SU_BFE_CAP) band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; if (txbf_bfe_cap & BRCMF_TXBF_MU_BFE_CAP) band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; if (txbf_bfr_cap & BRCMF_TXBF_SU_BFR_CAP) band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; if (txbf_bfr_cap & BRCMF_TXBF_MU_BFR_CAP) band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE; if ((txbf_bfe_cap || txbf_bfr_cap) && (txstreams > 1)) { band->vht_cap.cap |= (2 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT); band->vht_cap.cap |= ((txstreams - 1) << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT); band->vht_cap.cap |= IEEE80211_VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB; } } static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) { struct brcmf_pub *drvr = cfg->pub; struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); struct wiphy *wiphy = cfg_to_wiphy(cfg); u32 nmode; u32 vhtmode = 0; u32 bw_cap[2] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT }; u32 rxchain; u32 nchain; int err; s32 i; struct ieee80211_supported_band *band; u32 txstreams = 0; u32 txbf_bfe_cap = 0; u32 txbf_bfr_cap = 0; (void)brcmf_fil_iovar_int_get(ifp, "vhtmode", &vhtmode); err = brcmf_fil_iovar_int_get(ifp, "nmode", &nmode); if (err) { bphy_err(drvr, "nmode error (%d)\n", err); } else { brcmf_get_bwcap(ifp, bw_cap); } brcmf_dbg(INFO, "nmode=%d, vhtmode=%d, bw_cap=(%d, %d)\n", nmode, vhtmode, bw_cap[NL80211_BAND_2GHZ], bw_cap[NL80211_BAND_5GHZ]); err = brcmf_fil_iovar_int_get(ifp, "rxchain", &rxchain); if (err) { /* rxchain unsupported by firmware of older chips */ if (err == -EBADE) bphy_info_once(drvr, "rxchain unsupported\n"); else bphy_err(drvr, "rxchain error (%d)\n", err); nchain = 1; } else { for (nchain = 0; rxchain; nchain++) rxchain = rxchain & (rxchain - 1); } brcmf_dbg(INFO, "nchain=%d\n", nchain); err = brcmf_construct_chaninfo(cfg, bw_cap); if (err) { bphy_err(drvr, "brcmf_construct_chaninfo failed (%d)\n", err); return err; } if (vhtmode) { (void)brcmf_fil_iovar_int_get(ifp, "txstreams", &txstreams); (void)brcmf_fil_iovar_int_get(ifp, "txbf_bfe_cap", &txbf_bfe_cap); (void)brcmf_fil_iovar_int_get(ifp, "txbf_bfr_cap", &txbf_bfr_cap); } for (i = 0; i < ARRAY_SIZE(wiphy->bands); i++) { band = wiphy->bands[i]; if (band == NULL) continue; if (nmode) brcmf_update_ht_cap(band, bw_cap, nchain); if (vhtmode) brcmf_update_vht_cap(band, bw_cap, nchain, txstreams, txbf_bfe_cap, txbf_bfr_cap); } return 0; } static const struct ieee80211_txrx_stypes brcmf_txrx_stypes[NUM_NL80211_IFTYPES] = { [NL80211_IFTYPE_STATION] = { .tx = 0xffff, .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) }, [NL80211_IFTYPE_P2P_CLIENT] = { .tx = 0xffff, .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) }, [NL80211_IFTYPE_P2P_GO] = { .tx = 0xffff, .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | BIT(IEEE80211_STYPE_DISASSOC >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_DEAUTH >> 4) | BIT(IEEE80211_STYPE_ACTION >> 4) }, [NL80211_IFTYPE_P2P_DEVICE] = { .tx = 0xffff, .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) }, [NL80211_IFTYPE_AP] = { .tx = 0xffff, .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | BIT(IEEE80211_STYPE_DISASSOC >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_DEAUTH >> 4) | BIT(IEEE80211_STYPE_ACTION >> 4) } }; /** * brcmf_setup_ifmodes() - determine interface modes and combinations. * * @wiphy: wiphy object. * @ifp: interface object needed for feat module api. * * The interface modes and combinations are determined dynamically here * based on firmware functionality. * * no p2p and no mbss: * * #STA <= 1, #AP <= 1, channels = 1, 2 total * * no p2p and mbss: * * #STA <= 1, #AP <= 1, channels = 1, 2 total * #AP <= 4, matching BI, channels = 1, 4 total * * no p2p and rsdb: * #STA <= 1, #AP <= 2, channels = 2, 4 total * * p2p, no mchan, and mbss: * * #STA <= 1, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 1, channels = 1, 3 total * #STA <= 1, #P2P-DEV <= 1, #AP <= 1, #P2P-CL <= 1, channels = 1, 4 total * #AP <= 4, matching BI, channels = 1, 4 total * * p2p, mchan, and mbss: * * #STA <= 2, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 1, channels = 2, 3 total * #STA <= 1, #P2P-DEV <= 1, #AP <= 1, #P2P-CL <= 1, channels = 1, 4 total * #AP <= 4, matching BI, channels = 1, 4 total * * p2p, rsdb, and no mbss: * #STA <= 1, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 2, AP <= 2, * channels = 2, 4 total * * Return: 0 on success, negative errno on failure */ static int brcmf_setup_ifmodes(struct wiphy *wiphy, struct brcmf_if *ifp) { struct ieee80211_iface_combination *combo = NULL; struct ieee80211_iface_limit *c0_limits = NULL; struct ieee80211_iface_limit *p2p_limits = NULL; struct ieee80211_iface_limit *mbss_limits = NULL; bool mon_flag, mbss, p2p, rsdb, mchan; int i, c, n_combos, n_limits; mon_flag = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MONITOR_FLAG); mbss = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MBSS); p2p = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_P2P); rsdb = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_RSDB); mchan = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MCHAN); n_combos = 1 + !!(p2p && !rsdb) + !!mbss; combo = kcalloc(n_combos, sizeof(*combo), GFP_KERNEL); if (!combo) goto err; wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_AP); if (mon_flag) wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR); if (p2p) wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) | BIT(NL80211_IFTYPE_P2P_DEVICE); c = 0; i = 0; n_limits = 1 + mon_flag + (p2p ? 2 : 0) + (rsdb || !p2p); c0_limits = kcalloc(n_limits, sizeof(*c0_limits), GFP_KERNEL); if (!c0_limits) goto err; combo[c].num_different_channels = 1 + (rsdb || (p2p && mchan)); c0_limits[i].max = 1 + (p2p && mchan); c0_limits[i++].types = BIT(NL80211_IFTYPE_STATION); if (mon_flag) { c0_limits[i].max = 1; c0_limits[i++].types = BIT(NL80211_IFTYPE_MONITOR); } if (p2p) { c0_limits[i].max = 1; c0_limits[i++].types = BIT(NL80211_IFTYPE_P2P_DEVICE); c0_limits[i].max = 1 + rsdb; c0_limits[i++].types = BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO); } if (p2p && rsdb) { c0_limits[i].max = 2; c0_limits[i++].types = BIT(NL80211_IFTYPE_AP); combo[c].max_interfaces = 4; } else if (p2p) { combo[c].max_interfaces = i; } else if (rsdb) { c0_limits[i].max = 2; c0_limits[i++].types = BIT(NL80211_IFTYPE_AP); combo[c].max_interfaces = 3; } else { c0_limits[i].max = 1; c0_limits[i++].types = BIT(NL80211_IFTYPE_AP); combo[c].max_interfaces = i; } combo[c].n_limits = i; combo[c].limits = c0_limits; if (p2p && !rsdb) { c++; i = 0; p2p_limits = kcalloc(4, sizeof(*p2p_limits), GFP_KERNEL); if (!p2p_limits) goto err; p2p_limits[i].max = 1; p2p_limits[i++].types = BIT(NL80211_IFTYPE_STATION); p2p_limits[i].max = 1; p2p_limits[i++].types = BIT(NL80211_IFTYPE_AP); p2p_limits[i].max = 1; p2p_limits[i++].types = BIT(NL80211_IFTYPE_P2P_CLIENT); p2p_limits[i].max = 1; p2p_limits[i++].types = BIT(NL80211_IFTYPE_P2P_DEVICE); combo[c].num_different_channels = 1; combo[c].max_interfaces = i; combo[c].n_limits = i; combo[c].limits = p2p_limits; } if (mbss) { c++; i = 0; n_limits = 1 + mon_flag; mbss_limits = kcalloc(n_limits, sizeof(*mbss_limits), GFP_KERNEL); if (!mbss_limits) goto err; mbss_limits[i].max = 4; mbss_limits[i++].types = BIT(NL80211_IFTYPE_AP); if (mon_flag) { mbss_limits[i].max = 1; mbss_limits[i++].types = BIT(NL80211_IFTYPE_MONITOR); } combo[c].beacon_int_infra_match = true; combo[c].num_different_channels = 1; combo[c].max_interfaces = 4 + mon_flag; combo[c].n_limits = i; combo[c].limits = mbss_limits; } wiphy->n_iface_combinations = n_combos; wiphy->iface_combinations = combo; return 0; err: kfree(c0_limits); kfree(p2p_limits); kfree(mbss_limits); kfree(combo); return -ENOMEM; } #ifdef CONFIG_PM static const struct wiphy_wowlan_support brcmf_wowlan_support = { .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT, .n_patterns = BRCMF_WOWL_MAXPATTERNS, .pattern_max_len = BRCMF_WOWL_MAXPATTERNSIZE, .pattern_min_len = 1, .max_pkt_offset = 1500, }; #endif static void brcmf_wiphy_wowl_params(struct wiphy *wiphy, struct brcmf_if *ifp) { #ifdef CONFIG_PM struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct wiphy_wowlan_support *wowl; wowl = kmemdup(&brcmf_wowlan_support, sizeof(brcmf_wowlan_support), GFP_KERNEL); if (!wowl) { bphy_err(drvr, "only support basic wowlan features\n"); wiphy->wowlan = &brcmf_wowlan_support; return; } if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) { if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_ND)) { wowl->flags |= WIPHY_WOWLAN_NET_DETECT; wowl->max_nd_match_sets = BRCMF_PNO_MAX_PFN_COUNT; init_waitqueue_head(&cfg->wowl.nd_data_wait); } } if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_GTK)) { wowl->flags |= WIPHY_WOWLAN_SUPPORTS_GTK_REKEY; wowl->flags |= WIPHY_WOWLAN_GTK_REKEY_FAILURE; } wiphy->wowlan = wowl; #endif } static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; const struct ieee80211_iface_combination *combo; struct ieee80211_supported_band *band; u16 max_interfaces = 0; bool gscan; __le32 bandlist[3]; u32 n_bands; int err, i; wiphy->max_scan_ssids = WL_NUM_SCAN_MAX; wiphy->max_scan_ie_len = BRCMF_SCAN_IE_LEN_MAX; wiphy->max_num_pmkids = BRCMF_MAXPMKID; err = brcmf_setup_ifmodes(wiphy, ifp); if (err) return err; for (i = 0, combo = wiphy->iface_combinations; i < wiphy->n_iface_combinations; i++, combo++) { max_interfaces = max(max_interfaces, combo->max_interfaces); } for (i = 0; i < max_interfaces && i < ARRAY_SIZE(drvr->addresses); i++) { u8 *addr = drvr->addresses[i].addr; memcpy(addr, drvr->mac, ETH_ALEN); if (i) { addr[0] |= BIT(1); addr[ETH_ALEN - 1] ^= i; } } wiphy->addresses = drvr->addresses; wiphy->n_addresses = i; wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; wiphy->cipher_suites = brcmf_cipher_suites; wiphy->n_cipher_suites = ARRAY_SIZE(brcmf_cipher_suites); if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP)) wiphy->n_cipher_suites--; wiphy->bss_select_support = BIT(NL80211_BSS_SELECT_ATTR_RSSI) | BIT(NL80211_BSS_SELECT_ATTR_BAND_PREF) | BIT(NL80211_BSS_SELECT_ATTR_RSSI_ADJUST); + wiphy->bss_param_support = WIPHY_BSS_PARAM_AP_ISOLATE; + wiphy->flags |= WIPHY_FLAG_NETNS_OK | WIPHY_FLAG_PS_ON_BY_DEFAULT | WIPHY_FLAG_HAVE_AP_SME | WIPHY_FLAG_OFFCHAN_TX | WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_TDLS)) wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; if (!ifp->drvr->settings->roamoff) wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) { wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X); if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE)) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SAE_OFFLOAD); } if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWAUTH)) { wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_AP_PSK); if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE)) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SAE_OFFLOAD_AP); } if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) wiphy->features |= NL80211_FEATURE_SAE; wiphy->mgmt_stypes = brcmf_txrx_stypes; wiphy->max_remain_on_channel_duration = 5000; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) { gscan = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GSCAN); brcmf_pno_wiphy_params(wiphy, gscan); } /* vendor commands/events support */ wiphy->vendor_commands = brcmf_vendor_cmds; wiphy->n_vendor_commands = BRCMF_VNDR_CMDS_LAST - 1; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL)) brcmf_wiphy_wowl_params(wiphy, ifp); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BANDLIST, &bandlist, sizeof(bandlist)); if (err) { bphy_err(drvr, "could not obtain band info: err=%d\n", err); return err; } /* first entry in bandlist is number of bands */ n_bands = le32_to_cpu(bandlist[0]); for (i = 1; i <= n_bands && i < ARRAY_SIZE(bandlist); i++) { if (bandlist[i] == cpu_to_le32(WLC_BAND_2G)) { band = kmemdup(&__wl_band_2ghz, sizeof(__wl_band_2ghz), GFP_KERNEL); if (!band) return -ENOMEM; band->channels = kmemdup(&__wl_2ghz_channels, sizeof(__wl_2ghz_channels), GFP_KERNEL); if (!band->channels) { kfree(band); return -ENOMEM; } band->n_channels = ARRAY_SIZE(__wl_2ghz_channels); wiphy->bands[NL80211_BAND_2GHZ] = band; } if (bandlist[i] == cpu_to_le32(WLC_BAND_5G)) { band = kmemdup(&__wl_band_5ghz, sizeof(__wl_band_5ghz), GFP_KERNEL); if (!band) return -ENOMEM; band->channels = kmemdup(&__wl_5ghz_channels, sizeof(__wl_5ghz_channels), GFP_KERNEL); if (!band->channels) { kfree(band); return -ENOMEM; } band->n_channels = ARRAY_SIZE(__wl_5ghz_channels); wiphy->bands[NL80211_BAND_5GHZ] = band; } } if (wiphy->bands[NL80211_BAND_5GHZ] && brcmf_feat_is_enabled(ifp, BRCMF_FEAT_DOT11H)) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_DFS_OFFLOAD); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); wiphy_read_of_freq_limits(wiphy); return 0; } static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg) { struct brcmf_pub *drvr = cfg->pub; struct net_device *ndev; struct wireless_dev *wdev; struct brcmf_if *ifp; s32 power_mode; s32 err = 0; if (cfg->dongle_up) return err; ndev = cfg_to_ndev(cfg); wdev = ndev->ieee80211_ptr; ifp = netdev_priv(ndev); /* make sure RF is ready for work */ brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 0); brcmf_dongle_scantime(ifp); power_mode = cfg->pwr_save ? PM_FAST : PM_OFF; err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, power_mode); if (err) goto default_conf_out; brcmf_dbg(INFO, "power save set to %s\n", (power_mode ? "enabled" : "disabled")); err = brcmf_dongle_roam(ifp); if (err) goto default_conf_out; err = brcmf_cfg80211_change_iface(wdev->wiphy, ndev, wdev->iftype, NULL); if (err) goto default_conf_out; brcmf_configure_arp_nd_offload(ifp, true); err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_FAKEFRAG, 1); if (err) { bphy_err(drvr, "failed to set frameburst mode\n"); goto default_conf_out; } cfg->dongle_up = true; default_conf_out: return err; } static s32 __brcmf_cfg80211_up(struct brcmf_if *ifp) { set_bit(BRCMF_VIF_STATUS_READY, &ifp->vif->sme_state); return brcmf_config_dongle(ifp->drvr->config); } static s32 __brcmf_cfg80211_down(struct brcmf_if *ifp) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; /* * While going down, if associated with AP disassociate * from AP to save power */ if (check_vif_up(ifp->vif)) { brcmf_link_down(ifp->vif, WLAN_REASON_UNSPECIFIED, true); /* Make sure WPA_Supplicant receives all the event generated due to DISASSOC call to the fw to keep the state fw and WPA_Supplicant state consistent */ brcmf_delay(500); } brcmf_abort_scanning(cfg); clear_bit(BRCMF_VIF_STATUS_READY, &ifp->vif->sme_state); return 0; } s32 brcmf_cfg80211_up(struct net_device *ndev) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_info *cfg = ifp->drvr->config; s32 err = 0; mutex_lock(&cfg->usr_sync); err = __brcmf_cfg80211_up(ifp); mutex_unlock(&cfg->usr_sync); return err; } s32 brcmf_cfg80211_down(struct net_device *ndev) { struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_info *cfg = ifp->drvr->config; s32 err = 0; mutex_lock(&cfg->usr_sync); err = __brcmf_cfg80211_down(ifp); mutex_unlock(&cfg->usr_sync); return err; } bool brcmf_get_vif_state_any(struct brcmf_cfg80211_info *cfg, unsigned long state) { struct brcmf_cfg80211_vif *vif; list_for_each_entry(vif, &cfg->vif_list, list) { if (test_bit(state, &vif->sme_state)) return true; } return false; } static inline bool vif_event_equals(struct brcmf_cfg80211_vif_event *event, u8 action) { u8 evt_action; spin_lock(&event->vif_event_lock); evt_action = event->action; spin_unlock(&event->vif_event_lock); return evt_action == action; } void brcmf_cfg80211_arm_vif_event(struct brcmf_cfg80211_info *cfg, struct brcmf_cfg80211_vif *vif) { struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; spin_lock(&event->vif_event_lock); event->vif = vif; event->action = 0; spin_unlock(&event->vif_event_lock); } bool brcmf_cfg80211_vif_event_armed(struct brcmf_cfg80211_info *cfg) { struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; bool armed; spin_lock(&event->vif_event_lock); armed = event->vif != NULL; spin_unlock(&event->vif_event_lock); return armed; } int brcmf_cfg80211_wait_vif_event(struct brcmf_cfg80211_info *cfg, u8 action, ulong timeout) { struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; return wait_event_timeout(event->vif_wq, vif_event_equals(event, action), timeout); } static bool brmcf_use_iso3166_ccode_fallback(struct brcmf_pub *drvr) { if (drvr->settings->trivial_ccode_map) return true; switch (drvr->bus_if->chip) { case BRCM_CC_43430_CHIP_ID: case BRCM_CC_4345_CHIP_ID: case BRCM_CC_4356_CHIP_ID: case BRCM_CC_43602_CHIP_ID: return true; default: return false; } } static s32 brcmf_translate_country_code(struct brcmf_pub *drvr, char alpha2[2], struct brcmf_fil_country_le *ccreq) { struct brcmfmac_pd_cc *country_codes; struct brcmfmac_pd_cc_entry *cc; s32 found_index; int i; if ((alpha2[0] == ccreq->country_abbrev[0]) && (alpha2[1] == ccreq->country_abbrev[1])) { brcmf_dbg(TRACE, "Country code already set\n"); return -EAGAIN; } country_codes = drvr->settings->country_codes; if (!country_codes) { if (brmcf_use_iso3166_ccode_fallback(drvr)) { brcmf_dbg(TRACE, "No country codes configured for device, using ISO3166 code and 0 rev\n"); memset(ccreq, 0, sizeof(*ccreq)); ccreq->country_abbrev[0] = alpha2[0]; ccreq->country_abbrev[1] = alpha2[1]; ccreq->ccode[0] = alpha2[0]; ccreq->ccode[1] = alpha2[1]; return 0; } brcmf_dbg(TRACE, "No country codes configured for device\n"); return -EINVAL; } found_index = -1; for (i = 0; i < country_codes->table_size; i++) { cc = &country_codes->table[i]; if ((cc->iso3166[0] == '\0') && (found_index == -1)) found_index = i; if ((cc->iso3166[0] == alpha2[0]) && (cc->iso3166[1] == alpha2[1])) { found_index = i; break; } } if (found_index == -1) { brcmf_dbg(TRACE, "No country code match found\n"); return -EINVAL; } memset(ccreq, 0, sizeof(*ccreq)); ccreq->rev = cpu_to_le32(country_codes->table[found_index].rev); memcpy(ccreq->ccode, country_codes->table[found_index].cc, BRCMF_COUNTRY_BUF_SZ); ccreq->country_abbrev[0] = alpha2[0]; ccreq->country_abbrev[1] = alpha2[1]; ccreq->country_abbrev[2] = 0; return 0; } static int brcmf_parse_dump_obss(char *buf, struct brcmf_dump_survey *survey) { int i; char *token; char delim[] = "\n "; unsigned long val; int err = 0; token = strsep(&buf, delim); while (token) { if (!strcmp(token, "OBSS")) { for (i = 0; i < OBSS_TOKEN_IDX; i++) token = strsep(&buf, delim); err = kstrtoul(token, 10, &val); if (err) break; survey->obss = val; } if (!strcmp(token, "IBSS")) { for (i = 0; i < IBSS_TOKEN_IDX; i++) token = strsep(&buf, delim); err = kstrtoul(token, 10, &val); if (err) break; survey->ibss = val; } if (!strcmp(token, "TXDur")) { for (i = 0; i < TX_TOKEN_IDX; i++) token = strsep(&buf, delim); err = kstrtoul(token, 10, &val); if (err) break; survey->tx = val; } if (!strcmp(token, "Category")) { for (i = 0; i < CTG_TOKEN_IDX; i++) token = strsep(&buf, delim); err = kstrtoul(token, 10, &val); if (err) break; survey->no_ctg = val; } if (!strcmp(token, "Packet")) { for (i = 0; i < PKT_TOKEN_IDX; i++) token = strsep(&buf, delim); err = kstrtoul(token, 10, &val); if (err) break; survey->no_pckt = val; } if (!strcmp(token, "Opp(time):")) { for (i = 0; i < IDLE_TOKEN_IDX; i++) token = strsep(&buf, delim); err = kstrtoul(token, 10, &val); if (err) break; survey->idle = val; } token = strsep(&buf, delim); } return err; } static int brcmf_dump_obss(struct brcmf_if *ifp, struct cca_msrmnt_query req, struct brcmf_dump_survey *survey) { struct cca_stats_n_flags *results; char *buf; int err; buf = kzalloc(sizeof(char) * BRCMF_DCMD_MEDLEN, GFP_KERNEL); if (!buf) return -ENOMEM; memcpy(buf, &req, sizeof(struct cca_msrmnt_query)); err = brcmf_fil_iovar_data_get(ifp, "dump_obss", buf, BRCMF_DCMD_MEDLEN); if (err) { brcmf_err("dump_obss error (%d)\n", err); err = -EINVAL; goto exit; } results = (struct cca_stats_n_flags *)(buf); if (req.msrmnt_query) brcmf_parse_dump_obss(results->buf, survey); exit: kfree(buf); return err; } static s32 brcmf_set_channel(struct brcmf_cfg80211_info *cfg, struct ieee80211_channel *chan) { u16 chspec = 0; int err = 0; struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); if (chan->flags & IEEE80211_CHAN_DISABLED) return -EINVAL; /* set_channel */ chspec = channel_to_chanspec(&cfg->d11inf, chan); if (chspec != INVCHANSPEC) { err = brcmf_fil_iovar_int_set(ifp, "chanspec", chspec); if (err) { brcmf_err("set chanspec 0x%04x fail, reason %d\n", chspec, err); err = -EINVAL; } } else { brcmf_err("failed to convert host chanspec to fw chanspec\n"); err = -EINVAL; } return err; } static int brcmf_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev, int idx, struct survey_info *info) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); struct brcmf_dump_survey survey = {}; struct ieee80211_supported_band *band; enum nl80211_band band_id; struct cca_msrmnt_query req; u32 noise; int err; brcmf_dbg(TRACE, "Enter: channel idx=%d\n", idx); /* Do not run survey when VIF in CONNECTING / CONNECTED states */ if ((test_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state)) || (test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state))) { return -EBUSY; } for (band_id = 0; band_id < NUM_NL80211_BANDS; band_id++) { band = wiphy->bands[band_id]; if (!band) continue; if (idx >= band->n_channels) { idx -= band->n_channels; continue; } info->channel = &band->channels[idx]; break; } if (band_id == NUM_NL80211_BANDS) return -ENOENT; /* Setting current channel to the requested channel */ info->filled = 0; if (brcmf_set_channel(cfg, info->channel)) return 0; /* Disable mpc */ brcmf_set_mpc(ifp, 0); /* Set interface up, explicitly. */ err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); if (err) { brcmf_err("set interface up failed, err = %d\n", err); goto exit; } /* Get noise value */ err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_PHY_NOISE, &noise); if (err) { brcmf_err("Get Phy Noise failed, use dummy value\n"); noise = CHAN_NOISE_DUMMY; } /* Start Measurement for obss stats on current channel */ req.msrmnt_query = 0; req.time_req = ACS_MSRMNT_DELAY; err = brcmf_dump_obss(ifp, req, &survey); if (err) goto exit; /* Add 10 ms for IOVAR completion */ #if defined(__linux__) msleep(ACS_MSRMNT_DELAY + 10); #elif defined(__FreeBSD__) linux_msleep(ACS_MSRMNT_DELAY + 10); #endif /* Issue IOVAR to collect measurement results */ req.msrmnt_query = 1; err = brcmf_dump_obss(ifp, req, &survey); if (err) goto exit; info->noise = noise; info->time = ACS_MSRMNT_DELAY; info->time_busy = ACS_MSRMNT_DELAY - survey.idle; info->time_rx = survey.obss + survey.ibss + survey.no_ctg + survey.no_pckt; info->time_tx = survey.tx; info->filled = SURVEY_INFO_NOISE_DBM | SURVEY_INFO_TIME | SURVEY_INFO_TIME_BUSY | SURVEY_INFO_TIME_RX | SURVEY_INFO_TIME_TX; brcmf_dbg(INFO, "OBSS dump: channel %d: survey duration %d\n", ieee80211_frequency_to_channel(info->channel->center_freq), ACS_MSRMNT_DELAY); #if defined(__linux__) brcmf_dbg(INFO, "noise(%d) busy(%llu) rx(%llu) tx(%llu)\n", info->noise, info->time_busy, info->time_rx, info->time_tx); #elif defined(__FreeBSD__) brcmf_dbg(INFO, "noise(%d) busy(%ju) rx(%ju) tx(%ju)\n", info->noise, (uintmax_t)info->time_busy, (uintmax_t)info->time_rx, (uintmax_t)info->time_tx); #endif exit: if (!brcmf_is_apmode(ifp->vif)) brcmf_set_mpc(ifp, 1); return err; } static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy, struct regulatory_request *req) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = brcmf_get_ifp(cfg->pub, 0); struct brcmf_pub *drvr = cfg->pub; struct brcmf_fil_country_le ccreq; s32 err; int i; /* The country code gets set to "00" by default at boot, ignore */ if (req->alpha2[0] == '0' && req->alpha2[1] == '0') return; /* ignore non-ISO3166 country codes */ for (i = 0; i < 2; i++) if (req->alpha2[i] < 'A' || req->alpha2[i] > 'Z') { bphy_err(drvr, "not an ISO3166 code (0x%02x 0x%02x)\n", req->alpha2[0], req->alpha2[1]); return; } brcmf_dbg(TRACE, "Enter: initiator=%d, alpha=%c%c\n", req->initiator, req->alpha2[0], req->alpha2[1]); err = brcmf_fil_iovar_data_get(ifp, "country", &ccreq, sizeof(ccreq)); if (err) { bphy_err(drvr, "Country code iovar returned err = %d\n", err); return; } err = brcmf_translate_country_code(ifp->drvr, req->alpha2, &ccreq); if (err) return; err = brcmf_fil_iovar_data_set(ifp, "country", &ccreq, sizeof(ccreq)); if (err) { bphy_err(drvr, "Firmware rejected country setting\n"); return; } brcmf_setup_wiphybands(cfg); } static void brcmf_free_wiphy(struct wiphy *wiphy) { int i; if (!wiphy) return; if (wiphy->iface_combinations) { for (i = 0; i < wiphy->n_iface_combinations; i++) kfree(wiphy->iface_combinations[i].limits); } kfree(wiphy->iface_combinations); if (wiphy->bands[NL80211_BAND_2GHZ]) { kfree(wiphy->bands[NL80211_BAND_2GHZ]->channels); kfree(wiphy->bands[NL80211_BAND_2GHZ]); } if (wiphy->bands[NL80211_BAND_5GHZ]) { kfree(wiphy->bands[NL80211_BAND_5GHZ]->channels); kfree(wiphy->bands[NL80211_BAND_5GHZ]); } #if IS_ENABLED(CONFIG_PM) if (wiphy->wowlan != &brcmf_wowlan_support) kfree(wiphy->wowlan); #endif } struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr, struct cfg80211_ops *ops, bool p2pdev_forced) { struct wiphy *wiphy = drvr->wiphy; struct net_device *ndev = brcmf_get_ifp(drvr, 0)->ndev; struct brcmf_cfg80211_info *cfg; struct brcmf_cfg80211_vif *vif; struct brcmf_if *ifp; s32 err = 0; s32 io_type; u16 *cap = NULL; if (!ndev) { bphy_err(drvr, "ndev is invalid\n"); return NULL; } cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); if (!cfg) { bphy_err(drvr, "Could not allocate wiphy device\n"); return NULL; } cfg->wiphy = wiphy; cfg->pub = drvr; init_vif_event(&cfg->vif_event); INIT_LIST_HEAD(&cfg->vif_list); vif = brcmf_alloc_vif(cfg, NL80211_IFTYPE_STATION); if (IS_ERR(vif)) goto wiphy_out; ifp = netdev_priv(ndev); vif->ifp = ifp; vif->wdev.netdev = ndev; ndev->ieee80211_ptr = &vif->wdev; SET_NETDEV_DEV(ndev, wiphy_dev(cfg->wiphy)); err = wl_init_priv(cfg); if (err) { bphy_err(drvr, "Failed to init iwm_priv (%d)\n", err); brcmf_free_vif(vif); goto wiphy_out; } ifp->vif = vif; /* determine d11 io type before wiphy setup */ err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_VERSION, &io_type); if (err) { bphy_err(drvr, "Failed to get D11 version (%d)\n", err); goto priv_out; } cfg->d11inf.io_type = (u8)io_type; brcmu_d11_attach(&cfg->d11inf); /* regulatory notifier below needs access to cfg so * assign it now. */ drvr->config = cfg; err = brcmf_setup_wiphy(wiphy, ifp); if (err < 0) goto priv_out; brcmf_dbg(INFO, "Registering custom regulatory\n"); wiphy->reg_notifier = brcmf_cfg80211_reg_notifier; wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; wiphy_apply_custom_regulatory(wiphy, &brcmf_regdom); /* firmware defaults to 40MHz disabled in 2G band. We signal * cfg80211 here that we do and have it decide we can enable * it. But first check if device does support 2G operation. */ if (wiphy->bands[NL80211_BAND_2GHZ]) { cap = &wiphy->bands[NL80211_BAND_2GHZ]->ht_cap.cap; *cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; } #ifdef CONFIG_PM if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_GTK)) ops->set_rekey_data = brcmf_cfg80211_set_rekey_data; #endif if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_DUMP_OBSS)) ops->dump_survey = brcmf_cfg80211_dump_survey; err = wiphy_register(wiphy); if (err < 0) { bphy_err(drvr, "Could not register wiphy device (%d)\n", err); goto priv_out; } err = brcmf_setup_wiphybands(cfg); if (err) { bphy_err(drvr, "Setting wiphy bands failed (%d)\n", err); goto wiphy_unreg_out; } /* If cfg80211 didn't disable 40MHz HT CAP in wiphy_register(), * setup 40MHz in 2GHz band and enable OBSS scanning. */ if (cap && (*cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) { err = brcmf_enable_bw40_2g(cfg); if (!err) err = brcmf_fil_iovar_int_set(ifp, "obss_coex", BRCMF_OBSS_COEX_AUTO); else *cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; } err = brcmf_fweh_activate_events(ifp); if (err) { bphy_err(drvr, "FWEH activation failed (%d)\n", err); goto wiphy_unreg_out; } err = brcmf_p2p_attach(cfg, p2pdev_forced); if (err) { bphy_err(drvr, "P2P initialisation failed (%d)\n", err); goto wiphy_unreg_out; } err = brcmf_btcoex_attach(cfg); if (err) { bphy_err(drvr, "BT-coex initialisation failed (%d)\n", err); brcmf_p2p_detach(&cfg->p2p); goto wiphy_unreg_out; } err = brcmf_pno_attach(cfg); if (err) { bphy_err(drvr, "PNO initialisation failed (%d)\n", err); brcmf_btcoex_detach(cfg); brcmf_p2p_detach(&cfg->p2p); goto wiphy_unreg_out; } if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_TDLS)) { err = brcmf_fil_iovar_int_set(ifp, "tdls_enable", 1); if (err) { brcmf_dbg(INFO, "TDLS not enabled (%d)\n", err); wiphy->flags &= ~WIPHY_FLAG_SUPPORTS_TDLS; } else { brcmf_fweh_register(cfg->pub, BRCMF_E_TDLS_PEER_EVENT, brcmf_notify_tdls_peer_event); } } /* (re-) activate FWEH event handling */ err = brcmf_fweh_activate_events(ifp); if (err) { bphy_err(drvr, "FWEH activation failed (%d)\n", err); goto detach; } /* Fill in some of the advertised nl80211 supported features */ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_RANDOM_MAC)) { wiphy->features |= NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR; #ifdef CONFIG_PM if (wiphy->wowlan && wiphy->wowlan->flags & WIPHY_WOWLAN_NET_DETECT) wiphy->features |= NL80211_FEATURE_ND_RANDOM_MAC_ADDR; #endif } return cfg; detach: brcmf_pno_detach(cfg); brcmf_btcoex_detach(cfg); brcmf_p2p_detach(&cfg->p2p); wiphy_unreg_out: wiphy_unregister(cfg->wiphy); priv_out: wl_deinit_priv(cfg); brcmf_free_vif(vif); ifp->vif = NULL; wiphy_out: brcmf_free_wiphy(wiphy); kfree(cfg); return NULL; } void brcmf_cfg80211_detach(struct brcmf_cfg80211_info *cfg) { if (!cfg) return; brcmf_pno_detach(cfg); brcmf_btcoex_detach(cfg); wiphy_unregister(cfg->wiphy); wl_deinit_priv(cfg); cancel_work_sync(&cfg->escan_timeout_work); brcmf_free_wiphy(cfg->wiphy); kfree(cfg); } diff --git a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/chip.c b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/chip.c index 9074ab49e806..4239f2b21e54 100644 --- a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/chip.c +++ b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/chip.c @@ -1,1472 +1,1472 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2014 Broadcom Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "chip.h" /* SOC Interconnect types (aka chip types) */ #define SOCI_SB 0 #define SOCI_AI 1 /* PL-368 DMP definitions */ #define DMP_DESC_TYPE_MSK 0x0000000F #define DMP_DESC_EMPTY 0x00000000 #define DMP_DESC_VALID 0x00000001 #define DMP_DESC_COMPONENT 0x00000001 #define DMP_DESC_MASTER_PORT 0x00000003 #define DMP_DESC_ADDRESS 0x00000005 #define DMP_DESC_ADDRSIZE_GT32 0x00000008 #define DMP_DESC_EOT 0x0000000F #define DMP_COMP_DESIGNER 0xFFF00000 #define DMP_COMP_DESIGNER_S 20 #define DMP_COMP_PARTNUM 0x000FFF00 #define DMP_COMP_PARTNUM_S 8 #define DMP_COMP_CLASS 0x000000F0 #define DMP_COMP_CLASS_S 4 #define DMP_COMP_REVISION 0xFF000000 #define DMP_COMP_REVISION_S 24 #define DMP_COMP_NUM_SWRAP 0x00F80000 #define DMP_COMP_NUM_SWRAP_S 19 #define DMP_COMP_NUM_MWRAP 0x0007C000 #define DMP_COMP_NUM_MWRAP_S 14 #define DMP_COMP_NUM_SPORT 0x00003E00 #define DMP_COMP_NUM_SPORT_S 9 #define DMP_COMP_NUM_MPORT 0x000001F0 #define DMP_COMP_NUM_MPORT_S 4 #define DMP_MASTER_PORT_UID 0x0000FF00 #define DMP_MASTER_PORT_UID_S 8 #define DMP_MASTER_PORT_NUM 0x000000F0 #define DMP_MASTER_PORT_NUM_S 4 #define DMP_SLAVE_ADDR_BASE 0xFFFFF000 #define DMP_SLAVE_ADDR_BASE_S 12 #define DMP_SLAVE_PORT_NUM 0x00000F00 #define DMP_SLAVE_PORT_NUM_S 8 #define DMP_SLAVE_TYPE 0x000000C0 #define DMP_SLAVE_TYPE_S 6 #define DMP_SLAVE_TYPE_SLAVE 0 #define DMP_SLAVE_TYPE_BRIDGE 1 #define DMP_SLAVE_TYPE_SWRAP 2 #define DMP_SLAVE_TYPE_MWRAP 3 #define DMP_SLAVE_SIZE_TYPE 0x00000030 #define DMP_SLAVE_SIZE_TYPE_S 4 #define DMP_SLAVE_SIZE_4K 0 #define DMP_SLAVE_SIZE_8K 1 #define DMP_SLAVE_SIZE_16K 2 #define DMP_SLAVE_SIZE_DESC 3 /* EROM CompIdentB */ #define CIB_REV_MASK 0xff000000 #define CIB_REV_SHIFT 24 /* ARM CR4 core specific control flag bits */ #define ARMCR4_BCMA_IOCTL_CPUHALT 0x0020 /* D11 core specific control flag bits */ #define D11_BCMA_IOCTL_PHYCLOCKEN 0x0004 #define D11_BCMA_IOCTL_PHYRESET 0x0008 /* chip core base & ramsize */ /* bcm4329 */ /* SDIO device core, ID 0x829 */ #define BCM4329_CORE_BUS_BASE 0x18011000 /* internal memory core, ID 0x80e */ #define BCM4329_CORE_SOCRAM_BASE 0x18003000 /* ARM Cortex M3 core, ID 0x82a */ #define BCM4329_CORE_ARM_BASE 0x18002000 /* Max possibly supported memory size (limited by IO mapped memory) */ #define BRCMF_CHIP_MAX_MEMSIZE (4 * 1024 * 1024) #define CORE_SB(base, field) \ (base + SBCONFIGOFF + offsetof(struct sbconfig, field)) #define SBCOREREV(sbidh) \ ((((sbidh) & SSB_IDHIGH_RCHI) >> SSB_IDHIGH_RCHI_SHIFT) | \ ((sbidh) & SSB_IDHIGH_RCLO)) struct sbconfig { u32 PAD[2]; u32 sbipsflag; /* initiator port ocp slave flag */ u32 PAD[3]; u32 sbtpsflag; /* target port ocp slave flag */ u32 PAD[11]; u32 sbtmerrloga; /* (sonics >= 2.3) */ u32 PAD; u32 sbtmerrlog; /* (sonics >= 2.3) */ u32 PAD[3]; u32 sbadmatch3; /* address match3 */ u32 PAD; u32 sbadmatch2; /* address match2 */ u32 PAD; u32 sbadmatch1; /* address match1 */ u32 PAD[7]; u32 sbimstate; /* initiator agent state */ u32 sbintvec; /* interrupt mask */ u32 sbtmstatelow; /* target state */ u32 sbtmstatehigh; /* target state */ u32 sbbwa0; /* bandwidth allocation table0 */ u32 PAD; u32 sbimconfiglow; /* initiator configuration */ u32 sbimconfighigh; /* initiator configuration */ u32 sbadmatch0; /* address match0 */ u32 PAD; u32 sbtmconfiglow; /* target configuration */ u32 sbtmconfighigh; /* target configuration */ u32 sbbconfig; /* broadcast configuration */ u32 PAD; u32 sbbstate; /* broadcast state */ u32 PAD[3]; u32 sbactcnfg; /* activate configuration */ u32 PAD[3]; u32 sbflagst; /* current sbflags */ u32 PAD[3]; u32 sbidlow; /* identification */ u32 sbidhigh; /* identification */ }; #define INVALID_RAMBASE ((u32)(~0)) /* bankidx and bankinfo reg defines corerev >= 8 */ #define SOCRAM_BANKINFO_RETNTRAM_MASK 0x00010000 #define SOCRAM_BANKINFO_SZMASK 0x0000007f #define SOCRAM_BANKIDX_ROM_MASK 0x00000100 #define SOCRAM_BANKIDX_MEMTYPE_SHIFT 8 /* socram bankinfo memtype */ #define SOCRAM_MEMTYPE_RAM 0 #define SOCRAM_MEMTYPE_R0M 1 #define SOCRAM_MEMTYPE_DEVRAM 2 #define SOCRAM_BANKINFO_SZBASE 8192 #define SRCI_LSS_MASK 0x00f00000 #define SRCI_LSS_SHIFT 20 #define SRCI_SRNB_MASK 0xf0 #define SRCI_SRNB_MASK_EXT 0x100 #define SRCI_SRNB_SHIFT 4 #define SRCI_SRBSZ_MASK 0xf #define SRCI_SRBSZ_SHIFT 0 #define SR_BSZ_BASE 14 struct sbsocramregs { u32 coreinfo; u32 bwalloc; u32 extracoreinfo; u32 biststat; u32 bankidx; u32 standbyctrl; u32 errlogstatus; /* rev 6 */ u32 errlogaddr; /* rev 6 */ /* used for patching rev 3 & 5 */ u32 cambankidx; u32 cambankstandbyctrl; u32 cambankpatchctrl; u32 cambankpatchtblbaseaddr; u32 cambankcmdreg; u32 cambankdatareg; u32 cambankmaskreg; u32 PAD[1]; u32 bankinfo; /* corev 8 */ u32 bankpda; u32 PAD[14]; u32 extmemconfig; u32 extmemparitycsr; u32 extmemparityerrdata; u32 extmemparityerrcnt; u32 extmemwrctrlandsize; u32 PAD[84]; u32 workaround; u32 pwrctl; /* corerev >= 2 */ u32 PAD[133]; u32 sr_control; /* corerev >= 15 */ u32 sr_status; /* corerev >= 15 */ u32 sr_address; /* corerev >= 15 */ u32 sr_data; /* corerev >= 15 */ }; #define SOCRAMREGOFFS(_f) offsetof(struct sbsocramregs, _f) #define SYSMEMREGOFFS(_f) offsetof(struct sbsocramregs, _f) #define ARMCR4_CAP (0x04) #define ARMCR4_BANKIDX (0x40) #define ARMCR4_BANKINFO (0x44) #define ARMCR4_BANKPDA (0x4C) #define ARMCR4_TCBBNB_MASK 0xf0 #define ARMCR4_TCBBNB_SHIFT 4 #define ARMCR4_TCBANB_MASK 0xf #define ARMCR4_TCBANB_SHIFT 0 #define ARMCR4_BSZ_MASK 0x7f #define ARMCR4_BSZ_MULT 8192 #define ARMCR4_BLK_1K_MASK 0x200 struct brcmf_core_priv { struct brcmf_core pub; u32 wrapbase; struct list_head list; struct brcmf_chip_priv *chip; }; struct brcmf_chip_priv { struct brcmf_chip pub; const struct brcmf_buscore_ops *ops; void *ctx; /* assured first core is chipcommon, second core is buscore */ struct list_head cores; u16 num_cores; bool (*iscoreup)(struct brcmf_core_priv *core); void (*coredisable)(struct brcmf_core_priv *core, u32 prereset, u32 reset); void (*resetcore)(struct brcmf_core_priv *core, u32 prereset, u32 reset, u32 postreset); }; static void brcmf_chip_sb_corerev(struct brcmf_chip_priv *ci, struct brcmf_core *core) { u32 regdata; regdata = ci->ops->read32(ci->ctx, CORE_SB(core->base, sbidhigh)); core->rev = SBCOREREV(regdata); } static bool brcmf_chip_sb_iscoreup(struct brcmf_core_priv *core) { struct brcmf_chip_priv *ci; u32 regdata; u32 address; ci = core->chip; address = CORE_SB(core->pub.base, sbtmstatelow); regdata = ci->ops->read32(ci->ctx, address); regdata &= (SSB_TMSLOW_RESET | SSB_TMSLOW_REJECT | SSB_IMSTATE_REJECT | SSB_TMSLOW_CLOCK); return SSB_TMSLOW_CLOCK == regdata; } static bool brcmf_chip_ai_iscoreup(struct brcmf_core_priv *core) { struct brcmf_chip_priv *ci; u32 regdata; bool ret; ci = core->chip; regdata = ci->ops->read32(ci->ctx, core->wrapbase + BCMA_IOCTL); ret = (regdata & (BCMA_IOCTL_FGC | BCMA_IOCTL_CLK)) == BCMA_IOCTL_CLK; regdata = ci->ops->read32(ci->ctx, core->wrapbase + BCMA_RESET_CTL); ret = ret && ((regdata & BCMA_RESET_CTL_RESET) == 0); return ret; } static void brcmf_chip_sb_coredisable(struct brcmf_core_priv *core, u32 prereset, u32 reset) { struct brcmf_chip_priv *ci; u32 val, base; ci = core->chip; base = core->pub.base; val = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatelow)); if (val & SSB_TMSLOW_RESET) return; val = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatelow)); if ((val & SSB_TMSLOW_CLOCK) != 0) { /* * set target reject and spin until busy is clear * (preserve core-specific bits) */ val = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatelow)); ci->ops->write32(ci->ctx, CORE_SB(base, sbtmstatelow), val | SSB_TMSLOW_REJECT); val = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatelow)); udelay(1); SPINWAIT((ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatehigh)) & SSB_TMSHIGH_BUSY), 100000); val = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatehigh)); if (val & SSB_TMSHIGH_BUSY) brcmf_err("core state still busy\n"); val = ci->ops->read32(ci->ctx, CORE_SB(base, sbidlow)); if (val & SSB_IDLOW_INITIATOR) { val = ci->ops->read32(ci->ctx, CORE_SB(base, sbimstate)); val |= SSB_IMSTATE_REJECT; ci->ops->write32(ci->ctx, CORE_SB(base, sbimstate), val); val = ci->ops->read32(ci->ctx, CORE_SB(base, sbimstate)); udelay(1); SPINWAIT((ci->ops->read32(ci->ctx, CORE_SB(base, sbimstate)) & SSB_IMSTATE_BUSY), 100000); } /* set reset and reject while enabling the clocks */ val = SSB_TMSLOW_FGC | SSB_TMSLOW_CLOCK | SSB_TMSLOW_REJECT | SSB_TMSLOW_RESET; ci->ops->write32(ci->ctx, CORE_SB(base, sbtmstatelow), val); val = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatelow)); udelay(10); /* clear the initiator reject bit */ val = ci->ops->read32(ci->ctx, CORE_SB(base, sbidlow)); if (val & SSB_IDLOW_INITIATOR) { val = ci->ops->read32(ci->ctx, CORE_SB(base, sbimstate)); val &= ~SSB_IMSTATE_REJECT; ci->ops->write32(ci->ctx, CORE_SB(base, sbimstate), val); } } /* leave reset and reject asserted */ ci->ops->write32(ci->ctx, CORE_SB(base, sbtmstatelow), (SSB_TMSLOW_REJECT | SSB_TMSLOW_RESET)); udelay(1); } static void brcmf_chip_ai_coredisable(struct brcmf_core_priv *core, u32 prereset, u32 reset) { struct brcmf_chip_priv *ci; u32 regdata; ci = core->chip; /* if core is already in reset, skip reset */ regdata = ci->ops->read32(ci->ctx, core->wrapbase + BCMA_RESET_CTL); if ((regdata & BCMA_RESET_CTL_RESET) != 0) goto in_reset_configure; /* configure reset */ ci->ops->write32(ci->ctx, core->wrapbase + BCMA_IOCTL, prereset | BCMA_IOCTL_FGC | BCMA_IOCTL_CLK); ci->ops->read32(ci->ctx, core->wrapbase + BCMA_IOCTL); /* put in reset */ ci->ops->write32(ci->ctx, core->wrapbase + BCMA_RESET_CTL, BCMA_RESET_CTL_RESET); usleep_range(10, 20); /* wait till reset is 1 */ SPINWAIT(ci->ops->read32(ci->ctx, core->wrapbase + BCMA_RESET_CTL) != BCMA_RESET_CTL_RESET, 300); in_reset_configure: /* in-reset configure */ ci->ops->write32(ci->ctx, core->wrapbase + BCMA_IOCTL, reset | BCMA_IOCTL_FGC | BCMA_IOCTL_CLK); ci->ops->read32(ci->ctx, core->wrapbase + BCMA_IOCTL); } static void brcmf_chip_sb_resetcore(struct brcmf_core_priv *core, u32 prereset, u32 reset, u32 postreset) { struct brcmf_chip_priv *ci; u32 regdata; u32 base; ci = core->chip; base = core->pub.base; /* * Must do the disable sequence first to work for * arbitrary current core state. */ brcmf_chip_sb_coredisable(core, 0, 0); /* * Now do the initialization sequence. * set reset while enabling the clock and * forcing them on throughout the core */ ci->ops->write32(ci->ctx, CORE_SB(base, sbtmstatelow), SSB_TMSLOW_FGC | SSB_TMSLOW_CLOCK | SSB_TMSLOW_RESET); regdata = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatelow)); udelay(1); /* clear any serror */ regdata = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatehigh)); if (regdata & SSB_TMSHIGH_SERR) ci->ops->write32(ci->ctx, CORE_SB(base, sbtmstatehigh), 0); regdata = ci->ops->read32(ci->ctx, CORE_SB(base, sbimstate)); if (regdata & (SSB_IMSTATE_IBE | SSB_IMSTATE_TO)) { regdata &= ~(SSB_IMSTATE_IBE | SSB_IMSTATE_TO); ci->ops->write32(ci->ctx, CORE_SB(base, sbimstate), regdata); } /* clear reset and allow it to propagate throughout the core */ ci->ops->write32(ci->ctx, CORE_SB(base, sbtmstatelow), SSB_TMSLOW_FGC | SSB_TMSLOW_CLOCK); regdata = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatelow)); udelay(1); /* leave clock enabled */ ci->ops->write32(ci->ctx, CORE_SB(base, sbtmstatelow), SSB_TMSLOW_CLOCK); regdata = ci->ops->read32(ci->ctx, CORE_SB(base, sbtmstatelow)); udelay(1); } static void brcmf_chip_ai_resetcore(struct brcmf_core_priv *core, u32 prereset, u32 reset, u32 postreset) { struct brcmf_chip_priv *ci; int count; struct brcmf_core *d11core2 = NULL; struct brcmf_core_priv *d11priv2 = NULL; ci = core->chip; /* special handle two D11 cores reset */ if (core->pub.id == BCMA_CORE_80211) { d11core2 = brcmf_chip_get_d11core(&ci->pub, 1); if (d11core2) { brcmf_dbg(INFO, "found two d11 cores, reset both\n"); d11priv2 = container_of(d11core2, struct brcmf_core_priv, pub); } } /* must disable first to work for arbitrary current core state */ brcmf_chip_ai_coredisable(core, prereset, reset); if (d11priv2) brcmf_chip_ai_coredisable(d11priv2, prereset, reset); count = 0; while (ci->ops->read32(ci->ctx, core->wrapbase + BCMA_RESET_CTL) & BCMA_RESET_CTL_RESET) { ci->ops->write32(ci->ctx, core->wrapbase + BCMA_RESET_CTL, 0); count++; if (count > 50) break; usleep_range(40, 60); } if (d11priv2) { count = 0; while (ci->ops->read32(ci->ctx, d11priv2->wrapbase + BCMA_RESET_CTL) & BCMA_RESET_CTL_RESET) { ci->ops->write32(ci->ctx, d11priv2->wrapbase + BCMA_RESET_CTL, 0); count++; if (count > 50) break; usleep_range(40, 60); } } ci->ops->write32(ci->ctx, core->wrapbase + BCMA_IOCTL, postreset | BCMA_IOCTL_CLK); ci->ops->read32(ci->ctx, core->wrapbase + BCMA_IOCTL); if (d11priv2) { ci->ops->write32(ci->ctx, d11priv2->wrapbase + BCMA_IOCTL, postreset | BCMA_IOCTL_CLK); ci->ops->read32(ci->ctx, d11priv2->wrapbase + BCMA_IOCTL); } } char *brcmf_chip_name(u32 id, u32 rev, char *buf, uint len) { const char *fmt; fmt = ((id > 0xa000) || (id < 0x4000)) ? "BCM%d/%u" : "BCM%x/%u"; snprintf(buf, len, fmt, id, rev); return buf; } static struct brcmf_core *brcmf_chip_add_core(struct brcmf_chip_priv *ci, u16 coreid, u32 base, u32 wrapbase) { struct brcmf_core_priv *core; core = kzalloc(sizeof(*core), GFP_KERNEL); if (!core) return ERR_PTR(-ENOMEM); core->pub.id = coreid; core->pub.base = base; core->chip = ci; core->wrapbase = wrapbase; list_add_tail(&core->list, &ci->cores); return &core->pub; } /* safety check for chipinfo */ static int brcmf_chip_cores_check(struct brcmf_chip_priv *ci) { struct brcmf_core_priv *core; bool need_socram = false; bool has_socram = false; bool cpu_found = false; int idx = 1; list_for_each_entry(core, &ci->cores, list) { brcmf_dbg(INFO, " [%-2d] core 0x%x:%-3d base 0x%08x wrap 0x%08x\n", idx++, core->pub.id, core->pub.rev, core->pub.base, core->wrapbase); switch (core->pub.id) { case BCMA_CORE_ARM_CM3: cpu_found = true; need_socram = true; break; case BCMA_CORE_INTERNAL_MEM: has_socram = true; break; case BCMA_CORE_ARM_CR4: cpu_found = true; break; case BCMA_CORE_ARM_CA7: cpu_found = true; break; default: break; } } if (!cpu_found) { brcmf_err("CPU core not detected\n"); return -ENXIO; } /* check RAM core presence for ARM CM3 core */ if (need_socram && !has_socram) { brcmf_err("RAM core not provided with ARM CM3 core\n"); return -ENODEV; } return 0; } static u32 brcmf_chip_core_read32(struct brcmf_core_priv *core, u16 reg) { return core->chip->ops->read32(core->chip->ctx, core->pub.base + reg); } static void brcmf_chip_core_write32(struct brcmf_core_priv *core, u16 reg, u32 val) { core->chip->ops->write32(core->chip->ctx, core->pub.base + reg, val); } static bool brcmf_chip_socram_banksize(struct brcmf_core_priv *core, u8 idx, u32 *banksize) { u32 bankinfo; u32 bankidx = (SOCRAM_MEMTYPE_RAM << SOCRAM_BANKIDX_MEMTYPE_SHIFT); bankidx |= idx; brcmf_chip_core_write32(core, SOCRAMREGOFFS(bankidx), bankidx); bankinfo = brcmf_chip_core_read32(core, SOCRAMREGOFFS(bankinfo)); *banksize = (bankinfo & SOCRAM_BANKINFO_SZMASK) + 1; *banksize *= SOCRAM_BANKINFO_SZBASE; return !!(bankinfo & SOCRAM_BANKINFO_RETNTRAM_MASK); } static void brcmf_chip_socram_ramsize(struct brcmf_core_priv *sr, u32 *ramsize, u32 *srsize) { u32 coreinfo; uint nb, banksize, lss; bool retent; int i; *ramsize = 0; *srsize = 0; if (WARN_ON(sr->pub.rev < 4)) return; if (!brcmf_chip_iscoreup(&sr->pub)) brcmf_chip_resetcore(&sr->pub, 0, 0, 0); /* Get info for determining size */ coreinfo = brcmf_chip_core_read32(sr, SOCRAMREGOFFS(coreinfo)); nb = (coreinfo & SRCI_SRNB_MASK) >> SRCI_SRNB_SHIFT; if ((sr->pub.rev <= 7) || (sr->pub.rev == 12)) { banksize = (coreinfo & SRCI_SRBSZ_MASK); lss = (coreinfo & SRCI_LSS_MASK) >> SRCI_LSS_SHIFT; if (lss != 0) nb--; *ramsize = nb * (1 << (banksize + SR_BSZ_BASE)); if (lss != 0) *ramsize += (1 << ((lss - 1) + SR_BSZ_BASE)); } else { /* length of SRAM Banks increased for corerev greater than 23 */ if (sr->pub.rev >= 23) { nb = (coreinfo & (SRCI_SRNB_MASK | SRCI_SRNB_MASK_EXT)) >> SRCI_SRNB_SHIFT; } else { nb = (coreinfo & SRCI_SRNB_MASK) >> SRCI_SRNB_SHIFT; } for (i = 0; i < nb; i++) { retent = brcmf_chip_socram_banksize(sr, i, &banksize); *ramsize += banksize; if (retent) *srsize += banksize; } } /* hardcoded save&restore memory sizes */ switch (sr->chip->pub.chip) { case BRCM_CC_4334_CHIP_ID: if (sr->chip->pub.chiprev < 2) *srsize = (32 * 1024); break; case BRCM_CC_43430_CHIP_ID: case CY_CC_43439_CHIP_ID: /* assume sr for now as we can not check * firmware sr capability at this point. */ *srsize = (64 * 1024); break; default: break; } } /** Return the SYS MEM size */ static u32 brcmf_chip_sysmem_ramsize(struct brcmf_core_priv *sysmem) { u32 memsize = 0; u32 coreinfo; u32 idx; u32 nb; u32 banksize; if (!brcmf_chip_iscoreup(&sysmem->pub)) brcmf_chip_resetcore(&sysmem->pub, 0, 0, 0); coreinfo = brcmf_chip_core_read32(sysmem, SYSMEMREGOFFS(coreinfo)); nb = (coreinfo & SRCI_SRNB_MASK) >> SRCI_SRNB_SHIFT; for (idx = 0; idx < nb; idx++) { brcmf_chip_socram_banksize(sysmem, idx, &banksize); memsize += banksize; } return memsize; } /** Return the TCM-RAM size of the ARMCR4 core. */ static u32 brcmf_chip_tcm_ramsize(struct brcmf_core_priv *cr4) { u32 corecap; u32 memsize = 0; u32 nab; u32 nbb; u32 totb; u32 bxinfo; u32 blksize; u32 idx; corecap = brcmf_chip_core_read32(cr4, ARMCR4_CAP); nab = (corecap & ARMCR4_TCBANB_MASK) >> ARMCR4_TCBANB_SHIFT; nbb = (corecap & ARMCR4_TCBBNB_MASK) >> ARMCR4_TCBBNB_SHIFT; totb = nab + nbb; for (idx = 0; idx < totb; idx++) { brcmf_chip_core_write32(cr4, ARMCR4_BANKIDX, idx); bxinfo = brcmf_chip_core_read32(cr4, ARMCR4_BANKINFO); blksize = ARMCR4_BSZ_MULT; if (bxinfo & ARMCR4_BLK_1K_MASK) blksize >>= 3; memsize += ((bxinfo & ARMCR4_BSZ_MASK) + 1) * blksize; } return memsize; } static u32 brcmf_chip_tcm_rambase(struct brcmf_chip_priv *ci) { switch (ci->pub.chip) { case BRCM_CC_4345_CHIP_ID: case BRCM_CC_43454_CHIP_ID: return 0x198000; case BRCM_CC_4335_CHIP_ID: case BRCM_CC_4339_CHIP_ID: case BRCM_CC_4350_CHIP_ID: case BRCM_CC_4354_CHIP_ID: case BRCM_CC_4356_CHIP_ID: case BRCM_CC_43567_CHIP_ID: case BRCM_CC_43569_CHIP_ID: case BRCM_CC_43570_CHIP_ID: case BRCM_CC_4358_CHIP_ID: case BRCM_CC_43602_CHIP_ID: case BRCM_CC_4371_CHIP_ID: return 0x180000; case BRCM_CC_43465_CHIP_ID: case BRCM_CC_43525_CHIP_ID: case BRCM_CC_4365_CHIP_ID: case BRCM_CC_4366_CHIP_ID: case BRCM_CC_43664_CHIP_ID: case BRCM_CC_43666_CHIP_ID: return 0x200000; case BRCM_CC_4355_CHIP_ID: case BRCM_CC_4359_CHIP_ID: return (ci->pub.chiprev < 9) ? 0x180000 : 0x160000; case BRCM_CC_4364_CHIP_ID: case CY_CC_4373_CHIP_ID: return 0x160000; - case CY_CC_43752_CHIP_ID: case BRCM_CC_43751_CHIP_ID: + case BRCM_CC_43752_CHIP_ID: case BRCM_CC_4377_CHIP_ID: return 0x170000; case BRCM_CC_4378_CHIP_ID: return 0x352000; case BRCM_CC_4387_CHIP_ID: return 0x740000; default: brcmf_err("unknown chip: %s\n", ci->pub.name); break; } return INVALID_RAMBASE; } int brcmf_chip_get_raminfo(struct brcmf_chip *pub) { struct brcmf_chip_priv *ci = container_of(pub, struct brcmf_chip_priv, pub); struct brcmf_core_priv *mem_core; struct brcmf_core *mem; mem = brcmf_chip_get_core(&ci->pub, BCMA_CORE_ARM_CR4); if (mem) { mem_core = container_of(mem, struct brcmf_core_priv, pub); ci->pub.ramsize = brcmf_chip_tcm_ramsize(mem_core); ci->pub.rambase = brcmf_chip_tcm_rambase(ci); if (ci->pub.rambase == INVALID_RAMBASE) { brcmf_err("RAM base not provided with ARM CR4 core\n"); return -EINVAL; } } else { mem = brcmf_chip_get_core(&ci->pub, BCMA_CORE_SYS_MEM); if (mem) { mem_core = container_of(mem, struct brcmf_core_priv, pub); ci->pub.ramsize = brcmf_chip_sysmem_ramsize(mem_core); ci->pub.rambase = brcmf_chip_tcm_rambase(ci); if (ci->pub.rambase == INVALID_RAMBASE) { brcmf_err("RAM base not provided with ARM CA7 core\n"); return -EINVAL; } } else { mem = brcmf_chip_get_core(&ci->pub, BCMA_CORE_INTERNAL_MEM); if (!mem) { brcmf_err("No memory cores found\n"); return -ENOMEM; } mem_core = container_of(mem, struct brcmf_core_priv, pub); brcmf_chip_socram_ramsize(mem_core, &ci->pub.ramsize, &ci->pub.srsize); } } brcmf_dbg(INFO, "RAM: base=0x%x size=%d (0x%x) sr=%d (0x%x)\n", ci->pub.rambase, ci->pub.ramsize, ci->pub.ramsize, ci->pub.srsize, ci->pub.srsize); if (!ci->pub.ramsize) { brcmf_err("RAM size is undetermined\n"); return -ENOMEM; } if (ci->pub.ramsize > BRCMF_CHIP_MAX_MEMSIZE) { brcmf_err("RAM size is incorrect\n"); return -ENOMEM; } return 0; } static u32 brcmf_chip_dmp_get_desc(struct brcmf_chip_priv *ci, u32 *eromaddr, u8 *type) { u32 val; /* read next descriptor */ val = ci->ops->read32(ci->ctx, *eromaddr); *eromaddr += 4; if (!type) return val; /* determine descriptor type */ *type = (val & DMP_DESC_TYPE_MSK); if ((*type & ~DMP_DESC_ADDRSIZE_GT32) == DMP_DESC_ADDRESS) *type = DMP_DESC_ADDRESS; return val; } static int brcmf_chip_dmp_get_regaddr(struct brcmf_chip_priv *ci, u32 *eromaddr, u32 *regbase, u32 *wrapbase) { u8 desc; u32 val, szdesc; u8 stype, sztype, wraptype; *regbase = 0; *wrapbase = 0; val = brcmf_chip_dmp_get_desc(ci, eromaddr, &desc); if (desc == DMP_DESC_MASTER_PORT) { wraptype = DMP_SLAVE_TYPE_MWRAP; } else if (desc == DMP_DESC_ADDRESS) { /* revert erom address */ *eromaddr -= 4; wraptype = DMP_SLAVE_TYPE_SWRAP; } else { *eromaddr -= 4; return -EILSEQ; } do { /* locate address descriptor */ do { val = brcmf_chip_dmp_get_desc(ci, eromaddr, &desc); /* unexpected table end */ if (desc == DMP_DESC_EOT) { *eromaddr -= 4; return -EFAULT; } } while (desc != DMP_DESC_ADDRESS && desc != DMP_DESC_COMPONENT); /* stop if we crossed current component border */ if (desc == DMP_DESC_COMPONENT) { *eromaddr -= 4; return 0; } /* skip upper 32-bit address descriptor */ if (val & DMP_DESC_ADDRSIZE_GT32) brcmf_chip_dmp_get_desc(ci, eromaddr, NULL); sztype = (val & DMP_SLAVE_SIZE_TYPE) >> DMP_SLAVE_SIZE_TYPE_S; /* next size descriptor can be skipped */ if (sztype == DMP_SLAVE_SIZE_DESC) { szdesc = brcmf_chip_dmp_get_desc(ci, eromaddr, NULL); /* skip upper size descriptor if present */ if (szdesc & DMP_DESC_ADDRSIZE_GT32) brcmf_chip_dmp_get_desc(ci, eromaddr, NULL); } /* look for 4K or 8K register regions */ if (sztype != DMP_SLAVE_SIZE_4K && sztype != DMP_SLAVE_SIZE_8K) continue; stype = (val & DMP_SLAVE_TYPE) >> DMP_SLAVE_TYPE_S; /* only regular slave and wrapper */ if (*regbase == 0 && stype == DMP_SLAVE_TYPE_SLAVE) *regbase = val & DMP_SLAVE_ADDR_BASE; if (*wrapbase == 0 && stype == wraptype) *wrapbase = val & DMP_SLAVE_ADDR_BASE; } while (*regbase == 0 || *wrapbase == 0); return 0; } static int brcmf_chip_dmp_erom_scan(struct brcmf_chip_priv *ci) { struct brcmf_core *core; u32 eromaddr; u8 desc_type = 0; u32 val; u16 id; u8 nmw, nsw, rev; u32 base, wrap; int err; eromaddr = ci->ops->read32(ci->ctx, CORE_CC_REG(ci->pub.enum_base, eromptr)); while (desc_type != DMP_DESC_EOT) { val = brcmf_chip_dmp_get_desc(ci, &eromaddr, &desc_type); if (!(val & DMP_DESC_VALID)) continue; if (desc_type == DMP_DESC_EMPTY) continue; /* need a component descriptor */ if (desc_type != DMP_DESC_COMPONENT) continue; id = (val & DMP_COMP_PARTNUM) >> DMP_COMP_PARTNUM_S; /* next descriptor must be component as well */ val = brcmf_chip_dmp_get_desc(ci, &eromaddr, &desc_type); if (WARN_ON((val & DMP_DESC_TYPE_MSK) != DMP_DESC_COMPONENT)) return -EFAULT; /* only look at cores with master port(s) */ nmw = (val & DMP_COMP_NUM_MWRAP) >> DMP_COMP_NUM_MWRAP_S; nsw = (val & DMP_COMP_NUM_SWRAP) >> DMP_COMP_NUM_SWRAP_S; rev = (val & DMP_COMP_REVISION) >> DMP_COMP_REVISION_S; /* need core with ports */ if (nmw + nsw == 0 && id != BCMA_CORE_PMU && id != BCMA_CORE_GCI) continue; /* try to obtain register address info */ err = brcmf_chip_dmp_get_regaddr(ci, &eromaddr, &base, &wrap); if (err) continue; /* finally a core to be added */ core = brcmf_chip_add_core(ci, id, base, wrap); if (IS_ERR(core)) return PTR_ERR(core); core->rev = rev; } return 0; } u32 brcmf_chip_enum_base(u16 devid) { return SI_ENUM_BASE_DEFAULT; } static int brcmf_chip_recognition(struct brcmf_chip_priv *ci) { struct brcmf_core *core; u32 regdata; u32 socitype; int ret; const u32 READ_FAILED = 0xFFFFFFFF; /* Get CC core rev * Chipid is assume to be at offset 0 from SI_ENUM_BASE * For different chiptypes or old sdio hosts w/o chipcommon, * other ways of recognition should be added here. */ regdata = ci->ops->read32(ci->ctx, CORE_CC_REG(ci->pub.enum_base, chipid)); if (regdata == READ_FAILED) { brcmf_err("MMIO read failed: 0x%08x\n", regdata); return -ENODEV; } ci->pub.chip = regdata & CID_ID_MASK; ci->pub.chiprev = (regdata & CID_REV_MASK) >> CID_REV_SHIFT; socitype = (regdata & CID_TYPE_MASK) >> CID_TYPE_SHIFT; brcmf_chip_name(ci->pub.chip, ci->pub.chiprev, ci->pub.name, sizeof(ci->pub.name)); brcmf_dbg(INFO, "found %s chip: %s\n", socitype == SOCI_SB ? "SB" : "AXI", ci->pub.name); if (socitype == SOCI_SB) { if (ci->pub.chip != BRCM_CC_4329_CHIP_ID) { brcmf_err("SB chip is not supported\n"); return -ENODEV; } ci->iscoreup = brcmf_chip_sb_iscoreup; ci->coredisable = brcmf_chip_sb_coredisable; ci->resetcore = brcmf_chip_sb_resetcore; core = brcmf_chip_add_core(ci, BCMA_CORE_CHIPCOMMON, SI_ENUM_BASE_DEFAULT, 0); brcmf_chip_sb_corerev(ci, core); core = brcmf_chip_add_core(ci, BCMA_CORE_SDIO_DEV, BCM4329_CORE_BUS_BASE, 0); brcmf_chip_sb_corerev(ci, core); core = brcmf_chip_add_core(ci, BCMA_CORE_INTERNAL_MEM, BCM4329_CORE_SOCRAM_BASE, 0); brcmf_chip_sb_corerev(ci, core); core = brcmf_chip_add_core(ci, BCMA_CORE_ARM_CM3, BCM4329_CORE_ARM_BASE, 0); brcmf_chip_sb_corerev(ci, core); core = brcmf_chip_add_core(ci, BCMA_CORE_80211, 0x18001000, 0); brcmf_chip_sb_corerev(ci, core); } else if (socitype == SOCI_AI) { ci->iscoreup = brcmf_chip_ai_iscoreup; ci->coredisable = brcmf_chip_ai_coredisable; ci->resetcore = brcmf_chip_ai_resetcore; brcmf_chip_dmp_erom_scan(ci); } else { brcmf_err("chip backplane type %u is not supported\n", socitype); return -ENODEV; } ret = brcmf_chip_cores_check(ci); if (ret) return ret; /* assure chip is passive for core access */ brcmf_chip_set_passive(&ci->pub); /* Call bus specific reset function now. Cores have been determined * but further access may require a chip specific reset at this point. */ if (ci->ops->reset) { ci->ops->reset(ci->ctx, &ci->pub); brcmf_chip_set_passive(&ci->pub); } return brcmf_chip_get_raminfo(&ci->pub); } static void brcmf_chip_disable_arm(struct brcmf_chip_priv *chip, u16 id) { struct brcmf_core *core; struct brcmf_core_priv *cpu; u32 val; core = brcmf_chip_get_core(&chip->pub, id); if (!core) return; switch (id) { case BCMA_CORE_ARM_CM3: brcmf_chip_coredisable(core, 0, 0); break; case BCMA_CORE_ARM_CR4: case BCMA_CORE_ARM_CA7: cpu = container_of(core, struct brcmf_core_priv, pub); /* clear all IOCTL bits except HALT bit */ val = chip->ops->read32(chip->ctx, cpu->wrapbase + BCMA_IOCTL); val &= ARMCR4_BCMA_IOCTL_CPUHALT; brcmf_chip_resetcore(core, val, ARMCR4_BCMA_IOCTL_CPUHALT, ARMCR4_BCMA_IOCTL_CPUHALT); break; default: brcmf_err("unknown id: %u\n", id); break; } } static int brcmf_chip_setup(struct brcmf_chip_priv *chip) { struct brcmf_chip *pub; struct brcmf_core_priv *cc; struct brcmf_core *pmu; u32 base; u32 val; int ret = 0; pub = &chip->pub; cc = list_first_entry(&chip->cores, struct brcmf_core_priv, list); base = cc->pub.base; /* get chipcommon capabilites */ pub->cc_caps = chip->ops->read32(chip->ctx, CORE_CC_REG(base, capabilities)); pub->cc_caps_ext = chip->ops->read32(chip->ctx, CORE_CC_REG(base, capabilities_ext)); /* get pmu caps & rev */ pmu = brcmf_chip_get_pmu(pub); /* after reading cc_caps_ext */ if (pub->cc_caps & CC_CAP_PMU) { val = chip->ops->read32(chip->ctx, CORE_CC_REG(pmu->base, pmucapabilities)); pub->pmurev = val & PCAP_REV_MASK; pub->pmucaps = val; } brcmf_dbg(INFO, "ccrev=%d, pmurev=%d, pmucaps=0x%x\n", cc->pub.rev, pub->pmurev, pub->pmucaps); /* execute bus core specific setup */ if (chip->ops->setup) ret = chip->ops->setup(chip->ctx, pub); return ret; } struct brcmf_chip *brcmf_chip_attach(void *ctx, u16 devid, const struct brcmf_buscore_ops *ops) { struct brcmf_chip_priv *chip; int err = 0; if (WARN_ON(!ops->read32)) err = -EINVAL; if (WARN_ON(!ops->write32)) err = -EINVAL; if (WARN_ON(!ops->prepare)) err = -EINVAL; if (WARN_ON(!ops->activate)) err = -EINVAL; if (err < 0) return ERR_PTR(-EINVAL); chip = kzalloc(sizeof(*chip), GFP_KERNEL); if (!chip) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&chip->cores); chip->num_cores = 0; chip->ops = ops; chip->ctx = ctx; chip->pub.enum_base = brcmf_chip_enum_base(devid); err = ops->prepare(ctx); if (err < 0) goto fail; err = brcmf_chip_recognition(chip); if (err < 0) goto fail; err = brcmf_chip_setup(chip); if (err < 0) goto fail; return &chip->pub; fail: brcmf_chip_detach(&chip->pub); return ERR_PTR(err); } void brcmf_chip_detach(struct brcmf_chip *pub) { struct brcmf_chip_priv *chip; struct brcmf_core_priv *core; struct brcmf_core_priv *tmp; chip = container_of(pub, struct brcmf_chip_priv, pub); list_for_each_entry_safe(core, tmp, &chip->cores, list) { list_del(&core->list); kfree(core); } kfree(chip); } struct brcmf_core *brcmf_chip_get_d11core(struct brcmf_chip *pub, u8 unit) { struct brcmf_chip_priv *chip; struct brcmf_core_priv *core; chip = container_of(pub, struct brcmf_chip_priv, pub); list_for_each_entry(core, &chip->cores, list) { if (core->pub.id == BCMA_CORE_80211) { if (unit-- == 0) return &core->pub; } } return NULL; } struct brcmf_core *brcmf_chip_get_core(struct brcmf_chip *pub, u16 coreid) { struct brcmf_chip_priv *chip; struct brcmf_core_priv *core; chip = container_of(pub, struct brcmf_chip_priv, pub); list_for_each_entry(core, &chip->cores, list) if (core->pub.id == coreid) return &core->pub; return NULL; } struct brcmf_core *brcmf_chip_get_chipcommon(struct brcmf_chip *pub) { struct brcmf_chip_priv *chip; struct brcmf_core_priv *cc; chip = container_of(pub, struct brcmf_chip_priv, pub); cc = list_first_entry(&chip->cores, struct brcmf_core_priv, list); if (WARN_ON(!cc || cc->pub.id != BCMA_CORE_CHIPCOMMON)) return brcmf_chip_get_core(pub, BCMA_CORE_CHIPCOMMON); return &cc->pub; } struct brcmf_core *brcmf_chip_get_pmu(struct brcmf_chip *pub) { struct brcmf_core *cc = brcmf_chip_get_chipcommon(pub); struct brcmf_core *pmu; /* See if there is separated PMU core available */ if (cc->rev >= 35 && pub->cc_caps_ext & BCMA_CC_CAP_EXT_AOB_PRESENT) { pmu = brcmf_chip_get_core(pub, BCMA_CORE_PMU); if (pmu) return pmu; } /* Fallback to ChipCommon core for older hardware */ return cc; } bool brcmf_chip_iscoreup(struct brcmf_core *pub) { struct brcmf_core_priv *core; core = container_of(pub, struct brcmf_core_priv, pub); return core->chip->iscoreup(core); } void brcmf_chip_coredisable(struct brcmf_core *pub, u32 prereset, u32 reset) { struct brcmf_core_priv *core; core = container_of(pub, struct brcmf_core_priv, pub); core->chip->coredisable(core, prereset, reset); } void brcmf_chip_resetcore(struct brcmf_core *pub, u32 prereset, u32 reset, u32 postreset) { struct brcmf_core_priv *core; core = container_of(pub, struct brcmf_core_priv, pub); core->chip->resetcore(core, prereset, reset, postreset); } static void brcmf_chip_cm3_set_passive(struct brcmf_chip_priv *chip) { struct brcmf_core *core; struct brcmf_core_priv *sr; brcmf_chip_disable_arm(chip, BCMA_CORE_ARM_CM3); core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_80211); brcmf_chip_resetcore(core, D11_BCMA_IOCTL_PHYRESET | D11_BCMA_IOCTL_PHYCLOCKEN, D11_BCMA_IOCTL_PHYCLOCKEN, D11_BCMA_IOCTL_PHYCLOCKEN); core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_INTERNAL_MEM); brcmf_chip_resetcore(core, 0, 0, 0); /* disable bank #3 remap for this device */ if (chip->pub.chip == BRCM_CC_43430_CHIP_ID || chip->pub.chip == CY_CC_43439_CHIP_ID) { sr = container_of(core, struct brcmf_core_priv, pub); brcmf_chip_core_write32(sr, SOCRAMREGOFFS(bankidx), 3); brcmf_chip_core_write32(sr, SOCRAMREGOFFS(bankpda), 0); } } static bool brcmf_chip_cm3_set_active(struct brcmf_chip_priv *chip) { struct brcmf_core *core; core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_INTERNAL_MEM); if (!brcmf_chip_iscoreup(core)) { brcmf_err("SOCRAM core is down after reset?\n"); return false; } chip->ops->activate(chip->ctx, &chip->pub, 0); core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_ARM_CM3); brcmf_chip_resetcore(core, 0, 0, 0); return true; } static inline void brcmf_chip_cr4_set_passive(struct brcmf_chip_priv *chip) { int i; struct brcmf_core *core; brcmf_chip_disable_arm(chip, BCMA_CORE_ARM_CR4); /* Disable the cores only and let the firmware enable them. * Releasing reset ourselves breaks BCM4387 in weird ways. */ for (i = 0; (core = brcmf_chip_get_d11core(&chip->pub, i)); i++) brcmf_chip_coredisable(core, D11_BCMA_IOCTL_PHYRESET | D11_BCMA_IOCTL_PHYCLOCKEN, D11_BCMA_IOCTL_PHYCLOCKEN); } static bool brcmf_chip_cr4_set_active(struct brcmf_chip_priv *chip, u32 rstvec) { struct brcmf_core *core; chip->ops->activate(chip->ctx, &chip->pub, rstvec); /* restore ARM */ core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_ARM_CR4); brcmf_chip_resetcore(core, ARMCR4_BCMA_IOCTL_CPUHALT, 0, 0); return true; } static inline void brcmf_chip_ca7_set_passive(struct brcmf_chip_priv *chip) { struct brcmf_core *core; brcmf_chip_disable_arm(chip, BCMA_CORE_ARM_CA7); core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_80211); brcmf_chip_resetcore(core, D11_BCMA_IOCTL_PHYRESET | D11_BCMA_IOCTL_PHYCLOCKEN, D11_BCMA_IOCTL_PHYCLOCKEN, D11_BCMA_IOCTL_PHYCLOCKEN); } static bool brcmf_chip_ca7_set_active(struct brcmf_chip_priv *chip, u32 rstvec) { struct brcmf_core *core; chip->ops->activate(chip->ctx, &chip->pub, rstvec); /* restore ARM */ core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_ARM_CA7); brcmf_chip_resetcore(core, ARMCR4_BCMA_IOCTL_CPUHALT, 0, 0); return true; } void brcmf_chip_set_passive(struct brcmf_chip *pub) { struct brcmf_chip_priv *chip; struct brcmf_core *arm; brcmf_dbg(TRACE, "Enter\n"); chip = container_of(pub, struct brcmf_chip_priv, pub); arm = brcmf_chip_get_core(pub, BCMA_CORE_ARM_CR4); if (arm) { brcmf_chip_cr4_set_passive(chip); return; } arm = brcmf_chip_get_core(pub, BCMA_CORE_ARM_CA7); if (arm) { brcmf_chip_ca7_set_passive(chip); return; } arm = brcmf_chip_get_core(pub, BCMA_CORE_ARM_CM3); if (arm) { brcmf_chip_cm3_set_passive(chip); return; } } bool brcmf_chip_set_active(struct brcmf_chip *pub, u32 rstvec) { struct brcmf_chip_priv *chip; struct brcmf_core *arm; brcmf_dbg(TRACE, "Enter\n"); chip = container_of(pub, struct brcmf_chip_priv, pub); arm = brcmf_chip_get_core(pub, BCMA_CORE_ARM_CR4); if (arm) return brcmf_chip_cr4_set_active(chip, rstvec); arm = brcmf_chip_get_core(pub, BCMA_CORE_ARM_CA7); if (arm) return brcmf_chip_ca7_set_active(chip, rstvec); arm = brcmf_chip_get_core(pub, BCMA_CORE_ARM_CM3); if (arm) return brcmf_chip_cm3_set_active(chip); return false; } bool brcmf_chip_sr_capable(struct brcmf_chip *pub) { u32 base, addr, reg, pmu_cc3_mask = ~0; struct brcmf_chip_priv *chip; struct brcmf_core *pmu = brcmf_chip_get_pmu(pub); brcmf_dbg(TRACE, "Enter\n"); /* old chips with PMU version less than 17 don't support save restore */ if (pub->pmurev < 17) return false; base = brcmf_chip_get_chipcommon(pub)->base; chip = container_of(pub, struct brcmf_chip_priv, pub); switch (pub->chip) { case BRCM_CC_4354_CHIP_ID: case BRCM_CC_4356_CHIP_ID: case BRCM_CC_4345_CHIP_ID: case BRCM_CC_43454_CHIP_ID: /* explicitly check SR engine enable bit */ pmu_cc3_mask = BIT(2); fallthrough; case BRCM_CC_43241_CHIP_ID: case BRCM_CC_4335_CHIP_ID: case BRCM_CC_4339_CHIP_ID: /* read PMU chipcontrol register 3 */ addr = CORE_CC_REG(pmu->base, chipcontrol_addr); chip->ops->write32(chip->ctx, addr, 3); addr = CORE_CC_REG(pmu->base, chipcontrol_data); reg = chip->ops->read32(chip->ctx, addr); return (reg & pmu_cc3_mask) != 0; case BRCM_CC_43430_CHIP_ID: case CY_CC_43439_CHIP_ID: addr = CORE_CC_REG(base, sr_control1); reg = chip->ops->read32(chip->ctx, addr); return reg != 0; case BRCM_CC_4355_CHIP_ID: case CY_CC_4373_CHIP_ID: /* explicitly check SR engine enable bit */ addr = CORE_CC_REG(base, sr_control0); reg = chip->ops->read32(chip->ctx, addr); return (reg & CC_SR_CTL0_ENABLE_MASK) != 0; case BRCM_CC_4359_CHIP_ID: case BRCM_CC_43751_CHIP_ID: - case CY_CC_43752_CHIP_ID: + case BRCM_CC_43752_CHIP_ID: case CY_CC_43012_CHIP_ID: addr = CORE_CC_REG(pmu->base, retention_ctl); reg = chip->ops->read32(chip->ctx, addr); return (reg & (PMU_RCTL_MACPHY_DISABLE_MASK | PMU_RCTL_LOGIC_DISABLE_MASK)) == 0; default: addr = CORE_CC_REG(pmu->base, pmucapabilities_ext); reg = chip->ops->read32(chip->ctx, addr); if ((reg & PCAPEXT_SR_SUPPORTED_MASK) == 0) return false; addr = CORE_CC_REG(pmu->base, retention_ctl); reg = chip->ops->read32(chip->ctx, addr); return (reg & (PMU_RCTL_MACPHY_DISABLE_MASK | PMU_RCTL_LOGIC_DISABLE_MASK)) == 0; } } diff --git a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/dmi.c b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/dmi.c index c3a602197662..abe7f6501e5e 100644 --- a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/dmi.c +++ b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/dmi.c @@ -1,215 +1,229 @@ // SPDX-License-Identifier: ISC /* * Copyright 2018 Hans de Goede */ #include #include #include "core.h" #include "common.h" #include "brcm_hw_ids.h" /* The DMI data never changes so we can use a static buf for this */ static char dmi_board_type[128]; struct brcmf_dmi_data { u32 chip; u32 chiprev; const char *board_type; }; /* NOTE: Please keep all entries sorted alphabetically */ static const struct brcmf_dmi_data acepc_t8_data = { BRCM_CC_4345_CHIP_ID, 6, "acepc-t8" }; +static const struct brcmf_dmi_data acer_a1_840_data = { + BRCM_CC_43340_CHIP_ID, 2, "acer-a1-840" +}; + /* The Chuwi Hi8 Pro uses the same Ampak AP6212 module as the Chuwi Vi8 Plus * and the nvram for the Vi8 Plus is already in linux-firmware, so use that. */ static const struct brcmf_dmi_data chuwi_hi8_pro_data = { BRCM_CC_43430_CHIP_ID, 0, "ilife-S806" }; static const struct brcmf_dmi_data gpd_win_pocket_data = { BRCM_CC_4356_CHIP_ID, 2, "gpd-win-pocket" }; static const struct brcmf_dmi_data jumper_ezpad_mini3_data = { BRCM_CC_43430_CHIP_ID, 0, "jumper-ezpad-mini3" }; static const struct brcmf_dmi_data meegopad_t08_data = { BRCM_CC_43340_CHIP_ID, 2, "meegopad-t08" }; static const struct brcmf_dmi_data pov_tab_p1006w_data = { BRCM_CC_43340_CHIP_ID, 2, "pov-tab-p1006w-data" }; static const struct brcmf_dmi_data predia_basic_data = { BRCM_CC_43341_CHIP_ID, 2, "predia-basic" }; /* Note the Voyo winpad A15 tablet uses the same Ampak AP6330 module, with the * exact same nvram file as the Prowise-PT301 tablet. Since the nvram for the * Prowise-PT301 is already in linux-firmware we just point to that here. */ static const struct brcmf_dmi_data voyo_winpad_a15_data = { BRCM_CC_4330_CHIP_ID, 4, "Prowise-PT301" }; static const struct dmi_system_id dmi_platform_data[] = { { /* ACEPC T8 Cherry Trail Z8350 mini PC */ .matches = { DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T8"), /* also match on somewhat unique bios-version */ DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), }, .driver_data = (void *)&acepc_t8_data, }, { /* ACEPC T11 Cherry Trail Z8350 mini PC, same wifi as the T8 */ .matches = { DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T11"), /* also match on somewhat unique bios-version */ DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), }, .driver_data = (void *)&acepc_t8_data, }, { /* ACEPC W5 Pro Cherry Trail Z8350 HDMI stick, same wifi as the T8 */ .matches = { DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"), DMI_MATCH(DMI_CHASSIS_TYPE, "3"), DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), }, .driver_data = (void *)&acepc_t8_data, }, + { + /* Acer Iconia One 8 A1-840 (non FHD version) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "BayTrail"), + /* Above strings are too generic also match BIOS date */ + DMI_MATCH(DMI_BIOS_DATE, "04/01/2014"), + }, + .driver_data = (void *)&acer_a1_840_data, + }, { /* Chuwi Hi8 Pro with D2D3_Hi8Pro.233 BIOS */ .matches = { DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Hampoo"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "MRD"), /* Above strings are too generic, also match on BIOS date */ DMI_MATCH(DMI_BIOS_DATE, "05/10/2016"), }, .driver_data = (void *)&chuwi_hi8_pro_data, }, { /* Cyberbook T116 rugged tablet */ .matches = { DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "20170531"), }, /* The factory image nvram file is identical to the ACEPC T8 one */ .driver_data = (void *)&acepc_t8_data, }, { /* Match for the GPDwin which unfortunately uses somewhat * generic dmi strings, which is why we test for 4 strings. * Comparing against 23 other byt/cht boards, board_vendor * and board_name are unique to the GPDwin, where as only one * other board has the same board_serial and 3 others have * the same default product_name. Also the GPDwin is the * only device to have both board_ and product_name not set. */ .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), DMI_MATCH(DMI_BOARD_NAME, "Default string"), DMI_MATCH(DMI_BOARD_SERIAL, "Default string"), DMI_MATCH(DMI_PRODUCT_NAME, "Default string"), }, .driver_data = (void *)&gpd_win_pocket_data, }, { /* Jumper EZpad mini3 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), DMI_MATCH(DMI_PRODUCT_NAME, "CherryTrail"), /* jumperx.T87.KFBNEEA02 with the version-nr dropped */ DMI_MATCH(DMI_BIOS_VERSION, "jumperx.T87.KFBNEEA"), }, .driver_data = (void *)&jumper_ezpad_mini3_data, }, { /* Meegopad T08 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Default string"), DMI_MATCH(DMI_PRODUCT_NAME, "Default string"), DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"), DMI_MATCH(DMI_BOARD_VERSION, "V1.1"), }, .driver_data = (void *)&meegopad_t08_data, }, { /* Point of View TAB-P1006W-232 */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Insyde"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "BayTrail"), /* Note 105b is Foxcon's USB/PCI vendor id */ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "105B"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "0E57"), }, .driver_data = (void *)&pov_tab_p1006w_data, }, { /* Predia Basic tablet (+ with keyboard dock) */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), DMI_MATCH(DMI_PRODUCT_NAME, "CherryTrail"), /* Mx.WT107.KUBNGEA02 with the version-nr dropped */ DMI_MATCH(DMI_BIOS_VERSION, "Mx.WT107.KUBNGEA"), }, .driver_data = (void *)&predia_basic_data, }, { /* Voyo winpad A15 tablet */ .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), /* Above strings are too generic, also match on BIOS date */ DMI_MATCH(DMI_BIOS_DATE, "11/20/2014"), }, .driver_data = (void *)&voyo_winpad_a15_data, }, {} }; void brcmf_dmi_probe(struct brcmf_mp_device *settings, u32 chip, u32 chiprev) { const struct dmi_system_id *match; const struct brcmf_dmi_data *data; const char *sys_vendor; const char *product_name; /* Some models have DMI strings which are too generic, e.g. * "Default string", we use a quirk table for these. */ for (match = dmi_first_match(dmi_platform_data); match; match = dmi_first_match(match + 1)) { data = match->driver_data; if (data->chip == chip && data->chiprev == chiprev) { settings->board_type = data->board_type; return; } } /* Not found in the quirk-table, use sys_vendor-product_name */ sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR); product_name = dmi_get_system_info(DMI_PRODUCT_NAME); if (sys_vendor && product_name) { snprintf(dmi_board_type, sizeof(dmi_board_type), "%s-%s", sys_vendor, product_name); settings->board_type = dmi_board_type; } } diff --git a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/firmware.c b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/firmware.c index aa8f05e822a0..0ad9abd67fd6 100644 --- a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/firmware.c +++ b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/firmware.c @@ -1,883 +1,887 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2013 Broadcom Corporation */ #include #include #include #include #include #include #include #include "debug.h" #include "firmware.h" #include "core.h" #include "common.h" #include "chip.h" #define BRCMF_FW_MAX_NVRAM_SIZE 64000 #define BRCMF_FW_NVRAM_DEVPATH_LEN 19 /* devpath0=pcie/1/4/ */ #define BRCMF_FW_NVRAM_PCIEDEV_LEN 20 /* pcie/1/4/ + \0 */ #define BRCMF_FW_DEFAULT_BOARDREV "boardrev=0xff" #define BRCMF_FW_MACADDR_FMT "macaddr=%pM" #define BRCMF_FW_MACADDR_LEN (7 + ETH_ALEN * 3) enum nvram_parser_state { IDLE, KEY, VALUE, COMMENT, END }; /** * struct nvram_parser - internal info for parser. * * @state: current parser state. * @data: input buffer being parsed. * @nvram: output buffer with parse result. * @nvram_len: length of parse result. * @line: current line. * @column: current column in line. * @pos: byte offset in input buffer. * @entry: start position of key,value entry. * @multi_dev_v1: detect pcie multi device v1 (compressed). * @multi_dev_v2: detect pcie multi device v2. * @boardrev_found: nvram contains boardrev information. * @strip_mac: strip the MAC address. */ struct nvram_parser { enum nvram_parser_state state; const u8 *data; u8 *nvram; u32 nvram_len; u32 line; u32 column; u32 pos; u32 entry; bool multi_dev_v1; bool multi_dev_v2; bool boardrev_found; bool strip_mac; }; /* * is_nvram_char() - check if char is a valid one for NVRAM entry * * It accepts all printable ASCII chars except for '#' which opens a comment. * Please note that ' ' (space) while accepted is not a valid key name char. */ static bool is_nvram_char(char c) { /* comment marker excluded */ if (c == '#') return false; /* key and value may have any other readable character */ return (c >= 0x20 && c < 0x7f); } static bool is_whitespace(char c) { return (c == ' ' || c == '\r' || c == '\n' || c == '\t'); } static enum nvram_parser_state brcmf_nvram_handle_idle(struct nvram_parser *nvp) { char c; c = nvp->data[nvp->pos]; if (c == '\n') return COMMENT; if (is_whitespace(c) || c == '\0') goto proceed; if (c == '#') return COMMENT; if (is_nvram_char(c)) { nvp->entry = nvp->pos; return KEY; } brcmf_dbg(INFO, "warning: ln=%d:col=%d: ignoring invalid character\n", nvp->line, nvp->column); proceed: nvp->column++; nvp->pos++; return IDLE; } static enum nvram_parser_state brcmf_nvram_handle_key(struct nvram_parser *nvp) { enum nvram_parser_state st = nvp->state; char c; c = nvp->data[nvp->pos]; if (c == '=') { /* ignore RAW1 by treating as comment */ if (strncmp(&nvp->data[nvp->entry], "RAW1", 4) == 0) st = COMMENT; else st = VALUE; if (strncmp(&nvp->data[nvp->entry], "devpath", 7) == 0) nvp->multi_dev_v1 = true; if (strncmp(&nvp->data[nvp->entry], "pcie/", 5) == 0) nvp->multi_dev_v2 = true; if (strncmp(&nvp->data[nvp->entry], "boardrev", 8) == 0) nvp->boardrev_found = true; /* strip macaddr if platform MAC overrides */ if (nvp->strip_mac && strncmp(&nvp->data[nvp->entry], "macaddr", 7) == 0) st = COMMENT; } else if (!is_nvram_char(c) || c == ' ') { brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n", nvp->line, nvp->column); return COMMENT; } nvp->column++; nvp->pos++; return st; } static enum nvram_parser_state brcmf_nvram_handle_value(struct nvram_parser *nvp) { char c; #if defined(__linux__) char *skv; char *ekv; #elif defined(__FreeBSD__) const char *skv; const char *ekv; #endif u32 cplen; c = nvp->data[nvp->pos]; if (!is_nvram_char(c)) { /* key,value pair complete */ #if defined(__linux__) ekv = (u8 *)&nvp->data[nvp->pos]; skv = (u8 *)&nvp->data[nvp->entry]; #elif defined(__FreeBSD__) ekv = &nvp->data[nvp->pos]; skv = &nvp->data[nvp->entry]; #endif cplen = ekv - skv; if (nvp->nvram_len + cplen + 1 >= BRCMF_FW_MAX_NVRAM_SIZE) return END; /* copy to output buffer */ memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen); nvp->nvram_len += cplen; nvp->nvram[nvp->nvram_len] = '\0'; nvp->nvram_len++; return IDLE; } nvp->pos++; nvp->column++; return VALUE; } static enum nvram_parser_state brcmf_nvram_handle_comment(struct nvram_parser *nvp) { #if defined(__linux__) char *eoc, *sol; sol = (char *)&nvp->data[nvp->pos]; #elif defined(__FreeBSD__) const char *eoc, *sol; sol = &nvp->data[nvp->pos]; #endif eoc = strchr(sol, '\n'); if (!eoc) { eoc = strchr(sol, '\0'); if (!eoc) return END; } /* eat all moving to next line */ nvp->line++; nvp->column = 1; nvp->pos += (eoc - sol) + 1; return IDLE; } static enum nvram_parser_state brcmf_nvram_handle_end(struct nvram_parser *nvp) { /* final state */ return END; } static enum nvram_parser_state (*nv_parser_states[])(struct nvram_parser *nvp) = { brcmf_nvram_handle_idle, brcmf_nvram_handle_key, brcmf_nvram_handle_value, brcmf_nvram_handle_comment, brcmf_nvram_handle_end }; static int brcmf_init_nvram_parser(struct nvram_parser *nvp, const u8 *data, size_t data_len) { size_t size; memset(nvp, 0, sizeof(*nvp)); nvp->data = data; /* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */ if (data_len > BRCMF_FW_MAX_NVRAM_SIZE) size = BRCMF_FW_MAX_NVRAM_SIZE; else size = data_len; /* Add space for properties we may add */ size += strlen(BRCMF_FW_DEFAULT_BOARDREV) + 1; size += BRCMF_FW_MACADDR_LEN + 1; /* Alloc for extra 0 byte + roundup by 4 + length field */ size += 1 + 3 + sizeof(u32); nvp->nvram = kzalloc(size, GFP_KERNEL); if (!nvp->nvram) return -ENOMEM; nvp->line = 1; nvp->column = 1; return 0; } /* brcmf_fw_strip_multi_v1 :Some nvram files contain settings for multiple * devices. Strip it down for one device, use domain_nr/bus_nr to determine * which data is to be returned. v1 is the version where nvram is stored * compressed and "devpath" maps to index for valid entries. */ static void brcmf_fw_strip_multi_v1(struct nvram_parser *nvp, u16 domain_nr, u16 bus_nr) { /* Device path with a leading '=' key-value separator */ char pci_path[20]; size_t pci_len; char pcie_path[20]; size_t pcie_len; u32 i, j; bool found; u8 *nvram; u8 id; nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL); if (!nvram) goto fail; /* min length: devpath0=pcie/1/4/ + 0:x=y */ if (nvp->nvram_len < BRCMF_FW_NVRAM_DEVPATH_LEN + 6) goto fail; /* First search for the devpathX and see if it is the configuration * for domain_nr/bus_nr. Search complete nvp */ snprintf(pci_path, sizeof(pci_path), "=pci/%d/%d", domain_nr, bus_nr); pci_len = strlen(pci_path); snprintf(pcie_path, sizeof(pcie_path), "=pcie/%d/%d", domain_nr, bus_nr); pcie_len = strlen(pcie_path); found = false; i = 0; while (i < nvp->nvram_len - BRCMF_FW_NVRAM_DEVPATH_LEN) { /* Format: devpathX=pcie/Y/Z/ * Y = domain_nr, Z = bus_nr, X = virtual ID */ if (strncmp(&nvp->nvram[i], "devpath", 7) == 0 && (!strncmp(&nvp->nvram[i + 8], pci_path, pci_len) || !strncmp(&nvp->nvram[i + 8], pcie_path, pcie_len))) { id = nvp->nvram[i + 7] - '0'; found = true; break; } while (nvp->nvram[i] != 0) i++; i++; } if (!found) goto fail; /* Now copy all valid entries, release old nvram and assign new one */ i = 0; j = 0; while (i < nvp->nvram_len) { if ((nvp->nvram[i] - '0' == id) && (nvp->nvram[i + 1] == ':')) { i += 2; if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0) nvp->boardrev_found = true; while (nvp->nvram[i] != 0) { nvram[j] = nvp->nvram[i]; i++; j++; } nvram[j] = 0; j++; } while (nvp->nvram[i] != 0) i++; i++; } kfree(nvp->nvram); nvp->nvram = nvram; nvp->nvram_len = j; return; fail: kfree(nvram); nvp->nvram_len = 0; } /* brcmf_fw_strip_multi_v2 :Some nvram files contain settings for multiple * devices. Strip it down for one device, use domain_nr/bus_nr to determine * which data is to be returned. v2 is the version where nvram is stored * uncompressed, all relevant valid entries are identified by * pcie/domain_nr/bus_nr: */ static void brcmf_fw_strip_multi_v2(struct nvram_parser *nvp, u16 domain_nr, u16 bus_nr) { char prefix[BRCMF_FW_NVRAM_PCIEDEV_LEN]; size_t len; u32 i, j; u8 *nvram; nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL); if (!nvram) { nvp->nvram_len = 0; return; } /* Copy all valid entries, release old nvram and assign new one. * Valid entries are of type pcie/X/Y/ where X = domain_nr and * Y = bus_nr. */ snprintf(prefix, sizeof(prefix), "pcie/%d/%d/", domain_nr, bus_nr); len = strlen(prefix); i = 0; j = 0; while (i < nvp->nvram_len - len) { if (strncmp(&nvp->nvram[i], prefix, len) == 0) { i += len; if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0) nvp->boardrev_found = true; while (nvp->nvram[i] != 0) { nvram[j] = nvp->nvram[i]; i++; j++; } nvram[j] = 0; j++; } while (nvp->nvram[i] != 0) i++; i++; } kfree(nvp->nvram); nvp->nvram = nvram; nvp->nvram_len = j; } static void brcmf_fw_add_defaults(struct nvram_parser *nvp) { if (nvp->boardrev_found) return; memcpy(&nvp->nvram[nvp->nvram_len], &BRCMF_FW_DEFAULT_BOARDREV, strlen(BRCMF_FW_DEFAULT_BOARDREV)); nvp->nvram_len += strlen(BRCMF_FW_DEFAULT_BOARDREV); nvp->nvram[nvp->nvram_len] = '\0'; nvp->nvram_len++; } static void brcmf_fw_add_macaddr(struct nvram_parser *nvp, u8 *mac) { int len; len = scnprintf(&nvp->nvram[nvp->nvram_len], BRCMF_FW_MACADDR_LEN + 1, BRCMF_FW_MACADDR_FMT, mac); WARN_ON(len != BRCMF_FW_MACADDR_LEN); nvp->nvram_len += len + 1; } /* brcmf_nvram_strip :Takes a buffer of "=\n" lines read from a fil * and ending in a NUL. Removes carriage returns, empty lines, comment lines, * and converts newlines to NULs. Shortens buffer as needed and pads with NULs. * End of buffer is completed with token identifying length of buffer. */ static void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len, u32 *new_length, u16 domain_nr, u16 bus_nr, struct device *dev) { struct nvram_parser nvp; u32 pad; u32 token; __le32 token_le; u8 mac[ETH_ALEN]; if (brcmf_init_nvram_parser(&nvp, data, data_len) < 0) return NULL; if (eth_platform_get_mac_address(dev, mac) == 0) nvp.strip_mac = true; while (nvp.pos < data_len) { nvp.state = nv_parser_states[nvp.state](&nvp); if (nvp.state == END) break; } if (nvp.multi_dev_v1) { nvp.boardrev_found = false; brcmf_fw_strip_multi_v1(&nvp, domain_nr, bus_nr); } else if (nvp.multi_dev_v2) { nvp.boardrev_found = false; brcmf_fw_strip_multi_v2(&nvp, domain_nr, bus_nr); } if (nvp.nvram_len == 0) { kfree(nvp.nvram); return NULL; } brcmf_fw_add_defaults(&nvp); if (nvp.strip_mac) brcmf_fw_add_macaddr(&nvp, mac); pad = nvp.nvram_len; *new_length = roundup(nvp.nvram_len + 1, 4); while (pad != *new_length) { nvp.nvram[pad] = 0; pad++; } token = *new_length / 4; token = (~token << 16) | (token & 0x0000FFFF); token_le = cpu_to_le32(token); memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le)); *new_length += sizeof(token_le); return nvp.nvram; } void brcmf_fw_nvram_free(void *nvram) { kfree(nvram); } struct brcmf_fw { struct device *dev; struct brcmf_fw_request *req; u32 curpos; unsigned int board_index; void (*done)(struct device *dev, int err, struct brcmf_fw_request *req); }; #ifdef CONFIG_EFI /* In some cases the EFI-var stored nvram contains "ccode=ALL" or "ccode=XV" * to specify "worldwide" compatible settings, but these 2 ccode-s do not work * properly. "ccode=ALL" causes channels 12 and 13 to not be available, * "ccode=XV" causes all 5GHz channels to not be available. So we replace both * with "ccode=X2" which allows channels 12+13 and 5Ghz channels in * no-Initiate-Radiation mode. This means that we will never send on these * channels without first having received valid wifi traffic on the channel. */ static void brcmf_fw_fix_efi_nvram_ccode(char *data, unsigned long data_len) { char *ccode; ccode = strnstr((char *)data, "ccode=ALL", data_len); if (!ccode) ccode = strnstr((char *)data, "ccode=XV\r", data_len); if (!ccode) return; ccode[6] = 'X'; ccode[7] = '2'; ccode[8] = '\r'; } static u8 *brcmf_fw_nvram_from_efi(size_t *data_len_ret) { efi_guid_t guid = EFI_GUID(0x74b00bd9, 0x805a, 0x4d61, 0xb5, 0x1f, 0x43, 0x26, 0x81, 0x23, 0xd1, 0x13); unsigned long data_len = 0; efi_status_t status; u8 *data = NULL; if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) return NULL; status = efi.get_variable(L"nvram", &guid, NULL, &data_len, NULL); if (status != EFI_BUFFER_TOO_SMALL) goto fail; data = kmalloc(data_len, GFP_KERNEL); if (!data) goto fail; status = efi.get_variable(L"nvram", &guid, NULL, &data_len, data); if (status != EFI_SUCCESS) goto fail; brcmf_fw_fix_efi_nvram_ccode(data, data_len); brcmf_info("Using nvram EFI variable\n"); *data_len_ret = data_len; return data; fail: kfree(data); return NULL; } #else static inline u8 *brcmf_fw_nvram_from_efi(size_t *data_len) { return NULL; } #endif static void brcmf_fw_free_request(struct brcmf_fw_request *req) { struct brcmf_fw_item *item; int i; for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) { if (item->type == BRCMF_FW_TYPE_BINARY) release_firmware(item->binary); else if (item->type == BRCMF_FW_TYPE_NVRAM) brcmf_fw_nvram_free(item->nv_data.data); } kfree(req); } static int brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx) { struct brcmf_fw *fwctx = ctx; struct brcmf_fw_item *cur; bool free_bcm47xx_nvram = false; bool kfree_nvram = false; u32 nvram_length = 0; void *nvram = NULL; #if defined(__linux__) u8 *data = NULL; #elif defined(__FreeBSD__) const u8 *data = NULL; #endif size_t data_len; brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev)); cur = &fwctx->req->items[fwctx->curpos]; if (fw && fw->data) { #if defined(__linux__) data = (u8 *)fw->data; #elif defined(__FreeBSD__) data = fw->data; #endif data_len = fw->size; } else { - if ((data = bcm47xx_nvram_get_contents(&data_len))) + data = bcm47xx_nvram_get_contents(&data_len); + if (data) { free_bcm47xx_nvram = true; - else if ((data = brcmf_fw_nvram_from_efi(&data_len))) - kfree_nvram = true; - else if (!(cur->flags & BRCMF_FW_REQF_OPTIONAL)) - goto fail; + } else { + data = brcmf_fw_nvram_from_efi(&data_len); + if (data) + kfree_nvram = true; + else if (!(cur->flags & BRCMF_FW_REQF_OPTIONAL)) + goto fail; + } } if (data) nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length, fwctx->req->domain_nr, fwctx->req->bus_nr, fwctx->dev); if (free_bcm47xx_nvram) bcm47xx_nvram_release_contents(data); if (kfree_nvram) kfree(data); release_firmware(fw); if (!nvram && !(cur->flags & BRCMF_FW_REQF_OPTIONAL)) goto fail; brcmf_dbg(TRACE, "nvram %p len %d\n", nvram, nvram_length); cur->nv_data.data = nvram; cur->nv_data.len = nvram_length; return 0; fail: return -ENOENT; } static int brcmf_fw_complete_request(const struct firmware *fw, struct brcmf_fw *fwctx) { struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos]; int ret = 0; brcmf_dbg(TRACE, "firmware %s %sfound\n", cur->path, fw ? "" : "not "); switch (cur->type) { case BRCMF_FW_TYPE_NVRAM: ret = brcmf_fw_request_nvram_done(fw, fwctx); break; case BRCMF_FW_TYPE_BINARY: if (fw) cur->binary = fw; else ret = -ENOENT; break; default: /* something fishy here so bail out early */ brcmf_err("unknown fw type: %d\n", cur->type); release_firmware(fw); ret = -EINVAL; } return (cur->flags & BRCMF_FW_REQF_OPTIONAL) ? 0 : ret; } static char *brcm_alt_fw_path(const char *path, const char *board_type) { char base[BRCMF_FW_NAME_LEN]; const char *suffix; char *ret; if (!board_type) return NULL; suffix = strrchr(path, '.'); if (!suffix || suffix == path) return NULL; /* strip extension at the end */ strscpy(base, path, BRCMF_FW_NAME_LEN); base[suffix - path] = 0; ret = kasprintf(GFP_KERNEL, "%s.%s%s", base, board_type, suffix); if (!ret) brcmf_err("out of memory allocating firmware path for '%s'\n", path); brcmf_dbg(TRACE, "FW alt path: %s\n", ret); return ret; } static int brcmf_fw_request_firmware(const struct firmware **fw, struct brcmf_fw *fwctx) { struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos]; unsigned int i; int ret; /* Files can be board-specific, first try board-specific paths */ for (i = 0; i < ARRAY_SIZE(fwctx->req->board_types); i++) { char *alt_path; if (!fwctx->req->board_types[i]) goto fallback; alt_path = brcm_alt_fw_path(cur->path, fwctx->req->board_types[i]); if (!alt_path) goto fallback; ret = firmware_request_nowarn(fw, alt_path, fwctx->dev); kfree(alt_path); if (ret == 0) return ret; } fallback: return request_firmware(fw, cur->path, fwctx->dev); } static void brcmf_fw_request_done(const struct firmware *fw, void *ctx) { struct brcmf_fw *fwctx = ctx; int ret; ret = brcmf_fw_complete_request(fw, fwctx); while (ret == 0 && ++fwctx->curpos < fwctx->req->n_items) { brcmf_fw_request_firmware(&fw, fwctx); ret = brcmf_fw_complete_request(fw, ctx); } if (ret) { brcmf_fw_free_request(fwctx->req); fwctx->req = NULL; } fwctx->done(fwctx->dev, ret, fwctx->req); kfree(fwctx); } static void brcmf_fw_request_done_alt_path(const struct firmware *fw, void *ctx) { struct brcmf_fw *fwctx = ctx; struct brcmf_fw_item *first = &fwctx->req->items[0]; const char *board_type, *alt_path; int ret = 0; if (fw) { brcmf_fw_request_done(fw, ctx); return; } /* Try next board firmware */ if (fwctx->board_index < ARRAY_SIZE(fwctx->req->board_types)) { board_type = fwctx->req->board_types[fwctx->board_index++]; if (!board_type) goto fallback; alt_path = brcm_alt_fw_path(first->path, board_type); if (!alt_path) goto fallback; ret = request_firmware_nowait(THIS_MODULE, true, alt_path, fwctx->dev, GFP_KERNEL, fwctx, brcmf_fw_request_done_alt_path); kfree(alt_path); if (ret < 0) brcmf_fw_request_done(fw, ctx); return; } fallback: /* Fall back to canonical path if board firmware not found */ ret = request_firmware_nowait(THIS_MODULE, true, first->path, fwctx->dev, GFP_KERNEL, fwctx, brcmf_fw_request_done); if (ret < 0) brcmf_fw_request_done(fw, ctx); } static bool brcmf_fw_request_is_valid(struct brcmf_fw_request *req) { struct brcmf_fw_item *item; int i; if (!req->n_items) return false; for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) { if (!item->path) return false; } return true; } int brcmf_fw_get_firmwares(struct device *dev, struct brcmf_fw_request *req, void (*fw_cb)(struct device *dev, int err, struct brcmf_fw_request *req)) { struct brcmf_fw_item *first = &req->items[0]; struct brcmf_fw *fwctx; char *alt_path = NULL; int ret; brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(dev)); if (!fw_cb) return -EINVAL; if (!brcmf_fw_request_is_valid(req)) return -EINVAL; fwctx = kzalloc(sizeof(*fwctx), GFP_KERNEL); if (!fwctx) return -ENOMEM; fwctx->dev = dev; fwctx->req = req; fwctx->done = fw_cb; /* First try alternative board-specific path if any */ if (fwctx->req->board_types[0]) alt_path = brcm_alt_fw_path(first->path, fwctx->req->board_types[0]); if (alt_path) { fwctx->board_index++; ret = request_firmware_nowait(THIS_MODULE, true, alt_path, fwctx->dev, GFP_KERNEL, fwctx, brcmf_fw_request_done_alt_path); kfree(alt_path); } else { ret = request_firmware_nowait(THIS_MODULE, true, first->path, fwctx->dev, GFP_KERNEL, fwctx, brcmf_fw_request_done); } if (ret < 0) brcmf_fw_request_done(NULL, fwctx); return 0; } struct brcmf_fw_request * brcmf_fw_alloc_request(u32 chip, u32 chiprev, const struct brcmf_firmware_mapping mapping_table[], u32 table_size, struct brcmf_fw_name *fwnames, u32 n_fwnames) { struct brcmf_fw_request *fwreq; char chipname[12]; const char *mp_path; size_t mp_path_len; u32 i, j; char end = '\0'; if (chiprev >= BITS_PER_TYPE(u32)) { brcmf_err("Invalid chip revision %u\n", chiprev); return NULL; } for (i = 0; i < table_size; i++) { if (mapping_table[i].chipid == chip && mapping_table[i].revmask & BIT(chiprev)) break; } brcmf_chip_name(chip, chiprev, chipname, sizeof(chipname)); if (i == table_size) { brcmf_err("Unknown chip %s\n", chipname); return NULL; } fwreq = kzalloc(struct_size(fwreq, items, n_fwnames), GFP_KERNEL); if (!fwreq) return NULL; brcmf_info("using %s for chip %s\n", mapping_table[i].fw_base, chipname); mp_path = brcmf_mp_global.firmware_path; mp_path_len = strnlen(mp_path, BRCMF_FW_ALTPATH_LEN); if (mp_path_len) end = mp_path[mp_path_len - 1]; fwreq->n_items = n_fwnames; for (j = 0; j < n_fwnames; j++) { fwreq->items[j].path = fwnames[j].path; fwnames[j].path[0] = '\0'; /* check if firmware path is provided by module parameter */ if (brcmf_mp_global.firmware_path[0] != '\0') { strscpy(fwnames[j].path, mp_path, BRCMF_FW_NAME_LEN); if (end != '/') { strlcat(fwnames[j].path, "/", BRCMF_FW_NAME_LEN); } } strlcat(fwnames[j].path, mapping_table[i].fw_base, BRCMF_FW_NAME_LEN); strlcat(fwnames[j].path, fwnames[j].extension, BRCMF_FW_NAME_LEN); fwreq->items[j].path = fwnames[j].path; } return fwreq; } diff --git a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/p2p.c b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/p2p.c index e34ba5756af1..21237fa7e229 100644 --- a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/p2p.c +++ b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/p2p.c @@ -1,2569 +1,2561 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2012 Broadcom Corporation */ #include #include #include #include #include #if defined(__FreeBSD__) #include #endif #include #include #include #include "core.h" #include "debug.h" #include "fwil.h" #include "fwil_types.h" #include "p2p.h" #include "cfg80211.h" #include "feature.h" /* parameters used for p2p escan */ #define P2PAPI_SCAN_NPROBES 1 #define P2PAPI_SCAN_DWELL_TIME_MS 80 #define P2PAPI_SCAN_SOCIAL_DWELL_TIME_MS 40 #define P2PAPI_SCAN_HOME_TIME_MS 60 #define P2PAPI_SCAN_NPROBS_TIME_MS 30 #define P2PAPI_SCAN_AF_SEARCH_DWELL_TIME_MS 100 #define WL_SCAN_CONNECT_DWELL_TIME_MS 200 #define WL_SCAN_JOIN_PROBE_INTERVAL_MS 20 #define BRCMF_P2P_WILDCARD_SSID "DIRECT-" #define BRCMF_P2P_WILDCARD_SSID_LEN (sizeof(BRCMF_P2P_WILDCARD_SSID) - 1) #define SOCIAL_CHAN_1 1 #define SOCIAL_CHAN_2 6 #define SOCIAL_CHAN_3 11 #define IS_P2P_SOCIAL_CHANNEL(channel) ((channel == SOCIAL_CHAN_1) || \ (channel == SOCIAL_CHAN_2) || \ (channel == SOCIAL_CHAN_3)) #define BRCMF_P2P_TEMP_CHAN SOCIAL_CHAN_3 #define SOCIAL_CHAN_CNT 3 #define AF_PEER_SEARCH_CNT 2 #define BRCMF_SCB_TIMEOUT_VALUE 20 #define P2P_VER 9 /* P2P version: 9=WiFi P2P v1.0 */ #define P2P_PUB_AF_CATEGORY 0x04 #define P2P_PUB_AF_ACTION 0x09 #define P2P_AF_CATEGORY 0x7f #define P2P_OUI "\x50\x6F\x9A" /* P2P OUI */ #define P2P_OUI_LEN 3 /* P2P OUI length */ /* Action Frame Constants */ #define DOT11_ACTION_HDR_LEN 2 /* action frame category + action */ #define DOT11_ACTION_CAT_OFF 0 /* category offset */ #define DOT11_ACTION_ACT_OFF 1 /* action offset */ #define P2P_AF_DWELL_TIME 200 #define P2P_AF_MIN_DWELL_TIME 100 #define P2P_AF_MED_DWELL_TIME 400 #define P2P_AF_LONG_DWELL_TIME 1000 #define P2P_AF_TX_MAX_RETRY 5 #define P2P_AF_MAX_WAIT_TIME msecs_to_jiffies(2000) #define P2P_INVALID_CHANNEL -1 #define P2P_CHANNEL_SYNC_RETRY 5 #define P2P_AF_FRM_SCAN_MAX_WAIT msecs_to_jiffies(450) #define P2P_DEFAULT_SLEEP_TIME_VSDB 200 #define P2P_AF_RETRY_DELAY_TIME 40 /* WiFi P2P Public Action Frame OUI Subtypes */ #define P2P_PAF_GON_REQ 0 /* Group Owner Negotiation Req */ #define P2P_PAF_GON_RSP 1 /* Group Owner Negotiation Rsp */ #define P2P_PAF_GON_CONF 2 /* Group Owner Negotiation Confirm */ #define P2P_PAF_INVITE_REQ 3 /* P2P Invitation Request */ #define P2P_PAF_INVITE_RSP 4 /* P2P Invitation Response */ #define P2P_PAF_DEVDIS_REQ 5 /* Device Discoverability Request */ #define P2P_PAF_DEVDIS_RSP 6 /* Device Discoverability Response */ #define P2P_PAF_PROVDIS_REQ 7 /* Provision Discovery Request */ #define P2P_PAF_PROVDIS_RSP 8 /* Provision Discovery Response */ #define P2P_PAF_SUBTYPE_INVALID 255 /* Invalid Subtype */ /* WiFi P2P Action Frame OUI Subtypes */ #define P2P_AF_NOTICE_OF_ABSENCE 0 /* Notice of Absence */ #define P2P_AF_PRESENCE_REQ 1 /* P2P Presence Request */ #define P2P_AF_PRESENCE_RSP 2 /* P2P Presence Response */ #define P2P_AF_GO_DISC_REQ 3 /* GO Discoverability Request */ /* P2P Service Discovery related */ #define P2PSD_ACTION_CATEGORY 0x04 /* Public action frame */ #define P2PSD_ACTION_ID_GAS_IREQ 0x0a /* GAS Initial Request AF */ #define P2PSD_ACTION_ID_GAS_IRESP 0x0b /* GAS Initial Response AF */ #define P2PSD_ACTION_ID_GAS_CREQ 0x0c /* GAS Comeback Request AF */ #define P2PSD_ACTION_ID_GAS_CRESP 0x0d /* GAS Comeback Response AF */ #define BRCMF_P2P_DISABLE_TIMEOUT msecs_to_jiffies(500) /* Mask for retry counter of custom dwell time */ #define CUSTOM_RETRY_MASK 0xff000000 /** * struct brcmf_p2p_disc_st_le - set discovery state in firmware. * * @state: requested discovery state (see enum brcmf_p2p_disc_state). * @chspec: channel parameter for %WL_P2P_DISC_ST_LISTEN state. * @dwell: dwell time in ms for %WL_P2P_DISC_ST_LISTEN state. */ struct brcmf_p2p_disc_st_le { u8 state; __le16 chspec; __le16 dwell; }; /** * enum brcmf_p2p_disc_state - P2P discovery state values * * @WL_P2P_DISC_ST_SCAN: P2P discovery with wildcard SSID and P2P IE. * @WL_P2P_DISC_ST_LISTEN: P2P discovery off-channel for specified time. * @WL_P2P_DISC_ST_SEARCH: P2P discovery with P2P wildcard SSID and P2P IE. */ enum brcmf_p2p_disc_state { WL_P2P_DISC_ST_SCAN, WL_P2P_DISC_ST_LISTEN, WL_P2P_DISC_ST_SEARCH }; /** * struct brcmf_p2p_scan_le - P2P specific scan request. * * @type: type of scan method requested (values: 'E' or 'S'). * @reserved: reserved (ignored). * @eparams: parameters used for type 'E'. * @sparams: parameters used for type 'S'. */ struct brcmf_p2p_scan_le { u8 type; u8 reserved[3]; union { struct brcmf_escan_params_le eparams; struct brcmf_scan_params_le sparams; }; }; /** * struct brcmf_p2p_pub_act_frame - WiFi P2P Public Action Frame * * @category: P2P_PUB_AF_CATEGORY * @action: P2P_PUB_AF_ACTION * @oui: P2P_OUI * @oui_type: OUI type - P2P_VER * @subtype: OUI subtype - P2P_TYPE_* * @dialog_token: nonzero, identifies req/rsp transaction * @elts: Variable length information elements. */ struct brcmf_p2p_pub_act_frame { u8 category; u8 action; u8 oui[3]; u8 oui_type; u8 subtype; u8 dialog_token; u8 elts[]; }; /** * struct brcmf_p2p_action_frame - WiFi P2P Action Frame * * @category: P2P_AF_CATEGORY * @oui: OUI - P2P_OUI * @type: OUI Type - P2P_VER * @subtype: OUI Subtype - P2P_AF_* * @dialog_token: nonzero, identifies req/resp tranaction * @elts: Variable length information elements. */ struct brcmf_p2p_action_frame { u8 category; u8 oui[3]; u8 type; u8 subtype; u8 dialog_token; u8 elts[]; }; /** * struct brcmf_p2psd_gas_pub_act_frame - Wi-Fi GAS Public Action Frame * * @category: 0x04 Public Action Frame * @action: 0x6c Advertisement Protocol * @dialog_token: nonzero, identifies req/rsp transaction * @query_data: Query Data. SD gas ireq SD gas iresp */ struct brcmf_p2psd_gas_pub_act_frame { u8 category; u8 action; u8 dialog_token; u8 query_data[]; }; /** * struct brcmf_config_af_params - Action Frame Parameters for tx. * * @mpc_onoff: To make sure to send successfully action frame, we have to * turn off mpc 0: off, 1: on, (-1): do nothing * @search_channel: 1: search peer's channel to send af * @extra_listen: keep the dwell time to get af response frame. */ struct brcmf_config_af_params { s32 mpc_onoff; bool search_channel; bool extra_listen; }; /** * brcmf_p2p_is_pub_action() - true if p2p public type frame. * * @frame: action frame data. * @frame_len: length of action frame data. * * Determine if action frame is p2p public action type */ static bool brcmf_p2p_is_pub_action(void *frame, u32 frame_len) { struct brcmf_p2p_pub_act_frame *pact_frm; if (frame == NULL) return false; pact_frm = (struct brcmf_p2p_pub_act_frame *)frame; if (frame_len < sizeof(*pact_frm)) return false; if (pact_frm->category == P2P_PUB_AF_CATEGORY && pact_frm->action == P2P_PUB_AF_ACTION && pact_frm->oui_type == P2P_VER && memcmp(pact_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) return true; return false; } /** * brcmf_p2p_is_p2p_action() - true if p2p action type frame. * * @frame: action frame data. * @frame_len: length of action frame data. * * Determine if action frame is p2p action type */ static bool brcmf_p2p_is_p2p_action(void *frame, u32 frame_len) { struct brcmf_p2p_action_frame *act_frm; if (frame == NULL) return false; act_frm = (struct brcmf_p2p_action_frame *)frame; if (frame_len < sizeof(*act_frm)) return false; if (act_frm->category == P2P_AF_CATEGORY && act_frm->type == P2P_VER && memcmp(act_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) return true; return false; } /** * brcmf_p2p_is_gas_action() - true if p2p gas action type frame. * * @frame: action frame data. * @frame_len: length of action frame data. * * Determine if action frame is p2p gas action type */ static bool brcmf_p2p_is_gas_action(void *frame, u32 frame_len) { struct brcmf_p2psd_gas_pub_act_frame *sd_act_frm; if (frame == NULL) return false; sd_act_frm = (struct brcmf_p2psd_gas_pub_act_frame *)frame; if (frame_len < sizeof(*sd_act_frm)) return false; if (sd_act_frm->category != P2PSD_ACTION_CATEGORY) return false; if (sd_act_frm->action == P2PSD_ACTION_ID_GAS_IREQ || sd_act_frm->action == P2PSD_ACTION_ID_GAS_IRESP || sd_act_frm->action == P2PSD_ACTION_ID_GAS_CREQ || sd_act_frm->action == P2PSD_ACTION_ID_GAS_CRESP) return true; return false; } /** * brcmf_p2p_print_actframe() - debug print routine. * * @tx: Received or to be transmitted * @frame: action frame data. * @frame_len: length of action frame data. * * Print information about the p2p action frame */ #ifdef DEBUG static void brcmf_p2p_print_actframe(bool tx, void *frame, u32 frame_len) { struct brcmf_p2p_pub_act_frame *pact_frm; struct brcmf_p2p_action_frame *act_frm; struct brcmf_p2psd_gas_pub_act_frame *sd_act_frm; if (!frame || frame_len <= 2) return; if (brcmf_p2p_is_pub_action(frame, frame_len)) { pact_frm = (struct brcmf_p2p_pub_act_frame *)frame; switch (pact_frm->subtype) { case P2P_PAF_GON_REQ: brcmf_dbg(TRACE, "%s P2P Group Owner Negotiation Req Frame\n", (tx) ? "TX" : "RX"); break; case P2P_PAF_GON_RSP: brcmf_dbg(TRACE, "%s P2P Group Owner Negotiation Rsp Frame\n", (tx) ? "TX" : "RX"); break; case P2P_PAF_GON_CONF: brcmf_dbg(TRACE, "%s P2P Group Owner Negotiation Confirm Frame\n", (tx) ? "TX" : "RX"); break; case P2P_PAF_INVITE_REQ: brcmf_dbg(TRACE, "%s P2P Invitation Request Frame\n", (tx) ? "TX" : "RX"); break; case P2P_PAF_INVITE_RSP: brcmf_dbg(TRACE, "%s P2P Invitation Response Frame\n", (tx) ? "TX" : "RX"); break; case P2P_PAF_DEVDIS_REQ: brcmf_dbg(TRACE, "%s P2P Device Discoverability Request Frame\n", (tx) ? "TX" : "RX"); break; case P2P_PAF_DEVDIS_RSP: brcmf_dbg(TRACE, "%s P2P Device Discoverability Response Frame\n", (tx) ? "TX" : "RX"); break; case P2P_PAF_PROVDIS_REQ: brcmf_dbg(TRACE, "%s P2P Provision Discovery Request Frame\n", (tx) ? "TX" : "RX"); break; case P2P_PAF_PROVDIS_RSP: brcmf_dbg(TRACE, "%s P2P Provision Discovery Response Frame\n", (tx) ? "TX" : "RX"); break; default: brcmf_dbg(TRACE, "%s Unknown P2P Public Action Frame\n", (tx) ? "TX" : "RX"); break; } } else if (brcmf_p2p_is_p2p_action(frame, frame_len)) { act_frm = (struct brcmf_p2p_action_frame *)frame; switch (act_frm->subtype) { case P2P_AF_NOTICE_OF_ABSENCE: brcmf_dbg(TRACE, "%s P2P Notice of Absence Frame\n", (tx) ? "TX" : "RX"); break; case P2P_AF_PRESENCE_REQ: brcmf_dbg(TRACE, "%s P2P Presence Request Frame\n", (tx) ? "TX" : "RX"); break; case P2P_AF_PRESENCE_RSP: brcmf_dbg(TRACE, "%s P2P Presence Response Frame\n", (tx) ? "TX" : "RX"); break; case P2P_AF_GO_DISC_REQ: brcmf_dbg(TRACE, "%s P2P Discoverability Request Frame\n", (tx) ? "TX" : "RX"); break; default: brcmf_dbg(TRACE, "%s Unknown P2P Action Frame\n", (tx) ? "TX" : "RX"); } } else if (brcmf_p2p_is_gas_action(frame, frame_len)) { sd_act_frm = (struct brcmf_p2psd_gas_pub_act_frame *)frame; switch (sd_act_frm->action) { case P2PSD_ACTION_ID_GAS_IREQ: brcmf_dbg(TRACE, "%s P2P GAS Initial Request\n", (tx) ? "TX" : "RX"); break; case P2PSD_ACTION_ID_GAS_IRESP: brcmf_dbg(TRACE, "%s P2P GAS Initial Response\n", (tx) ? "TX" : "RX"); break; case P2PSD_ACTION_ID_GAS_CREQ: brcmf_dbg(TRACE, "%s P2P GAS Comeback Request\n", (tx) ? "TX" : "RX"); break; case P2PSD_ACTION_ID_GAS_CRESP: brcmf_dbg(TRACE, "%s P2P GAS Comeback Response\n", (tx) ? "TX" : "RX"); break; default: brcmf_dbg(TRACE, "%s Unknown P2P GAS Frame\n", (tx) ? "TX" : "RX"); break; } } } #else static void brcmf_p2p_print_actframe(bool tx, void *frame, u32 frame_len) { } #endif /** * brcmf_p2p_set_firmware() - prepare firmware for peer-to-peer operation. * * @ifp: ifp to use for iovars (primary). * @p2p_mac: mac address to configure for p2p_da_override */ static int brcmf_p2p_set_firmware(struct brcmf_if *ifp, u8 *p2p_mac) { struct brcmf_pub *drvr = ifp->drvr; s32 ret = 0; brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); brcmf_fil_iovar_int_set(ifp, "apsta", 1); brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); /* In case of COB type, firmware has default mac address * After Initializing firmware, we have to set current mac address to * firmware for P2P device address. This must be done with discovery * disabled. */ brcmf_fil_iovar_int_set(ifp, "p2p_disc", 0); ret = brcmf_fil_iovar_data_set(ifp, "p2p_da_override", p2p_mac, ETH_ALEN); if (ret) bphy_err(drvr, "failed to update device address ret %d\n", ret); return ret; } /** * brcmf_p2p_generate_bss_mac() - derive mac addresses for P2P. * * @p2p: P2P specific data. * @dev_addr: optional device address. * * P2P needs mac addresses for P2P device and interface. If no device * address it specified, these are derived from a random ethernet * address. */ static void brcmf_p2p_generate_bss_mac(struct brcmf_p2p_info *p2p, u8 *dev_addr) { struct brcmf_if *pri_ifp = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; bool random_addr = false; bool local_admin = false; if (!dev_addr || is_zero_ether_addr(dev_addr)) { /* If the primary interface address is already locally * administered, create a new random address. */ if (pri_ifp->mac_addr[0] & 0x02) { random_addr = true; } else { dev_addr = pri_ifp->mac_addr; local_admin = true; } } /* Generate the P2P Device Address obtaining a random ethernet * address with the locally administered bit set. */ if (random_addr) eth_random_addr(p2p->dev_addr); else memcpy(p2p->dev_addr, dev_addr, ETH_ALEN); if (local_admin) p2p->dev_addr[0] |= 0x02; /* Generate the P2P Interface Address. If the discovery and connection * BSSCFGs need to simultaneously co-exist, then this address must be * different from the P2P Device Address, but also locally administered. */ memcpy(p2p->conn_int_addr, p2p->dev_addr, ETH_ALEN); p2p->conn_int_addr[0] |= 0x02; p2p->conn_int_addr[4] ^= 0x80; memcpy(p2p->conn2_int_addr, p2p->dev_addr, ETH_ALEN); p2p->conn2_int_addr[0] |= 0x02; p2p->conn2_int_addr[4] ^= 0x90; } /** * brcmf_p2p_scan_is_p2p_request() - is cfg80211 scan request a P2P scan. * * @request: the scan request as received from cfg80211. * * returns true if one of the ssids in the request matches the * P2P wildcard ssid; otherwise returns false. */ static bool brcmf_p2p_scan_is_p2p_request(struct cfg80211_scan_request *request) { struct cfg80211_ssid *ssids = request->ssids; int i; for (i = 0; i < request->n_ssids; i++) { if (ssids[i].ssid_len != BRCMF_P2P_WILDCARD_SSID_LEN) continue; brcmf_dbg(INFO, "comparing ssid \"%s\"", ssids[i].ssid); if (!memcmp(BRCMF_P2P_WILDCARD_SSID, ssids[i].ssid, BRCMF_P2P_WILDCARD_SSID_LEN)) return true; } return false; } /** * brcmf_p2p_set_discover_state - set discover state in firmware. * * @ifp: low-level interface object. * @state: discover state to set. * @chanspec: channel parameters (for state @WL_P2P_DISC_ST_LISTEN only). * @listen_ms: duration to listen (for state @WL_P2P_DISC_ST_LISTEN only). */ static s32 brcmf_p2p_set_discover_state(struct brcmf_if *ifp, u8 state, u16 chanspec, u16 listen_ms) { struct brcmf_p2p_disc_st_le discover_state; s32 ret = 0; brcmf_dbg(TRACE, "enter\n"); discover_state.state = state; discover_state.chspec = cpu_to_le16(chanspec); discover_state.dwell = cpu_to_le16(listen_ms); ret = brcmf_fil_bsscfg_data_set(ifp, "p2p_state", &discover_state, sizeof(discover_state)); return ret; } /** * brcmf_p2p_deinit_discovery() - disable P2P device discovery. * * @p2p: P2P specific data. * * Resets the discovery state and disables it in firmware. */ static s32 brcmf_p2p_deinit_discovery(struct brcmf_p2p_info *p2p) { struct brcmf_cfg80211_vif *vif; brcmf_dbg(TRACE, "enter\n"); /* Set the discovery state to SCAN */ vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; (void)brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_SCAN, 0, 0); /* Disable P2P discovery in the firmware */ vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; (void)brcmf_fil_iovar_int_set(vif->ifp, "p2p_disc", 0); return 0; } /** * brcmf_p2p_enable_discovery() - initialize and configure discovery. * * @p2p: P2P specific data. * * Initializes the discovery device and configure the virtual interface. */ static int brcmf_p2p_enable_discovery(struct brcmf_p2p_info *p2p) { struct brcmf_pub *drvr = p2p->cfg->pub; struct brcmf_cfg80211_vif *vif; s32 ret = 0; brcmf_dbg(TRACE, "enter\n"); vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; if (!vif) { bphy_err(drvr, "P2P config device not available\n"); ret = -EPERM; goto exit; } if (test_bit(BRCMF_P2P_STATUS_ENABLED, &p2p->status)) { brcmf_dbg(INFO, "P2P config device already configured\n"); goto exit; } /* Re-initialize P2P Discovery in the firmware */ vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; ret = brcmf_fil_iovar_int_set(vif->ifp, "p2p_disc", 1); if (ret < 0) { bphy_err(drvr, "set p2p_disc error\n"); goto exit; } vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; ret = brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_SCAN, 0, 0); if (ret < 0) { bphy_err(drvr, "unable to set WL_P2P_DISC_ST_SCAN\n"); goto exit; } /* * Set wsec to any non-zero value in the discovery bsscfg * to ensure our P2P probe responses have the privacy bit * set in the 802.11 WPA IE. Some peer devices may not * initiate WPS with us if this bit is not set. */ ret = brcmf_fil_bsscfg_int_set(vif->ifp, "wsec", AES_ENABLED); if (ret < 0) { bphy_err(drvr, "wsec error %d\n", ret); goto exit; } set_bit(BRCMF_P2P_STATUS_ENABLED, &p2p->status); exit: return ret; } /** * brcmf_p2p_escan() - initiate a P2P scan. * * @p2p: P2P specific data. * @num_chans: number of channels to scan. * @chanspecs: channel parameters for @num_chans channels. * @search_state: P2P discover state to use. * @bss_type: type of P2P bss. */ static s32 brcmf_p2p_escan(struct brcmf_p2p_info *p2p, u32 num_chans, u16 chanspecs[], s32 search_state, enum p2p_bss_type bss_type) { struct brcmf_pub *drvr = p2p->cfg->pub; s32 ret = 0; s32 memsize = offsetof(struct brcmf_p2p_scan_le, eparams.params_le.channel_list); s32 nprobes; s32 active; u32 i; u8 *memblk; struct brcmf_cfg80211_vif *vif; struct brcmf_p2p_scan_le *p2p_params; struct brcmf_scan_params_le *sparams; memsize += num_chans * sizeof(__le16); memblk = kzalloc(memsize, GFP_KERNEL); if (!memblk) return -ENOMEM; vif = p2p->bss_idx[bss_type].vif; if (vif == NULL) { bphy_err(drvr, "no vif for bss type %d\n", bss_type); ret = -EINVAL; goto exit; } p2p_params = (struct brcmf_p2p_scan_le *)memblk; sparams = &p2p_params->eparams.params_le; switch (search_state) { case WL_P2P_DISC_ST_SEARCH: /* * If we in SEARCH STATE, we don't need to set SSID explictly * because dongle use P2P WILDCARD internally by default, use * null ssid, which it is already due to kzalloc. */ break; case WL_P2P_DISC_ST_SCAN: /* * wpa_supplicant has p2p_find command with type social or * progressive. For progressive, we need to set the ssid to * P2P WILDCARD because we just do broadcast scan unless * setting SSID. */ sparams->ssid_le.SSID_len = cpu_to_le32(BRCMF_P2P_WILDCARD_SSID_LEN); memcpy(sparams->ssid_le.SSID, BRCMF_P2P_WILDCARD_SSID, BRCMF_P2P_WILDCARD_SSID_LEN); break; default: bphy_err(drvr, " invalid search state %d\n", search_state); ret = -EINVAL; goto exit; } brcmf_p2p_set_discover_state(vif->ifp, search_state, 0, 0); /* * set p2p scan parameters. */ p2p_params->type = 'E'; /* determine the scan engine parameters */ sparams->bss_type = DOT11_BSSTYPE_ANY; sparams->scan_type = BRCMF_SCANTYPE_ACTIVE; eth_broadcast_addr(sparams->bssid); sparams->home_time = cpu_to_le32(P2PAPI_SCAN_HOME_TIME_MS); /* * SOCIAL_CHAN_CNT + 1 takes care of the Progressive scan * supported by the supplicant. */ if (num_chans == SOCIAL_CHAN_CNT || num_chans == (SOCIAL_CHAN_CNT + 1)) active = P2PAPI_SCAN_SOCIAL_DWELL_TIME_MS; else if (num_chans == AF_PEER_SEARCH_CNT) active = P2PAPI_SCAN_AF_SEARCH_DWELL_TIME_MS; else if (brcmf_get_vif_state_any(p2p->cfg, BRCMF_VIF_STATUS_CONNECTED)) active = -1; else active = P2PAPI_SCAN_DWELL_TIME_MS; /* Override scan params to find a peer for a connection */ if (num_chans == 1) { active = WL_SCAN_CONNECT_DWELL_TIME_MS; /* WAR to sync with presence period of VSDB GO. * send probe request more frequently */ nprobes = active / WL_SCAN_JOIN_PROBE_INTERVAL_MS; } else { nprobes = active / P2PAPI_SCAN_NPROBS_TIME_MS; } if (nprobes <= 0) nprobes = 1; brcmf_dbg(INFO, "nprobes # %d, active_time %d\n", nprobes, active); sparams->active_time = cpu_to_le32(active); sparams->nprobes = cpu_to_le32(nprobes); sparams->passive_time = cpu_to_le32(-1); sparams->channel_num = cpu_to_le32(num_chans & BRCMF_SCAN_PARAMS_COUNT_MASK); for (i = 0; i < num_chans; i++) sparams->channel_list[i] = cpu_to_le16(chanspecs[i]); /* set the escan specific parameters */ p2p_params->eparams.version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION); p2p_params->eparams.action = cpu_to_le16(WL_ESCAN_ACTION_START); p2p_params->eparams.sync_id = cpu_to_le16(0x1234); /* perform p2p scan on primary device */ ret = brcmf_fil_bsscfg_data_set(vif->ifp, "p2p_scan", memblk, memsize); if (!ret) set_bit(BRCMF_SCAN_STATUS_BUSY, &p2p->cfg->scan_status); exit: kfree(memblk); return ret; } /** * brcmf_p2p_run_escan() - escan callback for peer-to-peer. * * @cfg: driver private data for cfg80211 interface. * @ifp: interface control. * @request: scan request from cfg80211. * * Determines the P2P discovery state based to scan request parameters and * validates the channels in the request. */ static s32 brcmf_p2p_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, struct cfg80211_scan_request *request) { struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_pub *drvr = cfg->pub; s32 err = 0; s32 search_state = WL_P2P_DISC_ST_SCAN; struct brcmf_cfg80211_vif *vif; struct net_device *dev = NULL; int i, num_nodfs = 0; u16 *chanspecs; brcmf_dbg(TRACE, "enter\n"); if (!request) { err = -EINVAL; goto exit; } if (request->n_channels) { chanspecs = kcalloc(request->n_channels, sizeof(*chanspecs), GFP_KERNEL); if (!chanspecs) { err = -ENOMEM; goto exit; } vif = p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif; if (vif) dev = vif->wdev.netdev; if (request->n_channels == 3 && request->channels[0]->hw_value == SOCIAL_CHAN_1 && request->channels[1]->hw_value == SOCIAL_CHAN_2 && request->channels[2]->hw_value == SOCIAL_CHAN_3) { /* SOCIAL CHANNELS 1, 6, 11 */ search_state = WL_P2P_DISC_ST_SEARCH; brcmf_dbg(INFO, "P2P SEARCH PHASE START\n"); } else if (dev != NULL && vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) { /* If you are already a GO, then do SEARCH only */ brcmf_dbg(INFO, "Already a GO. Do SEARCH Only\n"); search_state = WL_P2P_DISC_ST_SEARCH; } else { brcmf_dbg(INFO, "P2P SCAN STATE START\n"); } /* * no P2P scanning on passive or DFS channels. */ for (i = 0; i < request->n_channels; i++) { struct ieee80211_channel *chan = request->channels[i]; if (chan->flags & (IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IR)) continue; chanspecs[i] = channel_to_chanspec(&p2p->cfg->d11inf, chan); brcmf_dbg(INFO, "%d: chan=%d, channel spec=%x\n", num_nodfs, chan->hw_value, chanspecs[i]); num_nodfs++; } err = brcmf_p2p_escan(p2p, num_nodfs, chanspecs, search_state, P2PAPI_BSSCFG_DEVICE); kfree(chanspecs); } exit: if (err) bphy_err(drvr, "error (%d)\n", err); return err; } /** * brcmf_p2p_find_listen_channel() - find listen channel in ie string. * * @ie: string of information elements. * @ie_len: length of string. * * Scan ie for p2p ie and look for attribute 6 channel. If available determine * channel and return it. */ static s32 brcmf_p2p_find_listen_channel(const u8 *ie, u32 ie_len) { u8 channel_ie[5]; s32 listen_channel; s32 err; err = cfg80211_get_p2p_attr(ie, ie_len, IEEE80211_P2P_ATTR_LISTEN_CHANNEL, channel_ie, sizeof(channel_ie)); if (err < 0) return err; /* listen channel subel length format: */ /* 3(country) + 1(op. class) + 1(chan num) */ listen_channel = (s32)channel_ie[3 + 1]; if (listen_channel == SOCIAL_CHAN_1 || listen_channel == SOCIAL_CHAN_2 || listen_channel == SOCIAL_CHAN_3) { brcmf_dbg(INFO, "Found my Listen Channel %d\n", listen_channel); return listen_channel; } return -EPERM; } /** * brcmf_p2p_scan_prep() - prepare scan based on request. * * @wiphy: wiphy device. * @request: scan request from cfg80211. * @vif: vif on which scan request is to be executed. * * Prepare the scan appropriately for type of scan requested. Overrides the * escan .run() callback for peer-to-peer scanning. */ int brcmf_p2p_scan_prep(struct wiphy *wiphy, struct cfg80211_scan_request *request, struct brcmf_cfg80211_vif *vif) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_p2p_info *p2p = &cfg->p2p; int err; if (brcmf_p2p_scan_is_p2p_request(request)) { /* find my listen channel */ err = brcmf_p2p_find_listen_channel(request->ie, request->ie_len); if (err < 0) return err; p2p->afx_hdl.my_listen_chan = err; clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); brcmf_dbg(INFO, "P2P: GO_NEG_PHASE status cleared\n"); err = brcmf_p2p_enable_discovery(p2p); if (err) return err; /* override .run_escan() callback. */ cfg->escan_info.run = brcmf_p2p_run_escan; } return 0; } /** * brcmf_p2p_discover_listen() - set firmware to discover listen state. * * @p2p: p2p device. * @channel: channel nr for discover listen. * @duration: time in ms to stay on channel. * */ static s32 brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 channel, u32 duration) { struct brcmf_pub *drvr = p2p->cfg->pub; struct brcmf_cfg80211_vif *vif; struct brcmu_chan ch; s32 err = 0; vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; if (!vif) { bphy_err(drvr, "Discovery is not set, so we have nothing to do\n"); err = -EPERM; goto exit; } if (test_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status)) { bphy_err(drvr, "Previous LISTEN is not completed yet\n"); /* WAR: prevent cookie mismatch in wpa_supplicant return OK */ goto exit; } ch.chnum = channel; ch.bw = BRCMU_CHAN_BW_20; p2p->cfg->d11inf.encchspec(&ch); err = brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_LISTEN, ch.chspec, (u16)duration); if (!err) { set_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status); p2p->remain_on_channel_cookie++; } exit: return err; } /** * brcmf_p2p_remain_on_channel() - put device on channel and stay there. * * @wiphy: wiphy device. * @wdev: wireless device. * @channel: channel to stay on. * @duration: time in ms to remain on channel. * @cookie: cookie. */ int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, unsigned int duration, u64 *cookie) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_p2p_info *p2p = &cfg->p2p; s32 err; u16 channel_nr; channel_nr = ieee80211_frequency_to_channel(channel->center_freq); brcmf_dbg(TRACE, "Enter, channel: %d, duration ms (%d)\n", channel_nr, duration); err = brcmf_p2p_enable_discovery(p2p); if (err) goto exit; err = brcmf_p2p_discover_listen(p2p, channel_nr, duration); if (err) goto exit; memcpy(&p2p->remain_on_channel, channel, sizeof(*channel)); *cookie = p2p->remain_on_channel_cookie; cfg80211_ready_on_channel(wdev, *cookie, channel, duration, GFP_KERNEL); exit: return err; } /** * brcmf_p2p_notify_listen_complete() - p2p listen has completed. * * @ifp: interfac control. * @e: event message. Not used, to make it usable for fweh event dispatcher. * @data: payload of message. Not used. * */ int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; struct brcmf_p2p_info *p2p = &cfg->p2p; brcmf_dbg(TRACE, "Enter\n"); if (test_and_clear_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status)) { if (test_and_clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, &p2p->status)) { clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status); brcmf_dbg(INFO, "Listen DONE, wake up wait_next_af\n"); complete(&p2p->wait_next_af); } cfg80211_remain_on_channel_expired(&ifp->vif->wdev, p2p->remain_on_channel_cookie, &p2p->remain_on_channel, GFP_KERNEL); } return 0; } /** * brcmf_p2p_cancel_remain_on_channel() - cancel p2p listen state. * * @ifp: interfac control. * */ void brcmf_p2p_cancel_remain_on_channel(struct brcmf_if *ifp) { if (!ifp) return; brcmf_p2p_set_discover_state(ifp, WL_P2P_DISC_ST_SCAN, 0, 0); brcmf_p2p_notify_listen_complete(ifp, NULL, NULL); } /** * brcmf_p2p_act_frm_search() - search function for action frame. * * @p2p: p2p device. * @channel: channel on which action frame is to be trasmitted. * * search function to reach at common channel to send action frame. When * channel is 0 then all social channels will be used to send af */ static s32 brcmf_p2p_act_frm_search(struct brcmf_p2p_info *p2p, u16 channel) { struct brcmf_pub *drvr = p2p->cfg->pub; s32 err; u32 channel_cnt; u16 *default_chan_list; u32 i; struct brcmu_chan ch; brcmf_dbg(TRACE, "Enter\n"); if (channel) channel_cnt = AF_PEER_SEARCH_CNT; else channel_cnt = SOCIAL_CHAN_CNT; default_chan_list = kcalloc(channel_cnt, sizeof(*default_chan_list), GFP_KERNEL); if (default_chan_list == NULL) { bphy_err(drvr, "channel list allocation failed\n"); err = -ENOMEM; goto exit; } ch.bw = BRCMU_CHAN_BW_20; if (channel) { ch.chnum = channel; p2p->cfg->d11inf.encchspec(&ch); /* insert same channel to the chan_list */ for (i = 0; i < channel_cnt; i++) default_chan_list[i] = ch.chspec; } else { ch.chnum = SOCIAL_CHAN_1; p2p->cfg->d11inf.encchspec(&ch); default_chan_list[0] = ch.chspec; ch.chnum = SOCIAL_CHAN_2; p2p->cfg->d11inf.encchspec(&ch); default_chan_list[1] = ch.chspec; ch.chnum = SOCIAL_CHAN_3; p2p->cfg->d11inf.encchspec(&ch); default_chan_list[2] = ch.chspec; } err = brcmf_p2p_escan(p2p, channel_cnt, default_chan_list, WL_P2P_DISC_ST_SEARCH, P2PAPI_BSSCFG_DEVICE); kfree(default_chan_list); exit: return err; } /** * brcmf_p2p_afx_handler() - afx worker thread. * * @work: * */ static void brcmf_p2p_afx_handler(struct work_struct *work) { struct afx_hdl *afx_hdl = container_of(work, struct afx_hdl, afx_work); struct brcmf_p2p_info *p2p = container_of(afx_hdl, struct brcmf_p2p_info, afx_hdl); struct brcmf_pub *drvr = p2p->cfg->pub; s32 err; if (!afx_hdl->is_active) return; if (afx_hdl->is_listen && afx_hdl->my_listen_chan) /* 100ms ~ 300ms */ err = brcmf_p2p_discover_listen(p2p, afx_hdl->my_listen_chan, 100 * get_random_u32_inclusive(1, 3)); else err = brcmf_p2p_act_frm_search(p2p, afx_hdl->peer_listen_chan); if (err) { bphy_err(drvr, "ERROR occurred! value is (%d)\n", err); if (test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status)) complete(&afx_hdl->act_frm_scan); } } /** * brcmf_p2p_af_searching_channel() - search channel. * * @p2p: p2p device info struct. * */ static s32 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) { struct afx_hdl *afx_hdl = &p2p->afx_hdl; struct brcmf_cfg80211_vif *pri_vif; s32 retry; brcmf_dbg(TRACE, "Enter\n"); pri_vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; reinit_completion(&afx_hdl->act_frm_scan); set_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status); afx_hdl->is_active = true; afx_hdl->peer_chan = P2P_INVALID_CHANNEL; /* Loop to wait until we find a peer's channel or the * pending action frame tx is cancelled. */ retry = 0; while ((retry < P2P_CHANNEL_SYNC_RETRY) && (afx_hdl->peer_chan == P2P_INVALID_CHANNEL)) { afx_hdl->is_listen = false; brcmf_dbg(TRACE, "Scheduling action frame for sending.. (%d)\n", retry); /* search peer on peer's listen channel */ schedule_work(&afx_hdl->afx_work); wait_for_completion_timeout(&afx_hdl->act_frm_scan, P2P_AF_FRM_SCAN_MAX_WAIT); if ((afx_hdl->peer_chan != P2P_INVALID_CHANNEL) || (!test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status))) break; if (afx_hdl->my_listen_chan) { brcmf_dbg(TRACE, "Scheduling listen peer, channel=%d\n", afx_hdl->my_listen_chan); /* listen on my listen channel */ afx_hdl->is_listen = true; schedule_work(&afx_hdl->afx_work); wait_for_completion_timeout(&afx_hdl->act_frm_scan, P2P_AF_FRM_SCAN_MAX_WAIT); } if ((afx_hdl->peer_chan != P2P_INVALID_CHANNEL) || (!test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status))) break; retry++; /* if sta is connected or connecting, sleep for a while before * retry af tx or finding a peer */ if (test_bit(BRCMF_VIF_STATUS_CONNECTED, &pri_vif->sme_state) || test_bit(BRCMF_VIF_STATUS_CONNECTING, &pri_vif->sme_state)) #if defined(__linux__) msleep(P2P_DEFAULT_SLEEP_TIME_VSDB); #elif defined(__FreeBSD__) linux_msleep(P2P_DEFAULT_SLEEP_TIME_VSDB); #endif } brcmf_dbg(TRACE, "Completed search/listen peer_chan=%d\n", afx_hdl->peer_chan); afx_hdl->is_active = false; clear_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status); return afx_hdl->peer_chan; } /** * brcmf_p2p_scan_finding_common_channel() - was escan used for finding channel * * @cfg: common configuration struct. * @bi: bss info struct, result from scan. * */ bool brcmf_p2p_scan_finding_common_channel(struct brcmf_cfg80211_info *cfg, struct brcmf_bss_info_le *bi) { struct brcmf_p2p_info *p2p = &cfg->p2p; struct afx_hdl *afx_hdl = &p2p->afx_hdl; struct brcmu_chan ch; u8 *ie; s32 err; u8 p2p_dev_addr[ETH_ALEN]; if (!test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status)) return false; if (bi == NULL) { brcmf_dbg(TRACE, "ACTION FRAME SCAN Done\n"); if (afx_hdl->peer_chan == P2P_INVALID_CHANNEL) complete(&afx_hdl->act_frm_scan); return true; } ie = ((u8 *)bi) + le16_to_cpu(bi->ie_offset); memset(p2p_dev_addr, 0, sizeof(p2p_dev_addr)); err = cfg80211_get_p2p_attr(ie, le32_to_cpu(bi->ie_length), IEEE80211_P2P_ATTR_DEVICE_INFO, p2p_dev_addr, sizeof(p2p_dev_addr)); if (err < 0) err = cfg80211_get_p2p_attr(ie, le32_to_cpu(bi->ie_length), IEEE80211_P2P_ATTR_DEVICE_ID, p2p_dev_addr, sizeof(p2p_dev_addr)); if ((err >= 0) && (ether_addr_equal(p2p_dev_addr, afx_hdl->tx_dst_addr))) { if (!bi->ctl_ch) { ch.chspec = le16_to_cpu(bi->chanspec); cfg->d11inf.decchspec(&ch); bi->ctl_ch = ch.control_ch_num; } afx_hdl->peer_chan = bi->ctl_ch; brcmf_dbg(TRACE, "ACTION FRAME SCAN : Peer %pM found, channel : %d\n", afx_hdl->tx_dst_addr, afx_hdl->peer_chan); complete(&afx_hdl->act_frm_scan); } return true; } /** * brcmf_p2p_abort_action_frame() - abort action frame. * * @cfg: common configuration struct. * */ static s32 brcmf_p2p_abort_action_frame(struct brcmf_cfg80211_info *cfg) { struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_cfg80211_vif *vif; s32 err; s32 int_val = 1; brcmf_dbg(TRACE, "Enter\n"); vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe_abort", &int_val, sizeof(s32)); if (err) brcmf_err(" aborting action frame has failed (%d)\n", err); return err; } /** * brcmf_p2p_stop_wait_next_action_frame() - finish scan if af tx complete. * * @cfg: common configuration struct. * */ static void brcmf_p2p_stop_wait_next_action_frame(struct brcmf_cfg80211_info *cfg) { struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_if *ifp = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; s32 err; if (test_bit(BRCMF_P2P_STATUS_SENDING_ACT_FRAME, &p2p->status) && (test_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status) || test_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status))) { brcmf_dbg(TRACE, "*** Wake UP ** abort actframe iovar\n"); /* if channel is not zero, "actfame" uses off channel scan. * So abort scan for off channel completion. */ if (p2p->af_sent_channel) { /* abort actframe using actframe_abort or abort scan */ err = brcmf_p2p_abort_action_frame(cfg); if (err) brcmf_notify_escan_complete(cfg, ifp, true, true); } } else if (test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, &p2p->status)) { brcmf_dbg(TRACE, "*** Wake UP ** abort listen for next af frame\n"); /* So abort scan to cancel listen */ brcmf_notify_escan_complete(cfg, ifp, true, true); } } /** * brcmf_p2p_gon_req_collision() - Check if go negotiaton collission * * @p2p: p2p device info struct. * @mac: MAC address. * * return true if recevied action frame is to be dropped. */ static bool #if defined(__linux__) brcmf_p2p_gon_req_collision(struct brcmf_p2p_info *p2p, u8 *mac) #elif defined(__FreeBSD__) brcmf_p2p_gon_req_collision(struct brcmf_p2p_info *p2p, const u8 *mac) #endif { struct brcmf_cfg80211_info *cfg = p2p->cfg; struct brcmf_if *ifp; brcmf_dbg(TRACE, "Enter\n"); if (!test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status) || !p2p->gon_req_action) return false; brcmf_dbg(TRACE, "GO Negotiation Request COLLISION !!!\n"); /* if sa(peer) addr is less than da(my) addr, then this device * process peer's gon request and block to send gon req. * if not (sa addr > da addr), * this device will process gon request and drop gon req of peer. */ ifp = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->ifp; if (memcmp(mac, ifp->mac_addr, ETH_ALEN) < 0) { brcmf_dbg(INFO, "Block transmit gon req !!!\n"); p2p->block_gon_req_tx = true; /* if we are finding a common channel for sending af, * do not scan more to block to send current gon req */ if (test_and_clear_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status)) complete(&p2p->afx_hdl.act_frm_scan); if (test_and_clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status)) brcmf_p2p_stop_wait_next_action_frame(cfg); return false; } /* drop gon request of peer to process gon request by this device. */ brcmf_dbg(INFO, "Drop received gon req !!!\n"); return true; } /** * brcmf_p2p_notify_action_frame_rx() - received action frame. * * @ifp: interfac control. * @e: event message. Not used, to make it usable for fweh event dispatcher. * @data: payload of message, containing action frame data. * */ int brcmf_p2p_notify_action_frame_rx(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_info *cfg = drvr->config; struct brcmf_p2p_info *p2p = &cfg->p2p; struct afx_hdl *afx_hdl = &p2p->afx_hdl; struct wireless_dev *wdev; u32 mgmt_frame_len = e->datalen - sizeof(struct brcmf_rx_mgmt_data); struct brcmf_rx_mgmt_data *rxframe = (struct brcmf_rx_mgmt_data *)data; u8 *frame = (u8 *)(rxframe + 1); struct brcmf_p2p_pub_act_frame *act_frm; struct brcmf_p2psd_gas_pub_act_frame *sd_act_frm; struct brcmu_chan ch; struct ieee80211_mgmt *mgmt_frame; s32 freq; u16 mgmt_type; u8 action; if (e->datalen < sizeof(*rxframe)) { brcmf_dbg(SCAN, "Event data too small. Ignore\n"); return 0; } ch.chspec = be16_to_cpu(rxframe->chanspec); cfg->d11inf.decchspec(&ch); /* Check if wpa_supplicant has registered for this frame */ brcmf_dbg(INFO, "ifp->vif->mgmt_rx_reg %04x\n", ifp->vif->mgmt_rx_reg); mgmt_type = (IEEE80211_STYPE_ACTION & IEEE80211_FCTL_STYPE) >> 4; if ((ifp->vif->mgmt_rx_reg & BIT(mgmt_type)) == 0) return 0; brcmf_p2p_print_actframe(false, frame, mgmt_frame_len); action = P2P_PAF_SUBTYPE_INVALID; if (brcmf_p2p_is_pub_action(frame, mgmt_frame_len)) { act_frm = (struct brcmf_p2p_pub_act_frame *)frame; action = act_frm->subtype; if ((action == P2P_PAF_GON_REQ) && #if defined(__linux__) (brcmf_p2p_gon_req_collision(p2p, (u8 *)e->addr))) { #elif defined(__FreeBSD__) (brcmf_p2p_gon_req_collision(p2p, e->addr))) { #endif if (test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status) && (ether_addr_equal(afx_hdl->tx_dst_addr, e->addr))) { afx_hdl->peer_chan = ch.control_ch_num; brcmf_dbg(INFO, "GON request: Peer found, channel=%d\n", afx_hdl->peer_chan); complete(&afx_hdl->act_frm_scan); } return 0; } /* After complete GO Negotiation, roll back to mpc mode */ if ((action == P2P_PAF_GON_CONF) || (action == P2P_PAF_PROVDIS_RSP)) brcmf_set_mpc(ifp, 1); if (action == P2P_PAF_GON_CONF) { brcmf_dbg(TRACE, "P2P: GO_NEG_PHASE status cleared\n"); clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); } } else if (brcmf_p2p_is_gas_action(frame, mgmt_frame_len)) { sd_act_frm = (struct brcmf_p2psd_gas_pub_act_frame *)frame; action = sd_act_frm->action; } if (test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status) && (p2p->next_af_subtype == action)) { brcmf_dbg(TRACE, "We got a right next frame! (%d)\n", action); clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status); /* Stop waiting for next AF. */ brcmf_p2p_stop_wait_next_action_frame(cfg); } mgmt_frame = kzalloc(offsetof(struct ieee80211_mgmt, u) + mgmt_frame_len, GFP_KERNEL); if (!mgmt_frame) { bphy_err(drvr, "No memory available for action frame\n"); return -ENOMEM; } memcpy(mgmt_frame->da, ifp->mac_addr, ETH_ALEN); brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSSID, mgmt_frame->bssid, ETH_ALEN); memcpy(mgmt_frame->sa, e->addr, ETH_ALEN); mgmt_frame->frame_control = cpu_to_le16(IEEE80211_STYPE_ACTION); memcpy(mgmt_frame->u.body, frame, mgmt_frame_len); mgmt_frame_len += offsetof(struct ieee80211_mgmt, u.body); freq = ieee80211_channel_to_frequency(ch.control_ch_num, ch.band == BRCMU_CHAN_BAND_2G ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); wdev = &ifp->vif->wdev; cfg80211_rx_mgmt(wdev, freq, 0, (u8 *)mgmt_frame, mgmt_frame_len, 0); kfree(mgmt_frame); return 0; } /** * brcmf_p2p_notify_action_tx_complete() - transmit action frame complete * * @ifp: interfac control. * @e: event message. Not used, to make it usable for fweh event dispatcher. * @data: not used. * */ int brcmf_p2p_notify_action_tx_complete(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; struct brcmf_p2p_info *p2p = &cfg->p2p; brcmf_dbg(INFO, "Enter: event %s, status=%d\n", e->event_code == BRCMF_E_ACTION_FRAME_OFF_CHAN_COMPLETE ? "ACTION_FRAME_OFF_CHAN_COMPLETE" : "ACTION_FRAME_COMPLETE", e->status); if (!test_bit(BRCMF_P2P_STATUS_SENDING_ACT_FRAME, &p2p->status)) return 0; if (e->event_code == BRCMF_E_ACTION_FRAME_COMPLETE) { if (e->status == BRCMF_E_STATUS_SUCCESS) { set_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status); if (!p2p->wait_for_offchan_complete) complete(&p2p->send_af_done); } else { set_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status); /* If there is no ack, we don't need to wait for * WLC_E_ACTION_FRAME_OFFCHAN_COMPLETE event */ brcmf_p2p_stop_wait_next_action_frame(cfg); } } else { complete(&p2p->send_af_done); } return 0; } /** * brcmf_p2p_tx_action_frame() - send action frame over fil. * + * @ifp: interface to transmit on. * @p2p: p2p info struct for vif. * @af_params: action frame data/info. * * Send an action frame immediately without doing channel synchronization. * * This function waits for a completion event before returning. * The WLC_E_ACTION_FRAME_COMPLETE event will be received when the action * frame is transmitted. */ -static s32 brcmf_p2p_tx_action_frame(struct brcmf_p2p_info *p2p, +static s32 brcmf_p2p_tx_action_frame(struct brcmf_if *ifp, + struct brcmf_p2p_info *p2p, struct brcmf_fil_af_params_le *af_params) { struct brcmf_pub *drvr = p2p->cfg->pub; - struct brcmf_cfg80211_vif *vif; - struct brcmf_p2p_action_frame *p2p_af; s32 err = 0; brcmf_dbg(TRACE, "Enter\n"); reinit_completion(&p2p->send_af_done); clear_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status); clear_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status); - /* check if it is a p2p_presence response */ - p2p_af = (struct brcmf_p2p_action_frame *)af_params->action_frame.data; - if (p2p_af->subtype == P2P_AF_PRESENCE_RSP) - vif = p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif; - else - vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; - - err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe", af_params, + err = brcmf_fil_bsscfg_data_set(ifp, "actframe", af_params, sizeof(*af_params)); if (err) { bphy_err(drvr, " sending action frame has failed\n"); goto exit; } p2p->af_sent_channel = le32_to_cpu(af_params->channel); p2p->af_tx_sent_jiffies = jiffies; if (test_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status) && p2p->af_sent_channel == ieee80211_frequency_to_channel(p2p->remain_on_channel.center_freq)) p2p->wait_for_offchan_complete = false; else p2p->wait_for_offchan_complete = true; brcmf_dbg(TRACE, "Waiting for %s tx completion event\n", (p2p->wait_for_offchan_complete) ? "off-channel" : "on-channel"); wait_for_completion_timeout(&p2p->send_af_done, P2P_AF_MAX_WAIT_TIME); if (test_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status)) { brcmf_dbg(TRACE, "TX action frame operation is success\n"); } else { err = -EIO; brcmf_dbg(TRACE, "TX action frame operation has failed\n"); } /* clear status bit for action tx */ clear_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status); clear_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status); exit: return err; } /** * brcmf_p2p_pub_af_tx() - public action frame tx routine. * * @cfg: driver private data for cfg80211 interface. * @af_params: action frame data/info. * @config_af_params: configuration data for action frame. * * routine which transmits ation frame public type. */ static s32 brcmf_p2p_pub_af_tx(struct brcmf_cfg80211_info *cfg, struct brcmf_fil_af_params_le *af_params, struct brcmf_config_af_params *config_af_params) { struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_pub *drvr = cfg->pub; struct brcmf_fil_action_frame_le *action_frame; struct brcmf_p2p_pub_act_frame *act_frm; s32 err = 0; u16 ie_len; action_frame = &af_params->action_frame; act_frm = (struct brcmf_p2p_pub_act_frame *)(action_frame->data); config_af_params->extra_listen = true; switch (act_frm->subtype) { case P2P_PAF_GON_REQ: brcmf_dbg(TRACE, "P2P: GO_NEG_PHASE status set\n"); set_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); config_af_params->mpc_onoff = 0; config_af_params->search_channel = true; p2p->next_af_subtype = act_frm->subtype + 1; p2p->gon_req_action = true; /* increase dwell time to wait for RESP frame */ af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); break; case P2P_PAF_GON_RSP: p2p->next_af_subtype = act_frm->subtype + 1; /* increase dwell time to wait for CONF frame */ af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); break; case P2P_PAF_GON_CONF: /* If we reached till GO Neg confirmation reset the filter */ brcmf_dbg(TRACE, "P2P: GO_NEG_PHASE status cleared\n"); clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); /* turn on mpc again if go nego is done */ config_af_params->mpc_onoff = 1; /* minimize dwell time */ af_params->dwell_time = cpu_to_le32(P2P_AF_MIN_DWELL_TIME); config_af_params->extra_listen = false; break; case P2P_PAF_INVITE_REQ: config_af_params->search_channel = true; p2p->next_af_subtype = act_frm->subtype + 1; /* increase dwell time */ af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); break; case P2P_PAF_INVITE_RSP: /* minimize dwell time */ af_params->dwell_time = cpu_to_le32(P2P_AF_MIN_DWELL_TIME); config_af_params->extra_listen = false; break; case P2P_PAF_DEVDIS_REQ: config_af_params->search_channel = true; p2p->next_af_subtype = act_frm->subtype + 1; /* maximize dwell time to wait for RESP frame */ af_params->dwell_time = cpu_to_le32(P2P_AF_LONG_DWELL_TIME); break; case P2P_PAF_DEVDIS_RSP: /* minimize dwell time */ af_params->dwell_time = cpu_to_le32(P2P_AF_MIN_DWELL_TIME); config_af_params->extra_listen = false; break; case P2P_PAF_PROVDIS_REQ: ie_len = le16_to_cpu(action_frame->len) - offsetof(struct brcmf_p2p_pub_act_frame, elts); if (cfg80211_get_p2p_attr(&act_frm->elts[0], ie_len, IEEE80211_P2P_ATTR_GROUP_ID, NULL, 0) < 0) config_af_params->search_channel = true; config_af_params->mpc_onoff = 0; p2p->next_af_subtype = act_frm->subtype + 1; /* increase dwell time to wait for RESP frame */ af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); break; case P2P_PAF_PROVDIS_RSP: /* wpa_supplicant send go nego req right after prov disc */ p2p->next_af_subtype = P2P_PAF_GON_REQ; /* increase dwell time to MED level */ af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); config_af_params->extra_listen = false; break; default: bphy_err(drvr, "Unknown p2p pub act frame subtype: %d\n", act_frm->subtype); err = -EINVAL; } return err; } static bool brcmf_p2p_check_dwell_overflow(u32 requested_dwell, unsigned long dwell_jiffies) { if ((requested_dwell & CUSTOM_RETRY_MASK) && (jiffies_to_msecs(jiffies - dwell_jiffies) > (requested_dwell & ~CUSTOM_RETRY_MASK))) { brcmf_err("Action frame TX retry time over dwell time!\n"); return true; } return false; } /** * brcmf_p2p_send_action_frame() - send action frame . * - * @cfg: driver private data for cfg80211 interface. - * @ndev: net device to transmit on. + * @ifp: interface to transmit on. * @af_params: configuration data for action frame. */ -bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, - struct net_device *ndev, +bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp, struct brcmf_fil_af_params_le *af_params) { + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; struct brcmf_p2p_info *p2p = &cfg->p2p; - struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_fil_action_frame_le *action_frame; struct brcmf_config_af_params config_af_params; struct afx_hdl *afx_hdl = &p2p->afx_hdl; struct brcmf_pub *drvr = cfg->pub; u16 action_frame_len; bool ack = false; u8 category; u8 action; s32 tx_retry; s32 extra_listen_time; uint delta_ms; unsigned long dwell_jiffies = 0; bool dwell_overflow = false; u32 requested_dwell = le32_to_cpu(af_params->dwell_time); action_frame = &af_params->action_frame; action_frame_len = le16_to_cpu(action_frame->len); brcmf_p2p_print_actframe(true, action_frame->data, action_frame_len); /* Add the default dwell time. Dwell time to stay off-channel */ /* to wait for a response action frame after transmitting an */ /* GO Negotiation action frame */ af_params->dwell_time = cpu_to_le32(P2P_AF_DWELL_TIME); category = action_frame->data[DOT11_ACTION_CAT_OFF]; action = action_frame->data[DOT11_ACTION_ACT_OFF]; /* initialize variables */ p2p->next_af_subtype = P2P_PAF_SUBTYPE_INVALID; p2p->gon_req_action = false; /* config parameters */ config_af_params.mpc_onoff = -1; config_af_params.search_channel = false; config_af_params.extra_listen = false; if (brcmf_p2p_is_pub_action(action_frame->data, action_frame_len)) { /* p2p public action frame process */ if (brcmf_p2p_pub_af_tx(cfg, af_params, &config_af_params)) { /* Just send unknown subtype frame with */ /* default parameters. */ bphy_err(drvr, "P2P Public action frame, unknown subtype.\n"); } } else if (brcmf_p2p_is_gas_action(action_frame->data, action_frame_len)) { /* service discovery process */ if (action == P2PSD_ACTION_ID_GAS_IREQ || action == P2PSD_ACTION_ID_GAS_CREQ) { /* configure service discovery query frame */ config_af_params.search_channel = true; /* save next af suptype to cancel */ /* remaining dwell time */ p2p->next_af_subtype = action + 1; af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); } else if (action == P2PSD_ACTION_ID_GAS_IRESP || action == P2PSD_ACTION_ID_GAS_CRESP) { /* configure service discovery response frame */ af_params->dwell_time = cpu_to_le32(P2P_AF_MIN_DWELL_TIME); } else { bphy_err(drvr, "Unknown action type: %d\n", action); goto exit; } } else if (brcmf_p2p_is_p2p_action(action_frame->data, action_frame_len)) { /* do not configure anything. it will be */ /* sent with a default configuration */ } else { bphy_err(drvr, "Unknown Frame: category 0x%x, action 0x%x\n", category, action); return false; } /* if connecting on primary iface, sleep for a while before sending * af tx for VSDB */ if (test_bit(BRCMF_VIF_STATUS_CONNECTING, &p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->sme_state)) #if defined(__linux__) msleep(50); #elif defined(__FreeBSD__) linux_msleep(50); #endif /* if scan is ongoing, abort current scan. */ if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) brcmf_abort_scanning(cfg); memcpy(afx_hdl->tx_dst_addr, action_frame->da, ETH_ALEN); /* To make sure to send successfully action frame, turn off mpc */ if (config_af_params.mpc_onoff == 0) brcmf_set_mpc(ifp, 0); /* set status and destination address before sending af */ if (p2p->next_af_subtype != P2P_PAF_SUBTYPE_INVALID) { /* set status to cancel the remained dwell time in rx process */ set_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status); } p2p->af_sent_channel = 0; set_bit(BRCMF_P2P_STATUS_SENDING_ACT_FRAME, &p2p->status); /* validate channel and p2p ies */ if (config_af_params.search_channel && IS_P2P_SOCIAL_CHANNEL(le32_to_cpu(af_params->channel)) && p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->saved_ie.probe_req_ie_len) { afx_hdl = &p2p->afx_hdl; afx_hdl->peer_listen_chan = le32_to_cpu(af_params->channel); if (brcmf_p2p_af_searching_channel(p2p) == P2P_INVALID_CHANNEL) { bphy_err(drvr, "Couldn't find peer's channel.\n"); goto exit; } /* Abort scan even for VSDB scenarios. Scan gets aborted in * firmware but after the check of piggyback algorithm. To take * care of current piggback algo, lets abort the scan here * itself. */ brcmf_notify_escan_complete(cfg, ifp, true, true); /* update channel */ af_params->channel = cpu_to_le32(afx_hdl->peer_chan); } dwell_jiffies = jiffies; dwell_overflow = brcmf_p2p_check_dwell_overflow(requested_dwell, dwell_jiffies); tx_retry = 0; while (!p2p->block_gon_req_tx && (!ack) && (tx_retry < P2P_AF_TX_MAX_RETRY) && !dwell_overflow) { if (af_params->channel) #if defined(__linux__) msleep(P2P_AF_RETRY_DELAY_TIME); #elif defined(__FreeBSD__) linux_msleep(P2P_AF_RETRY_DELAY_TIME); #endif - ack = !brcmf_p2p_tx_action_frame(p2p, af_params); + ack = !brcmf_p2p_tx_action_frame(ifp, p2p, af_params); tx_retry++; dwell_overflow = brcmf_p2p_check_dwell_overflow(requested_dwell, dwell_jiffies); } if (!ack) { bphy_err(drvr, "Failed to send Action Frame(retry %d)\n", tx_retry); clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); } exit: clear_bit(BRCMF_P2P_STATUS_SENDING_ACT_FRAME, &p2p->status); /* WAR: sometimes dongle does not keep the dwell time of 'actframe'. * if we coundn't get the next action response frame and dongle does * not keep the dwell time, go to listen state again to get next action * response frame. */ if (ack && config_af_params.extra_listen && !p2p->block_gon_req_tx && test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status) && p2p->af_sent_channel == afx_hdl->my_listen_chan) { delta_ms = jiffies_to_msecs(jiffies - p2p->af_tx_sent_jiffies); if (le32_to_cpu(af_params->dwell_time) > delta_ms) extra_listen_time = le32_to_cpu(af_params->dwell_time) - delta_ms; else extra_listen_time = 0; if (extra_listen_time > 50) { set_bit(BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, &p2p->status); brcmf_dbg(INFO, "Wait more time! actual af time:%d, calculated extra listen:%d\n", le32_to_cpu(af_params->dwell_time), extra_listen_time); extra_listen_time += 100; if (!brcmf_p2p_discover_listen(p2p, p2p->af_sent_channel, extra_listen_time)) { unsigned long duration; extra_listen_time += 100; duration = msecs_to_jiffies(extra_listen_time); wait_for_completion_timeout(&p2p->wait_next_af, duration); } clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, &p2p->status); } } if (p2p->block_gon_req_tx) { /* if ack is true, supplicant will wait more time(100ms). * so we will return it as a success to get more time . */ p2p->block_gon_req_tx = false; ack = true; } clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status); /* if all done, turn mpc on again */ if (config_af_params.mpc_onoff == 1) brcmf_set_mpc(ifp, 1); return ack; } /** * brcmf_p2p_notify_rx_mgmt_p2p_probereq() - Event handler for p2p probe req. * * @ifp: interface pointer for which event was received. * @e: even message. * @data: payload of event message (probe request). */ s32 brcmf_p2p_notify_rx_mgmt_p2p_probereq(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; struct brcmf_p2p_info *p2p = &cfg->p2p; struct afx_hdl *afx_hdl = &p2p->afx_hdl; struct brcmf_cfg80211_vif *vif = ifp->vif; struct brcmf_rx_mgmt_data *rxframe = (struct brcmf_rx_mgmt_data *)data; struct brcmu_chan ch; u8 *mgmt_frame; u32 mgmt_frame_len; s32 freq; u16 mgmt_type; brcmf_dbg(INFO, "Enter: event %d reason %d\n", e->event_code, e->reason); if (e->datalen < sizeof(*rxframe)) { brcmf_dbg(SCAN, "Event data too small. Ignore\n"); return 0; } ch.chspec = be16_to_cpu(rxframe->chanspec); cfg->d11inf.decchspec(&ch); if (test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status) && (ether_addr_equal(afx_hdl->tx_dst_addr, e->addr))) { afx_hdl->peer_chan = ch.control_ch_num; brcmf_dbg(INFO, "PROBE REQUEST: Peer found, channel=%d\n", afx_hdl->peer_chan); complete(&afx_hdl->act_frm_scan); } /* Firmware sends us two proberesponses for each idx one. At the */ /* moment anything but bsscfgidx 0 is passed up to supplicant */ if (e->bsscfgidx == 0) return 0; /* Filter any P2P probe reqs arriving during the GO-NEG Phase */ if (test_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status)) { brcmf_dbg(INFO, "Filtering P2P probe_req in GO-NEG phase\n"); return 0; } /* Check if wpa_supplicant has registered for this frame */ brcmf_dbg(INFO, "vif->mgmt_rx_reg %04x\n", vif->mgmt_rx_reg); mgmt_type = (IEEE80211_STYPE_PROBE_REQ & IEEE80211_FCTL_STYPE) >> 4; if ((vif->mgmt_rx_reg & BIT(mgmt_type)) == 0) return 0; mgmt_frame = (u8 *)(rxframe + 1); mgmt_frame_len = e->datalen - sizeof(*rxframe); freq = ieee80211_channel_to_frequency(ch.control_ch_num, ch.band == BRCMU_CHAN_BAND_2G ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); cfg80211_rx_mgmt(&vif->wdev, freq, 0, mgmt_frame, mgmt_frame_len, 0); brcmf_dbg(INFO, "mgmt_frame_len (%d) , e->datalen (%d), chanspec (%04x), freq (%d)\n", mgmt_frame_len, e->datalen, ch.chspec, freq); return 0; } /** * brcmf_p2p_get_current_chanspec() - Get current operation channel. * * @p2p: P2P specific data. * @chanspec: chanspec to be returned. */ static void brcmf_p2p_get_current_chanspec(struct brcmf_p2p_info *p2p, u16 *chanspec) { struct brcmf_if *ifp; u8 mac_addr[ETH_ALEN]; struct brcmu_chan ch; struct brcmf_bss_info_le *bi; u8 *buf; ifp = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; if (brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSSID, mac_addr, ETH_ALEN) == 0) { buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); if (buf != NULL) { *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); if (brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, buf, WL_BSS_INFO_MAX) == 0) { bi = (struct brcmf_bss_info_le *)(buf + 4); *chanspec = le16_to_cpu(bi->chanspec); kfree(buf); return; } kfree(buf); } } /* Use default channel for P2P */ ch.chnum = BRCMF_P2P_TEMP_CHAN; ch.bw = BRCMU_CHAN_BW_20; p2p->cfg->d11inf.encchspec(&ch); *chanspec = ch.chspec; } /** * brcmf_p2p_ifchange - Change a P2P Role. * @cfg: driver private data for cfg80211 interface. * @if_type: interface type. * Returns 0 if success. */ int brcmf_p2p_ifchange(struct brcmf_cfg80211_info *cfg, enum brcmf_fil_p2p_if_types if_type) { struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_vif *vif; struct brcmf_fil_p2p_if_le if_request; s32 err; u16 chanspec; brcmf_dbg(TRACE, "Enter\n"); vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; if (!vif) { bphy_err(drvr, "vif for P2PAPI_BSSCFG_PRIMARY does not exist\n"); return -EPERM; } brcmf_notify_escan_complete(cfg, vif->ifp, true, true); vif = p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif; if (!vif) { bphy_err(drvr, "vif for P2PAPI_BSSCFG_CONNECTION does not exist\n"); return -EPERM; } brcmf_set_mpc(vif->ifp, 0); /* In concurrency case, STA may be already associated in a particular */ /* channel. so retrieve the current channel of primary interface and */ /* then start the virtual interface on that. */ brcmf_p2p_get_current_chanspec(p2p, &chanspec); if_request.type = cpu_to_le16((u16)if_type); if_request.chspec = cpu_to_le16(chanspec); memcpy(if_request.addr, p2p->conn_int_addr, sizeof(if_request.addr)); brcmf_cfg80211_arm_vif_event(cfg, vif); err = brcmf_fil_iovar_data_set(vif->ifp, "p2p_ifupd", &if_request, sizeof(if_request)); if (err) { bphy_err(drvr, "p2p_ifupd FAILED, err=%d\n", err); brcmf_cfg80211_arm_vif_event(cfg, NULL); return err; } err = brcmf_cfg80211_wait_vif_event(cfg, BRCMF_E_IF_CHANGE, BRCMF_VIF_EVENT_TIMEOUT); brcmf_cfg80211_arm_vif_event(cfg, NULL); if (!err) { bphy_err(drvr, "No BRCMF_E_IF_CHANGE event received\n"); return -EIO; } err = brcmf_fil_cmd_int_set(vif->ifp, BRCMF_C_SET_SCB_TIMEOUT, BRCMF_SCB_TIMEOUT_VALUE); return err; } static int brcmf_p2p_request_p2p_if(struct brcmf_p2p_info *p2p, struct brcmf_if *ifp, u8 ea[ETH_ALEN], enum brcmf_fil_p2p_if_types iftype) { struct brcmf_fil_p2p_if_le if_request; int err; u16 chanspec; /* we need a default channel */ brcmf_p2p_get_current_chanspec(p2p, &chanspec); /* fill the firmware request */ memcpy(if_request.addr, ea, ETH_ALEN); if_request.type = cpu_to_le16((u16)iftype); if_request.chspec = cpu_to_le16(chanspec); err = brcmf_fil_iovar_data_set(ifp, "p2p_ifadd", &if_request, sizeof(if_request)); return err; } static int brcmf_p2p_disable_p2p_if(struct brcmf_cfg80211_vif *vif) { struct brcmf_cfg80211_info *cfg = wdev_to_cfg(&vif->wdev); struct net_device *pri_ndev = cfg_to_ndev(cfg); struct brcmf_if *ifp = netdev_priv(pri_ndev); const u8 *addr = vif->wdev.netdev->dev_addr; return brcmf_fil_iovar_data_set(ifp, "p2p_ifdis", addr, ETH_ALEN); } static int brcmf_p2p_release_p2p_if(struct brcmf_cfg80211_vif *vif) { struct brcmf_cfg80211_info *cfg = wdev_to_cfg(&vif->wdev); struct net_device *pri_ndev = cfg_to_ndev(cfg); struct brcmf_if *ifp = netdev_priv(pri_ndev); const u8 *addr = vif->wdev.netdev->dev_addr; return brcmf_fil_iovar_data_set(ifp, "p2p_ifdel", addr, ETH_ALEN); } /** * brcmf_p2p_create_p2pdev() - create a P2P_DEVICE virtual interface. * * @p2p: P2P specific data. * @wiphy: wiphy device of new interface. * @addr: mac address for this new interface. */ static struct wireless_dev *brcmf_p2p_create_p2pdev(struct brcmf_p2p_info *p2p, struct wiphy *wiphy, u8 *addr) { struct brcmf_pub *drvr = p2p->cfg->pub; struct brcmf_cfg80211_vif *p2p_vif; struct brcmf_if *p2p_ifp; struct brcmf_if *pri_ifp; int err; u32 bsscfgidx; if (p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif) return ERR_PTR(-ENOSPC); p2p_vif = brcmf_alloc_vif(p2p->cfg, NL80211_IFTYPE_P2P_DEVICE); if (IS_ERR(p2p_vif)) { bphy_err(drvr, "could not create discovery vif\n"); return (struct wireless_dev *)p2p_vif; } pri_ifp = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; /* firmware requires unique mac address for p2pdev interface */ if (addr && ether_addr_equal(addr, pri_ifp->mac_addr)) { bphy_err(drvr, "discovery vif must be different from primary interface\n"); err = -EINVAL; goto fail; } brcmf_p2p_generate_bss_mac(p2p, addr); brcmf_p2p_set_firmware(pri_ifp, p2p->dev_addr); brcmf_cfg80211_arm_vif_event(p2p->cfg, p2p_vif); brcmf_fweh_p2pdev_setup(pri_ifp, true); /* Initialize P2P Discovery in the firmware */ err = brcmf_fil_iovar_int_set(pri_ifp, "p2p_disc", 1); if (err < 0) { bphy_err(drvr, "set p2p_disc error\n"); brcmf_fweh_p2pdev_setup(pri_ifp, false); brcmf_cfg80211_arm_vif_event(p2p->cfg, NULL); goto fail; } /* wait for firmware event */ err = brcmf_cfg80211_wait_vif_event(p2p->cfg, BRCMF_E_IF_ADD, BRCMF_VIF_EVENT_TIMEOUT); brcmf_cfg80211_arm_vif_event(p2p->cfg, NULL); brcmf_fweh_p2pdev_setup(pri_ifp, false); if (!err) { bphy_err(drvr, "timeout occurred\n"); err = -EIO; goto fail; } /* discovery interface created */ p2p_ifp = p2p_vif->ifp; p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif = p2p_vif; memcpy(p2p_ifp->mac_addr, p2p->dev_addr, ETH_ALEN); memcpy(&p2p_vif->wdev.address, p2p->dev_addr, sizeof(p2p->dev_addr)); /* verify bsscfg index for P2P discovery */ err = brcmf_fil_iovar_int_get(pri_ifp, "p2p_dev", &bsscfgidx); if (err < 0) { bphy_err(drvr, "retrieving discover bsscfg index failed\n"); goto fail; } WARN_ON(p2p_ifp->bsscfgidx != bsscfgidx); - init_completion(&p2p->send_af_done); INIT_WORK(&p2p->afx_hdl.afx_work, brcmf_p2p_afx_handler); init_completion(&p2p->afx_hdl.act_frm_scan); init_completion(&p2p->wait_next_af); return &p2p_vif->wdev; fail: brcmf_free_vif(p2p_vif); return ERR_PTR(err); } static int brcmf_p2p_get_conn_idx(struct brcmf_cfg80211_info *cfg) { int i; struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); if (!ifp) return -ENODEV; for (i = P2PAPI_BSSCFG_CONNECTION; i < P2PAPI_BSSCFG_MAX; i++) { if (!cfg->p2p.bss_idx[i].vif) { if (i == P2PAPI_BSSCFG_CONNECTION2 && !(brcmf_feat_is_enabled(ifp, BRCMF_FEAT_RSDB))) { brcmf_err("Multi p2p not supported"); return -EIO; } return i; } } return -EIO; } /** * brcmf_p2p_add_vif() - create a new P2P virtual interface. * * @wiphy: wiphy device of new interface. * @name: name of the new interface. * @name_assign_type: origin of the interface name * @type: nl80211 interface type. * @params: contains mac address for P2P device. */ struct wireless_dev *brcmf_p2p_add_vif(struct wiphy *wiphy, const char *name, unsigned char name_assign_type, enum nl80211_iftype type, struct vif_params *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); struct brcmf_pub *drvr = cfg->pub; struct brcmf_cfg80211_vif *vif; enum brcmf_fil_p2p_if_types iftype; int err = 0; int connidx; u8 *p2p_intf_addr; if (brcmf_cfg80211_vif_event_armed(cfg)) return ERR_PTR(-EBUSY); brcmf_dbg(INFO, "adding vif \"%s\" (type=%d)\n", name, type); switch (type) { case NL80211_IFTYPE_P2P_CLIENT: iftype = BRCMF_FIL_P2P_IF_CLIENT; break; case NL80211_IFTYPE_P2P_GO: iftype = BRCMF_FIL_P2P_IF_GO; break; case NL80211_IFTYPE_P2P_DEVICE: return brcmf_p2p_create_p2pdev(&cfg->p2p, wiphy, params->macaddr); default: return ERR_PTR(-EOPNOTSUPP); } vif = brcmf_alloc_vif(cfg, type); if (IS_ERR(vif)) return (struct wireless_dev *)vif; brcmf_cfg80211_arm_vif_event(cfg, vif); connidx = brcmf_p2p_get_conn_idx(cfg); if (connidx == P2PAPI_BSSCFG_CONNECTION) p2p_intf_addr = cfg->p2p.conn_int_addr; else if (connidx == P2PAPI_BSSCFG_CONNECTION2) p2p_intf_addr = cfg->p2p.conn2_int_addr; else err = -EINVAL; if (!err) err = brcmf_p2p_request_p2p_if(&cfg->p2p, ifp, p2p_intf_addr, iftype); if (err) { brcmf_err("request p2p interface failed\n"); brcmf_cfg80211_arm_vif_event(cfg, NULL); goto fail; } /* wait for firmware event */ err = brcmf_cfg80211_wait_vif_event(cfg, BRCMF_E_IF_ADD, BRCMF_VIF_EVENT_TIMEOUT); brcmf_cfg80211_arm_vif_event(cfg, NULL); if (!err) { bphy_err(drvr, "timeout occurred\n"); err = -EIO; goto fail; } /* interface created in firmware */ ifp = vif->ifp; if (!ifp) { bphy_err(drvr, "no if pointer provided\n"); err = -ENOENT; goto fail; } strscpy(ifp->ndev->name, name, sizeof(ifp->ndev->name)); ifp->ndev->name_assign_type = name_assign_type; err = brcmf_net_attach(ifp, true); if (err) { bphy_err(drvr, "Registering netdevice failed\n"); free_netdev(ifp->ndev); goto fail; } cfg->p2p.bss_idx[connidx].vif = vif; /* Disable firmware roaming for P2P interface */ brcmf_fil_iovar_int_set(ifp, "roam_off", 1); if (iftype == BRCMF_FIL_P2P_IF_GO) { /* set station timeout for p2p */ brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCB_TIMEOUT, BRCMF_SCB_TIMEOUT_VALUE); } return &ifp->vif->wdev; fail: brcmf_free_vif(vif); return ERR_PTR(err); } /** * brcmf_p2p_del_vif() - delete a P2P virtual interface. * * @wiphy: wiphy device of interface. * @wdev: wireless device of interface. */ int brcmf_p2p_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_cfg80211_vif *vif; enum nl80211_iftype iftype; bool wait_for_disable = false; int err; brcmf_dbg(TRACE, "delete P2P vif\n"); vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); iftype = vif->wdev.iftype; brcmf_cfg80211_arm_vif_event(cfg, vif); switch (iftype) { case NL80211_IFTYPE_P2P_CLIENT: if (test_bit(BRCMF_VIF_STATUS_DISCONNECTING, &vif->sme_state)) wait_for_disable = true; break; case NL80211_IFTYPE_P2P_GO: if (!brcmf_p2p_disable_p2p_if(vif)) wait_for_disable = true; break; case NL80211_IFTYPE_P2P_DEVICE: if (!p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif) return 0; brcmf_p2p_cancel_remain_on_channel(vif->ifp); brcmf_p2p_deinit_discovery(p2p); break; default: return -ENOTSUPP; } clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); brcmf_dbg(INFO, "P2P: GO_NEG_PHASE status cleared\n"); if (wait_for_disable) wait_for_completion_timeout(&cfg->vif_disabled, BRCMF_P2P_DISABLE_TIMEOUT); err = 0; if (iftype != NL80211_IFTYPE_P2P_DEVICE) { brcmf_vif_clear_mgmt_ies(vif); err = brcmf_p2p_release_p2p_if(vif); } if (!err) { /* wait for firmware event */ err = brcmf_cfg80211_wait_vif_event(cfg, BRCMF_E_IF_DEL, BRCMF_VIF_EVENT_TIMEOUT); if (!err) err = -EIO; else err = 0; } brcmf_remove_interface(vif->ifp, true); brcmf_cfg80211_arm_vif_event(cfg, NULL); if (iftype != NL80211_IFTYPE_P2P_DEVICE) { if (vif == p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif) p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif = NULL; if (vif == p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION2].vif) p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION2].vif = NULL; } return err; } void brcmf_p2p_ifp_removed(struct brcmf_if *ifp, bool locked) { struct brcmf_cfg80211_info *cfg; struct brcmf_cfg80211_vif *vif; brcmf_dbg(INFO, "P2P: device interface removed\n"); vif = ifp->vif; cfg = wdev_to_cfg(&vif->wdev); cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif = NULL; if (!locked) { rtnl_lock(); wiphy_lock(cfg->wiphy); cfg80211_unregister_wdev(&vif->wdev); wiphy_unlock(cfg->wiphy); rtnl_unlock(); } else { cfg80211_unregister_wdev(&vif->wdev); } brcmf_free_vif(vif); } int brcmf_p2p_start_device(struct wiphy *wiphy, struct wireless_dev *wdev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_cfg80211_vif *vif; int err; vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); mutex_lock(&cfg->usr_sync); err = brcmf_p2p_enable_discovery(p2p); if (!err) set_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state); mutex_unlock(&cfg->usr_sync); return err; } void brcmf_p2p_stop_device(struct wiphy *wiphy, struct wireless_dev *wdev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_cfg80211_vif *vif; vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); /* This call can be result of the unregister_wdev call. In that case * we dont want to do anything anymore. Just return. The config vif * will have been cleared at this point. */ if (p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif == vif) { mutex_lock(&cfg->usr_sync); /* Set the discovery state to SCAN */ (void)brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_SCAN, 0, 0); brcmf_abort_scanning(cfg); clear_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state); mutex_unlock(&cfg->usr_sync); } } /** * brcmf_p2p_attach() - attach for P2P. * * @cfg: driver private data for cfg80211 interface. * @p2pdev_forced: create p2p device interface at attach. */ s32 brcmf_p2p_attach(struct brcmf_cfg80211_info *cfg, bool p2pdev_forced) { struct brcmf_pub *drvr = cfg->pub; struct brcmf_p2p_info *p2p; struct brcmf_if *pri_ifp; s32 err = 0; void *err_ptr; p2p = &cfg->p2p; p2p->cfg = cfg; pri_ifp = brcmf_get_ifp(cfg->pub, 0); p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif = pri_ifp->vif; + init_completion(&p2p->send_af_done); + if (p2pdev_forced) { err_ptr = brcmf_p2p_create_p2pdev(p2p, NULL, NULL); if (IS_ERR(err_ptr)) { bphy_err(drvr, "P2P device creation failed.\n"); err = PTR_ERR(err_ptr); } } else { p2p->p2pdev_dynamically = true; } return err; } /** * brcmf_p2p_detach() - detach P2P. * * @p2p: P2P specific data. */ void brcmf_p2p_detach(struct brcmf_p2p_info *p2p) { struct brcmf_cfg80211_vif *vif; vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; if (vif != NULL) { brcmf_p2p_cancel_remain_on_channel(vif->ifp); brcmf_p2p_deinit_discovery(p2p); brcmf_remove_interface(vif->ifp, false); } /* just set it all to zero */ memset(p2p, 0, sizeof(*p2p)); } diff --git a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/p2p.h b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/p2p.h index d2ecee565bf2..d3137ebd7158 100644 --- a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/p2p.h +++ b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/p2p.h @@ -1,179 +1,178 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2012 Broadcom Corporation */ #ifndef WL_CFGP2P_H_ #define WL_CFGP2P_H_ #include struct brcmf_cfg80211_info; /** * enum p2p_bss_type - different type of BSS configurations. * * @P2PAPI_BSSCFG_PRIMARY: maps to driver's primary bsscfg. * @P2PAPI_BSSCFG_DEVICE: maps to driver's P2P device discovery bsscfg. * @P2PAPI_BSSCFG_CONNECTION: maps to driver's 1st P2P connection bsscfg. * @P2PAPI_BSSCFG_CONNECTION2: maps to driver's 2nd P2P connection bsscfg. * @P2PAPI_BSSCFG_MAX: used for range checking. */ enum p2p_bss_type { P2PAPI_BSSCFG_PRIMARY, /* maps to driver's primary bsscfg */ P2PAPI_BSSCFG_DEVICE, /* maps to driver's P2P device discovery bsscfg */ P2PAPI_BSSCFG_CONNECTION, /* driver's 1st P2P connection bsscfg */ P2PAPI_BSSCFG_CONNECTION2, /* driver's 2nd P2P connection bsscfg */ P2PAPI_BSSCFG_MAX }; /** * struct p2p_bss - peer-to-peer bss related information. * * @vif: virtual interface of this P2P bss. * @private_data: TBD */ struct p2p_bss { struct brcmf_cfg80211_vif *vif; void *private_data; }; /** * enum brcmf_p2p_status - P2P specific dongle status. * * @BRCMF_P2P_STATUS_IF_ADD: peer-to-peer vif add sent to dongle. * @BRCMF_P2P_STATUS_IF_DEL: NOT-USED? * @BRCMF_P2P_STATUS_IF_DELETING: peer-to-peer vif delete sent to dongle. * @BRCMF_P2P_STATUS_IF_CHANGING: peer-to-peer vif change sent to dongle. * @BRCMF_P2P_STATUS_IF_CHANGED: peer-to-peer vif change completed on dongle. * @BRCMF_P2P_STATUS_ACTION_TX_COMPLETED: action frame tx completed. * @BRCMF_P2P_STATUS_ACTION_TX_NOACK: action frame tx not acked. * @BRCMF_P2P_STATUS_GO_NEG_PHASE: P2P GO negotiation ongoing. * @BRCMF_P2P_STATUS_DISCOVER_LISTEN: P2P listen, remaining on channel. * @BRCMF_P2P_STATUS_SENDING_ACT_FRAME: In the process of sending action frame. * @BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN: extra listen time for af tx. * @BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME: waiting for action frame response. * @BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL: search channel for AF active. */ enum brcmf_p2p_status { BRCMF_P2P_STATUS_ENABLED, BRCMF_P2P_STATUS_IF_ADD, BRCMF_P2P_STATUS_IF_DEL, BRCMF_P2P_STATUS_IF_DELETING, BRCMF_P2P_STATUS_IF_CHANGING, BRCMF_P2P_STATUS_IF_CHANGED, BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, BRCMF_P2P_STATUS_ACTION_TX_NOACK, BRCMF_P2P_STATUS_GO_NEG_PHASE, BRCMF_P2P_STATUS_DISCOVER_LISTEN, BRCMF_P2P_STATUS_SENDING_ACT_FRAME, BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL }; /** * struct afx_hdl - action frame off channel storage. * * @afx_work: worker thread for searching channel * @act_frm_scan: thread synchronizing struct. * @is_active: channel searching active. * @peer_chan: current channel. * @is_listen: sets mode for afx worker. * @my_listen_chan: this peers listen channel. * @peer_listen_chan: remote peers listen channel. * @tx_dst_addr: mac address where tx af should be sent to. */ struct afx_hdl { struct work_struct afx_work; struct completion act_frm_scan; bool is_active; s32 peer_chan; bool is_listen; u16 my_listen_chan; u16 peer_listen_chan; u8 tx_dst_addr[ETH_ALEN]; }; /** * struct brcmf_p2p_info - p2p specific driver information. * * @cfg: driver private data for cfg80211 interface. * @status: status of P2P (see enum brcmf_p2p_status). * @dev_addr: P2P device address. * @int_addr: P2P interface address. * @bss_idx: informate for P2P bss types. * @listen_timer: timer for @WL_P2P_DISC_ST_LISTEN discover state. * @listen_channel: channel for @WL_P2P_DISC_ST_LISTEN discover state. * @remain_on_channel: contains copy of struct used by cfg80211. * @remain_on_channel_cookie: cookie counter for remain on channel cmd * @next_af_subtype: expected action frame subtype. * @send_af_done: indication that action frame tx is complete. * @afx_hdl: action frame search handler info. * @af_sent_channel: channel action frame is sent. * @af_tx_sent_jiffies: jiffies time when af tx was transmitted. * @wait_next_af: thread synchronizing struct. * @gon_req_action: about to send go negotiation requets frame. * @block_gon_req_tx: drop tx go negotiation requets frame. * @p2pdev_dynamically: is p2p device if created by module param or supplicant. * @wait_for_offchan_complete: wait for off-channel tx completion event. */ struct brcmf_p2p_info { struct brcmf_cfg80211_info *cfg; unsigned long status; u8 dev_addr[ETH_ALEN]; u8 conn_int_addr[ETH_ALEN]; u8 conn2_int_addr[ETH_ALEN]; struct p2p_bss bss_idx[P2PAPI_BSSCFG_MAX]; struct timer_list listen_timer; u8 listen_channel; struct ieee80211_channel remain_on_channel; u32 remain_on_channel_cookie; u8 next_af_subtype; struct completion send_af_done; struct afx_hdl afx_hdl; u32 af_sent_channel; unsigned long af_tx_sent_jiffies; struct completion wait_next_af; bool gon_req_action; bool block_gon_req_tx; bool p2pdev_dynamically; bool wait_for_offchan_complete; }; s32 brcmf_p2p_attach(struct brcmf_cfg80211_info *cfg, bool p2pdev_forced); void brcmf_p2p_detach(struct brcmf_p2p_info *p2p); struct wireless_dev *brcmf_p2p_add_vif(struct wiphy *wiphy, const char *name, unsigned char name_assign_type, enum nl80211_iftype type, struct vif_params *params); int brcmf_p2p_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev); int brcmf_p2p_ifchange(struct brcmf_cfg80211_info *cfg, enum brcmf_fil_p2p_if_types if_type); void brcmf_p2p_ifp_removed(struct brcmf_if *ifp, bool rtnl_locked); int brcmf_p2p_start_device(struct wiphy *wiphy, struct wireless_dev *wdev); void brcmf_p2p_stop_device(struct wiphy *wiphy, struct wireless_dev *wdev); int brcmf_p2p_scan_prep(struct wiphy *wiphy, struct cfg80211_scan_request *request, struct brcmf_cfg80211_vif *vif); int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, unsigned int duration, u64 *cookie); int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data); void brcmf_p2p_cancel_remain_on_channel(struct brcmf_if *ifp); int brcmf_p2p_notify_action_frame_rx(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data); int brcmf_p2p_notify_action_tx_complete(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data); -bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, - struct net_device *ndev, +bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp, struct brcmf_fil_af_params_le *af_params); bool brcmf_p2p_scan_finding_common_channel(struct brcmf_cfg80211_info *cfg, struct brcmf_bss_info_le *bi); s32 brcmf_p2p_notify_rx_mgmt_p2p_probereq(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data); #endif /* WL_CFGP2P_H_ */ diff --git a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/sdio.c b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/sdio.c index 8a0bad5119a0..8cf9d7e7c3f7 100644 --- a/sys/contrib/dev/broadcom/brcm80211/brcmfmac/sdio.c +++ b/sys/contrib/dev/broadcom/brcm80211/brcmfmac/sdio.c @@ -1,4650 +1,4650 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2010 Broadcom Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sdio.h" #include "chip.h" #include "firmware.h" #include "core.h" #include "common.h" #include "bcdc.h" #define DCMD_RESP_TIMEOUT msecs_to_jiffies(2500) #define CTL_DONE_TIMEOUT msecs_to_jiffies(2500) /* watermark expressed in number of words */ #define DEFAULT_F2_WATERMARK 0x8 #define CY_4373_F2_WATERMARK 0x40 #define CY_4373_F1_MESBUSYCTRL (CY_4373_F2_WATERMARK | SBSDIO_MESBUSYCTRL_ENAB) #define CY_43012_F2_WATERMARK 0x60 #define CY_43012_MES_WATERMARK 0x50 #define CY_43012_MESBUSYCTRL (CY_43012_MES_WATERMARK | \ SBSDIO_MESBUSYCTRL_ENAB) #define CY_4339_F2_WATERMARK 48 #define CY_4339_MES_WATERMARK 80 #define CY_4339_MESBUSYCTRL (CY_4339_MES_WATERMARK | \ SBSDIO_MESBUSYCTRL_ENAB) #define CY_43455_F2_WATERMARK 0x60 #define CY_43455_MES_WATERMARK 0x50 #define CY_43455_MESBUSYCTRL (CY_43455_MES_WATERMARK | \ SBSDIO_MESBUSYCTRL_ENAB) #define CY_435X_F2_WATERMARK 0x40 #define CY_435X_F1_MESBUSYCTRL (CY_435X_F2_WATERMARK | \ SBSDIO_MESBUSYCTRL_ENAB) #ifdef DEBUG #define BRCMF_TRAP_INFO_SIZE 80 #define CBUF_LEN (128) /* Device console log buffer state */ #define CONSOLE_BUFFER_MAX 2024 struct rte_log_le { __le32 buf; /* Can't be pointer on (64-bit) hosts */ __le32 buf_size; __le32 idx; char *_buf_compat; /* Redundant pointer for backward compat. */ }; struct rte_console { /* Virtual UART * When there is no UART (e.g. Quickturn), * the host should write a complete * input line directly into cbuf and then write * the length into vcons_in. * This may also be used when there is a real UART * (at risk of conflicting with * the real UART). vcons_out is currently unused. */ uint vcons_in; uint vcons_out; /* Output (logging) buffer * Console output is written to a ring buffer log_buf at index log_idx. * The host may read the output when it sees log_idx advance. * Output will be lost if the output wraps around faster than the host * polls. */ struct rte_log_le log_le; /* Console input line buffer * Characters are read one at a time into cbuf * until is received, then * the buffer is processed as a command line. * Also used for virtual UART. */ uint cbuf_idx; char cbuf[CBUF_LEN]; }; #endif /* DEBUG */ #include #include "bus.h" #include "debug.h" #include "tracepoint.h" #define TXQLEN 2048 /* bulk tx queue length */ #define TXHI (TXQLEN - 256) /* turn on flow control above TXHI */ #define TXLOW (TXHI - 256) /* turn off flow control below TXLOW */ #define PRIOMASK 7 #define TXRETRIES 2 /* # of retries for tx frames */ #define BRCMF_RXBOUND 50 /* Default for max rx frames in one scheduling */ #define BRCMF_TXBOUND 20 /* Default for max tx frames in one scheduling */ #define BRCMF_TXMINMAX 1 /* Max tx frames if rx still pending */ #define MEMBLOCK 2048 /* Block size used for downloading of dongle image */ #define MAX_DATA_BUF (32 * 1024) /* Must be large enough to hold biggest possible glom */ #define BRCMF_FIRSTREAD (1 << 6) /* SBSDIO_DEVICE_CTL */ /* 1: device will assert busy signal when receiving CMD53 */ #define SBSDIO_DEVCTL_SETBUSY 0x01 /* 1: assertion of sdio interrupt is synchronous to the sdio clock */ #define SBSDIO_DEVCTL_SPI_INTR_SYNC 0x02 /* 1: mask all interrupts to host except the chipActive (rev 8) */ #define SBSDIO_DEVCTL_CA_INT_ONLY 0x04 /* 1: isolate internal sdio signals, put external pads in tri-state; requires * sdio bus power cycle to clear (rev 9) */ #define SBSDIO_DEVCTL_PADS_ISO 0x08 /* 1: enable F2 Watermark */ #define SBSDIO_DEVCTL_F2WM_ENAB 0x10 /* Force SD->SB reset mapping (rev 11) */ #define SBSDIO_DEVCTL_SB_RST_CTL 0x30 /* Determined by CoreControl bit */ #define SBSDIO_DEVCTL_RST_CORECTL 0x00 /* Force backplane reset */ #define SBSDIO_DEVCTL_RST_BPRESET 0x10 /* Force no backplane reset */ #define SBSDIO_DEVCTL_RST_NOBPRESET 0x20 /* direct(mapped) cis space */ /* MAPPED common CIS address */ #define SBSDIO_CIS_BASE_COMMON 0x1000 /* maximum bytes in one CIS */ #define SBSDIO_CIS_SIZE_LIMIT 0x200 /* cis offset addr is < 17 bits */ #define SBSDIO_CIS_OFT_ADDR_MASK 0x1FFFF /* manfid tuple length, include tuple, link bytes */ #define SBSDIO_CIS_MANFID_TUPLE_LEN 6 #define SD_REG(field) \ (offsetof(struct sdpcmd_regs, field)) /* SDIO function 1 register CHIPCLKCSR */ /* Force ALP request to backplane */ #define SBSDIO_FORCE_ALP 0x01 /* Force HT request to backplane */ #define SBSDIO_FORCE_HT 0x02 /* Force ILP request to backplane */ #define SBSDIO_FORCE_ILP 0x04 /* Make ALP ready (power up xtal) */ #define SBSDIO_ALP_AVAIL_REQ 0x08 /* Make HT ready (power up PLL) */ #define SBSDIO_HT_AVAIL_REQ 0x10 /* Squelch clock requests from HW */ #define SBSDIO_FORCE_HW_CLKREQ_OFF 0x20 /* Status: ALP is ready */ #define SBSDIO_ALP_AVAIL 0x40 /* Status: HT is ready */ #define SBSDIO_HT_AVAIL 0x80 #define SBSDIO_CSR_MASK 0x1F #define SBSDIO_AVBITS (SBSDIO_HT_AVAIL | SBSDIO_ALP_AVAIL) #define SBSDIO_ALPAV(regval) ((regval) & SBSDIO_AVBITS) #define SBSDIO_HTAV(regval) (((regval) & SBSDIO_AVBITS) == SBSDIO_AVBITS) #define SBSDIO_ALPONLY(regval) (SBSDIO_ALPAV(regval) && !SBSDIO_HTAV(regval)) #define SBSDIO_CLKAV(regval, alponly) \ (SBSDIO_ALPAV(regval) && (alponly ? 1 : SBSDIO_HTAV(regval))) /* intstatus */ #define I_SMB_SW0 (1 << 0) /* To SB Mail S/W interrupt 0 */ #define I_SMB_SW1 (1 << 1) /* To SB Mail S/W interrupt 1 */ #define I_SMB_SW2 (1 << 2) /* To SB Mail S/W interrupt 2 */ #define I_SMB_SW3 (1 << 3) /* To SB Mail S/W interrupt 3 */ #define I_SMB_SW_MASK 0x0000000f /* To SB Mail S/W interrupts mask */ #define I_SMB_SW_SHIFT 0 /* To SB Mail S/W interrupts shift */ #define I_HMB_SW0 (1 << 4) /* To Host Mail S/W interrupt 0 */ #define I_HMB_SW1 (1 << 5) /* To Host Mail S/W interrupt 1 */ #define I_HMB_SW2 (1 << 6) /* To Host Mail S/W interrupt 2 */ #define I_HMB_SW3 (1 << 7) /* To Host Mail S/W interrupt 3 */ #define I_HMB_SW_MASK 0x000000f0 /* To Host Mail S/W interrupts mask */ #define I_HMB_SW_SHIFT 4 /* To Host Mail S/W interrupts shift */ #define I_WR_OOSYNC (1 << 8) /* Write Frame Out Of Sync */ #define I_RD_OOSYNC (1 << 9) /* Read Frame Out Of Sync */ #define I_PC (1 << 10) /* descriptor error */ #define I_PD (1 << 11) /* data error */ #define I_DE (1 << 12) /* Descriptor protocol Error */ #define I_RU (1 << 13) /* Receive descriptor Underflow */ #define I_RO (1 << 14) /* Receive fifo Overflow */ #define I_XU (1 << 15) /* Transmit fifo Underflow */ #define I_RI (1 << 16) /* Receive Interrupt */ #define I_BUSPWR (1 << 17) /* SDIO Bus Power Change (rev 9) */ #define I_XMTDATA_AVAIL (1 << 23) /* bits in fifo */ #define I_XI (1 << 24) /* Transmit Interrupt */ #define I_RF_TERM (1 << 25) /* Read Frame Terminate */ #define I_WF_TERM (1 << 26) /* Write Frame Terminate */ #define I_PCMCIA_XU (1 << 27) /* PCMCIA Transmit FIFO Underflow */ #define I_SBINT (1 << 28) /* sbintstatus Interrupt */ #define I_CHIPACTIVE (1 << 29) /* chip from doze to active state */ #define I_SRESET (1 << 30) /* CCCR RES interrupt */ #define I_IOE2 (1U << 31) /* CCCR IOE2 Bit Changed */ #define I_ERRORS (I_PC | I_PD | I_DE | I_RU | I_RO | I_XU) #define I_DMA (I_RI | I_XI | I_ERRORS) /* corecontrol */ #define CC_CISRDY (1 << 0) /* CIS Ready */ #define CC_BPRESEN (1 << 1) /* CCCR RES signal */ #define CC_F2RDY (1 << 2) /* set CCCR IOR2 bit */ #define CC_CLRPADSISO (1 << 3) /* clear SDIO pads isolation */ #define CC_XMTDATAAVAIL_MODE (1 << 4) #define CC_XMTDATAAVAIL_CTRL (1 << 5) /* SDA_FRAMECTRL */ #define SFC_RF_TERM (1 << 0) /* Read Frame Terminate */ #define SFC_WF_TERM (1 << 1) /* Write Frame Terminate */ #define SFC_CRC4WOOS (1 << 2) /* CRC error for write out of sync */ #define SFC_ABORTALL (1 << 3) /* Abort all in-progress frames */ /* * Software allocation of To SB Mailbox resources */ /* tosbmailbox bits corresponding to intstatus bits */ #define SMB_NAK (1 << 0) /* Frame NAK */ #define SMB_INT_ACK (1 << 1) /* Host Interrupt ACK */ #define SMB_USE_OOB (1 << 2) /* Use OOB Wakeup */ #define SMB_DEV_INT (1 << 3) /* Miscellaneous Interrupt */ /* tosbmailboxdata */ #define SMB_DATA_VERSION_SHIFT 16 /* host protocol version */ /* * Software allocation of To Host Mailbox resources */ /* intstatus bits */ #define I_HMB_FC_STATE I_HMB_SW0 /* Flow Control State */ #define I_HMB_FC_CHANGE I_HMB_SW1 /* Flow Control State Changed */ #define I_HMB_FRAME_IND I_HMB_SW2 /* Frame Indication */ #define I_HMB_HOST_INT I_HMB_SW3 /* Miscellaneous Interrupt */ /* tohostmailboxdata */ #define HMB_DATA_NAKHANDLED 0x0001 /* retransmit NAK'd frame */ #define HMB_DATA_DEVREADY 0x0002 /* talk to host after enable */ #define HMB_DATA_FC 0x0004 /* per prio flowcontrol update flag */ #define HMB_DATA_FWREADY 0x0008 /* fw ready for protocol activity */ #define HMB_DATA_FWHALT 0x0010 /* firmware halted */ #define HMB_DATA_FCDATA_MASK 0xff000000 #define HMB_DATA_FCDATA_SHIFT 24 #define HMB_DATA_VERSION_MASK 0x00ff0000 #define HMB_DATA_VERSION_SHIFT 16 /* * Software-defined protocol header */ /* Current protocol version */ #define SDPCM_PROT_VERSION 4 /* * Shared structure between dongle and the host. * The structure contains pointers to trap or assert information. */ #define SDPCM_SHARED_VERSION 0x0003 #define SDPCM_SHARED_VERSION_MASK 0x00FF #define SDPCM_SHARED_ASSERT_BUILT 0x0100 #define SDPCM_SHARED_ASSERT 0x0200 #define SDPCM_SHARED_TRAP 0x0400 /* Space for header read, limit for data packets */ #define MAX_HDR_READ (1 << 6) #define MAX_RX_DATASZ 2048 /* Bump up limit on waiting for HT to account for first startup; * if the image is doing a CRC calculation before programming the PMU * for HT availability, it could take a couple hundred ms more, so * max out at a 1 second (1000000us). */ #undef PMU_MAX_TRANSITION_DLY #define PMU_MAX_TRANSITION_DLY 1000000 /* Value for ChipClockCSR during initial setup */ #define BRCMF_INIT_CLKCTL1 (SBSDIO_FORCE_HW_CLKREQ_OFF | \ SBSDIO_ALP_AVAIL_REQ) /* Flags for SDH calls */ #define F2SYNC (SDIO_REQ_4BYTE | SDIO_REQ_FIXED) #define BRCMF_IDLE_ACTIVE 0 /* Do not request any SD clock change * when idle */ #define BRCMF_IDLE_INTERVAL 1 #define KSO_WAIT_US 50 #define MAX_KSO_ATTEMPTS (PMU_MAX_TRANSITION_DLY/KSO_WAIT_US) #define BRCMF_SDIO_MAX_ACCESS_ERRORS 5 #ifdef DEBUG /* Device console log buffer state */ struct brcmf_console { uint count; /* Poll interval msec counter */ uint log_addr; /* Log struct address (fixed) */ struct rte_log_le log_le; /* Log struct (host copy) */ uint bufsize; /* Size of log buffer */ u8 *buf; /* Log buffer (host copy) */ uint last; /* Last buffer read index */ }; struct brcmf_trap_info { __le32 type; __le32 epc; __le32 cpsr; __le32 spsr; __le32 r0; /* a1 */ __le32 r1; /* a2 */ __le32 r2; /* a3 */ __le32 r3; /* a4 */ __le32 r4; /* v1 */ __le32 r5; /* v2 */ __le32 r6; /* v3 */ __le32 r7; /* v4 */ __le32 r8; /* v5 */ __le32 r9; /* sb/v6 */ __le32 r10; /* sl/v7 */ __le32 r11; /* fp/v8 */ __le32 r12; /* ip */ __le32 r13; /* sp */ __le32 r14; /* lr */ __le32 pc; /* r15 */ }; #endif /* DEBUG */ struct sdpcm_shared { u32 flags; u32 trap_addr; u32 assert_exp_addr; u32 assert_file_addr; u32 assert_line; u32 console_addr; /* Address of struct rte_console */ u32 msgtrace_addr; u8 tag[32]; u32 brpt_addr; }; struct sdpcm_shared_le { __le32 flags; __le32 trap_addr; __le32 assert_exp_addr; __le32 assert_file_addr; __le32 assert_line; __le32 console_addr; /* Address of struct rte_console */ __le32 msgtrace_addr; u8 tag[32]; __le32 brpt_addr; }; /* dongle SDIO bus specific header info */ struct brcmf_sdio_hdrinfo { u8 seq_num; u8 channel; u16 len; u16 len_left; u16 len_nxtfrm; u8 dat_offset; bool lastfrm; u16 tail_pad; }; /* * hold counter variables */ struct brcmf_sdio_count { uint intrcount; /* Count of device interrupt callbacks */ uint lastintrs; /* Count as of last watchdog timer */ uint pollcnt; /* Count of active polls */ uint regfails; /* Count of R_REG failures */ uint tx_sderrs; /* Count of tx attempts with sd errors */ uint fcqueued; /* Tx packets that got queued */ uint rxrtx; /* Count of rtx requests (NAK to dongle) */ uint rx_toolong; /* Receive frames too long to receive */ uint rxc_errors; /* SDIO errors when reading control frames */ uint rx_hdrfail; /* SDIO errors on header reads */ uint rx_badhdr; /* Bad received headers (roosync?) */ uint rx_badseq; /* Mismatched rx sequence number */ uint fc_rcvd; /* Number of flow-control events received */ uint fc_xoff; /* Number which turned on flow-control */ uint fc_xon; /* Number which turned off flow-control */ uint rxglomfail; /* Failed deglom attempts */ uint rxglomframes; /* Number of glom frames (superframes) */ uint rxglompkts; /* Number of packets from glom frames */ uint f2rxhdrs; /* Number of header reads */ uint f2rxdata; /* Number of frame data reads */ uint f2txdata; /* Number of f2 frame writes */ uint f1regdata; /* Number of f1 register accesses */ uint tickcnt; /* Number of watchdog been schedule */ ulong tx_ctlerrs; /* Err of sending ctrl frames */ ulong tx_ctlpkts; /* Ctrl frames sent to dongle */ ulong rx_ctlerrs; /* Err of processing rx ctrl frames */ ulong rx_ctlpkts; /* Ctrl frames processed from dongle */ ulong rx_readahead_cnt; /* packets where header read-ahead was used */ }; /* misc chip info needed by some of the routines */ /* Private data for SDIO bus interaction */ struct brcmf_sdio { struct brcmf_sdio_dev *sdiodev; /* sdio device handler */ struct brcmf_chip *ci; /* Chip info struct */ struct brcmf_core *sdio_core; /* sdio core info struct */ u32 hostintmask; /* Copy of Host Interrupt Mask */ atomic_t intstatus; /* Intstatus bits (events) pending */ atomic_t fcstate; /* State of dongle flow-control */ uint blocksize; /* Block size of SDIO transfers */ uint roundup; /* Max roundup limit */ struct pktq txq; /* Queue length used for flow-control */ u8 flowcontrol; /* per prio flow control bitmask */ u8 tx_seq; /* Transmit sequence number (next) */ u8 tx_max; /* Maximum transmit sequence allowed */ u8 *hdrbuf; /* buffer for handling rx frame */ u8 *rxhdr; /* Header of current rx frame (in hdrbuf) */ u8 rx_seq; /* Receive sequence number (expected) */ struct brcmf_sdio_hdrinfo cur_read; /* info of current read frame */ bool rxskip; /* Skip receive (awaiting NAK ACK) */ bool rxpending; /* Data frame pending in dongle */ uint rxbound; /* Rx frames to read before resched */ uint txbound; /* Tx frames to send before resched */ uint txminmax; struct sk_buff *glomd; /* Packet containing glomming descriptor */ struct sk_buff_head glom; /* Packet list for glommed superframe */ u8 *rxbuf; /* Buffer for receiving control packets */ uint rxblen; /* Allocated length of rxbuf */ u8 *rxctl; /* Aligned pointer into rxbuf */ u8 *rxctl_orig; /* pointer for freeing rxctl */ uint rxlen; /* Length of valid data in buffer */ spinlock_t rxctl_lock; /* protection lock for ctrl frame resources */ u8 sdpcm_ver; /* Bus protocol reported by dongle */ bool intr; /* Use interrupts */ bool poll; /* Use polling */ atomic_t ipend; /* Device interrupt is pending */ uint spurious; /* Count of spurious interrupts */ uint pollrate; /* Ticks between device polls */ uint polltick; /* Tick counter */ #ifdef DEBUG uint console_interval; struct brcmf_console console; /* Console output polling support */ uint console_addr; /* Console address from shared struct */ #endif /* DEBUG */ uint clkstate; /* State of sd and backplane clock(s) */ s32 idletime; /* Control for activity timeout */ s32 idlecount; /* Activity timeout counter */ s32 idleclock; /* How to set bus driver when idle */ bool rxflow_mode; /* Rx flow control mode */ bool rxflow; /* Is rx flow control on */ bool alp_only; /* Don't use HT clock (ALP only) */ u8 *ctrl_frame_buf; u16 ctrl_frame_len; bool ctrl_frame_stat; int ctrl_frame_err; spinlock_t txq_lock; /* protect bus->txq */ wait_queue_head_t ctrl_wait; wait_queue_head_t dcmd_resp_wait; struct timer_list timer; struct completion watchdog_wait; struct task_struct *watchdog_tsk; bool wd_active; struct workqueue_struct *brcmf_wq; struct work_struct datawork; bool dpc_triggered; bool dpc_running; bool txoff; /* Transmit flow-controlled */ struct brcmf_sdio_count sdcnt; bool sr_enabled; /* SaveRestore enabled */ bool sleeping; u8 tx_hdrlen; /* sdio bus header length for tx packet */ bool txglom; /* host tx glomming enable flag */ u16 head_align; /* buffer pointer alignment */ u16 sgentry_align; /* scatter-gather buffer alignment */ }; /* clkstate */ #define CLK_NONE 0 #define CLK_SDONLY 1 #define CLK_PENDING 2 #define CLK_AVAIL 3 #ifdef DEBUG static int qcount[NUMPRIO]; #endif /* DEBUG */ #define DEFAULT_SDIO_DRIVE_STRENGTH 6 /* in milliamps */ #define RETRYCHAN(chan) ((chan) == SDPCM_EVENT_CHANNEL) /* Limit on rounding up frames */ static const uint max_roundup = 512; #ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT #define ALIGNMENT 8 #else #define ALIGNMENT 4 #endif enum brcmf_sdio_frmtype { BRCMF_SDIO_FT_NORMAL, BRCMF_SDIO_FT_SUPER, BRCMF_SDIO_FT_SUB, }; #define SDIOD_DRVSTR_KEY(chip, pmu) (((unsigned int)(chip) << 16) | (pmu)) /* SDIO Pad drive strength to select value mappings */ struct sdiod_drive_str { u8 strength; /* Pad Drive Strength in mA */ u8 sel; /* Chip-specific select value */ }; /* SDIO Drive Strength to sel value table for PMU Rev 11 (1.8V) */ static const struct sdiod_drive_str sdiod_drvstr_tab1_1v8[] = { {32, 0x6}, {26, 0x7}, {22, 0x4}, {16, 0x5}, {12, 0x2}, {8, 0x3}, {4, 0x0}, {0, 0x1} }; /* SDIO Drive Strength to sel value table for PMU Rev 13 (1.8v) */ static const struct sdiod_drive_str sdiod_drive_strength_tab5_1v8[] = { {6, 0x7}, {5, 0x6}, {4, 0x5}, {3, 0x4}, {2, 0x2}, {1, 0x1}, {0, 0x0} }; /* SDIO Drive Strength to sel value table for PMU Rev 17 (1.8v) */ static const struct sdiod_drive_str sdiod_drvstr_tab6_1v8[] = { {3, 0x3}, {2, 0x2}, {1, 0x1}, {0, 0x0} }; /* SDIO Drive Strength to sel value table for 43143 PMU Rev 17 (3.3V) */ static const struct sdiod_drive_str sdiod_drvstr_tab2_3v3[] = { {16, 0x7}, {12, 0x5}, {8, 0x3}, {4, 0x1} }; BRCMF_FW_DEF(43143, "brcmfmac43143-sdio"); BRCMF_FW_DEF(43241B0, "brcmfmac43241b0-sdio"); BRCMF_FW_DEF(43241B4, "brcmfmac43241b4-sdio"); BRCMF_FW_DEF(43241B5, "brcmfmac43241b5-sdio"); BRCMF_FW_DEF(4329, "brcmfmac4329-sdio"); BRCMF_FW_DEF(4330, "brcmfmac4330-sdio"); BRCMF_FW_DEF(4334, "brcmfmac4334-sdio"); BRCMF_FW_DEF(43340, "brcmfmac43340-sdio"); BRCMF_FW_DEF(4335, "brcmfmac4335-sdio"); BRCMF_FW_DEF(43362, "brcmfmac43362-sdio"); BRCMF_FW_DEF(4339, "brcmfmac4339-sdio"); BRCMF_FW_DEF(43430A0, "brcmfmac43430a0-sdio"); /* Note the names are not postfixed with a1 for backward compatibility */ BRCMF_FW_CLM_DEF(43430A1, "brcmfmac43430-sdio"); BRCMF_FW_DEF(43430B0, "brcmfmac43430b0-sdio"); BRCMF_FW_CLM_DEF(43439, "brcmfmac43439-sdio"); BRCMF_FW_CLM_DEF(43455, "brcmfmac43455-sdio"); BRCMF_FW_DEF(43456, "brcmfmac43456-sdio"); BRCMF_FW_CLM_DEF(4354, "brcmfmac4354-sdio"); BRCMF_FW_CLM_DEF(4356, "brcmfmac4356-sdio"); BRCMF_FW_DEF(4359, "brcmfmac4359-sdio"); BRCMF_FW_CLM_DEF(4373, "brcmfmac4373-sdio"); BRCMF_FW_CLM_DEF(43012, "brcmfmac43012-sdio"); BRCMF_FW_CLM_DEF(43752, "brcmfmac43752-sdio"); /* firmware config files */ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-sdio.*.txt"); /* per-board firmware binaries */ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-sdio.*.bin"); static const struct brcmf_firmware_mapping brcmf_sdio_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_43143_CHIP_ID, 0xFFFFFFFF, 43143), BRCMF_FW_ENTRY(BRCM_CC_43241_CHIP_ID, 0x0000001F, 43241B0), BRCMF_FW_ENTRY(BRCM_CC_43241_CHIP_ID, 0x00000020, 43241B4), BRCMF_FW_ENTRY(BRCM_CC_43241_CHIP_ID, 0xFFFFFFC0, 43241B5), BRCMF_FW_ENTRY(BRCM_CC_4329_CHIP_ID, 0xFFFFFFFF, 4329), BRCMF_FW_ENTRY(BRCM_CC_4330_CHIP_ID, 0xFFFFFFFF, 4330), BRCMF_FW_ENTRY(BRCM_CC_4334_CHIP_ID, 0xFFFFFFFF, 4334), BRCMF_FW_ENTRY(BRCM_CC_43340_CHIP_ID, 0xFFFFFFFF, 43340), BRCMF_FW_ENTRY(BRCM_CC_43341_CHIP_ID, 0xFFFFFFFF, 43340), BRCMF_FW_ENTRY(BRCM_CC_4335_CHIP_ID, 0xFFFFFFFF, 4335), BRCMF_FW_ENTRY(BRCM_CC_43362_CHIP_ID, 0xFFFFFFFE, 43362), BRCMF_FW_ENTRY(BRCM_CC_4339_CHIP_ID, 0xFFFFFFFF, 4339), BRCMF_FW_ENTRY(BRCM_CC_43430_CHIP_ID, 0x00000001, 43430A0), BRCMF_FW_ENTRY(BRCM_CC_43430_CHIP_ID, 0x00000002, 43430A1), BRCMF_FW_ENTRY(BRCM_CC_43430_CHIP_ID, 0xFFFFFFFC, 43430B0), BRCMF_FW_ENTRY(BRCM_CC_4345_CHIP_ID, 0x00000200, 43456), BRCMF_FW_ENTRY(BRCM_CC_4345_CHIP_ID, 0xFFFFFDC0, 43455), BRCMF_FW_ENTRY(BRCM_CC_43454_CHIP_ID, 0x00000040, 43455), BRCMF_FW_ENTRY(BRCM_CC_4354_CHIP_ID, 0xFFFFFFFF, 4354), BRCMF_FW_ENTRY(BRCM_CC_4356_CHIP_ID, 0xFFFFFFFF, 4356), BRCMF_FW_ENTRY(BRCM_CC_4359_CHIP_ID, 0xFFFFFFFF, 4359), BRCMF_FW_ENTRY(BRCM_CC_43751_CHIP_ID, 0xFFFFFFFF, 43752), + BRCMF_FW_ENTRY(BRCM_CC_43752_CHIP_ID, 0xFFFFFFFF, 43752), BRCMF_FW_ENTRY(CY_CC_4373_CHIP_ID, 0xFFFFFFFF, 4373), BRCMF_FW_ENTRY(CY_CC_43012_CHIP_ID, 0xFFFFFFFF, 43012), BRCMF_FW_ENTRY(CY_CC_43439_CHIP_ID, 0xFFFFFFFF, 43439), - BRCMF_FW_ENTRY(CY_CC_43752_CHIP_ID, 0xFFFFFFFF, 43752) }; #define TXCTL_CREDITS 2 static void pkt_align(struct sk_buff *p, int len, int align) { uint datalign; datalign = (unsigned long)(p->data); datalign = roundup(datalign, (align)) - datalign; if (datalign) skb_pull(p, datalign); __skb_trim(p, len); } /* To check if there's window offered */ static bool data_ok(struct brcmf_sdio *bus) { u8 tx_rsv = 0; /* Reserve TXCTL_CREDITS credits for txctl when it is ready to send */ if (bus->ctrl_frame_stat) tx_rsv = TXCTL_CREDITS; return (bus->tx_max - bus->tx_seq - tx_rsv) != 0 && ((bus->tx_max - bus->tx_seq - tx_rsv) & 0x80) == 0; } /* To check if there's window offered */ static bool txctl_ok(struct brcmf_sdio *bus) { return (bus->tx_max - bus->tx_seq) != 0 && ((bus->tx_max - bus->tx_seq) & 0x80) == 0; } static int brcmf_sdio_kso_control(struct brcmf_sdio *bus, bool on) { u8 wr_val = 0, rd_val, cmp_val, bmask; int err = 0; int err_cnt = 0; int try_cnt = 0; brcmf_dbg(TRACE, "Enter: on=%d\n", on); sdio_retune_crc_disable(bus->sdiodev->func1); /* Cannot re-tune if device is asleep; defer till we're awake */ if (on) sdio_retune_hold_now(bus->sdiodev->func1); wr_val = (on << SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT); /* 1st KSO write goes to AOS wake up core if device is asleep */ brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, wr_val, &err); /* In case of 43012 chip, the chip could go down immediately after * KSO bit is cleared. So the further reads of KSO register could * fail. Thereby just bailing out immediately after clearing KSO * bit, to avoid polling of KSO bit. */ if (!on && bus->ci->chip == CY_CC_43012_CHIP_ID) return err; if (on) { /* device WAKEUP through KSO: * write bit 0 & read back until * both bits 0 (kso bit) & 1 (dev on status) are set */ cmp_val = SBSDIO_FUNC1_SLEEPCSR_KSO_MASK | SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK; bmask = cmp_val; usleep_range(2000, 3000); } else { /* Put device to sleep, turn off KSO */ cmp_val = 0; /* only check for bit0, bit1(dev on status) may not * get cleared right away */ bmask = SBSDIO_FUNC1_SLEEPCSR_KSO_MASK; } do { /* reliable KSO bit set/clr: * the sdiod sleep write access is synced to PMU 32khz clk * just one write attempt may fail, * read it back until it matches written value */ rd_val = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, &err); if (!err) { if ((rd_val & bmask) == cmp_val) break; err_cnt = 0; } /* bail out upon subsequent access errors */ if (err && (err_cnt++ > BRCMF_SDIO_MAX_ACCESS_ERRORS)) break; udelay(KSO_WAIT_US); brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, wr_val, &err); } while (try_cnt++ < MAX_KSO_ATTEMPTS); if (try_cnt > 2) brcmf_dbg(SDIO, "try_cnt=%d rd_val=0x%x err=%d\n", try_cnt, rd_val, err); if (try_cnt > MAX_KSO_ATTEMPTS) brcmf_err("max tries: rd_val=0x%x err=%d\n", rd_val, err); if (on) sdio_retune_release(bus->sdiodev->func1); sdio_retune_crc_enable(bus->sdiodev->func1); return err; } #define HOSTINTMASK (I_HMB_SW_MASK | I_CHIPACTIVE) /* Turn backplane clock on or off */ static int brcmf_sdio_htclk(struct brcmf_sdio *bus, bool on, bool pendok) { int err; u8 clkctl, clkreq, devctl; unsigned long timeout; brcmf_dbg(SDIO, "Enter\n"); clkctl = 0; if (bus->sr_enabled) { bus->clkstate = (on ? CLK_AVAIL : CLK_SDONLY); return 0; } if (on) { /* Request HT Avail */ clkreq = bus->alp_only ? SBSDIO_ALP_AVAIL_REQ : SBSDIO_HT_AVAIL_REQ; brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, clkreq, &err); if (err) { brcmf_err("HT Avail request error: %d\n", err); return -EBADE; } /* Check current status */ clkctl = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, &err); if (err) { brcmf_err("HT Avail read error: %d\n", err); return -EBADE; } /* Go to pending and await interrupt if appropriate */ if (!SBSDIO_CLKAV(clkctl, bus->alp_only) && pendok) { /* Allow only clock-available interrupt */ devctl = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_DEVICE_CTL, &err); if (err) { brcmf_err("Devctl error setting CA: %d\n", err); return -EBADE; } devctl |= SBSDIO_DEVCTL_CA_INT_ONLY; brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_DEVICE_CTL, devctl, &err); brcmf_dbg(SDIO, "CLKCTL: set PENDING\n"); bus->clkstate = CLK_PENDING; return 0; } else if (bus->clkstate == CLK_PENDING) { /* Cancel CA-only interrupt filter */ devctl = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_DEVICE_CTL, &err); devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_DEVICE_CTL, devctl, &err); } /* Otherwise, wait here (polling) for HT Avail */ timeout = jiffies + msecs_to_jiffies(PMU_MAX_TRANSITION_DLY/1000); while (!SBSDIO_CLKAV(clkctl, bus->alp_only)) { clkctl = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, &err); if (time_after(jiffies, timeout)) break; else usleep_range(5000, 10000); } if (err) { brcmf_err("HT Avail request error: %d\n", err); return -EBADE; } if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) { brcmf_err("HT Avail timeout (%d): clkctl 0x%02x\n", PMU_MAX_TRANSITION_DLY, clkctl); return -EBADE; } /* Mark clock available */ bus->clkstate = CLK_AVAIL; brcmf_dbg(SDIO, "CLKCTL: turned ON\n"); #if defined(DEBUG) if (!bus->alp_only) { if (SBSDIO_ALPONLY(clkctl)) brcmf_err("HT Clock should be on\n"); } #endif /* defined (DEBUG) */ } else { clkreq = 0; if (bus->clkstate == CLK_PENDING) { /* Cancel CA-only interrupt filter */ devctl = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_DEVICE_CTL, &err); devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_DEVICE_CTL, devctl, &err); } bus->clkstate = CLK_SDONLY; brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, clkreq, &err); brcmf_dbg(SDIO, "CLKCTL: turned OFF\n"); if (err) { brcmf_err("Failed access turning clock off: %d\n", err); return -EBADE; } } return 0; } /* Change idle/active SD state */ static int brcmf_sdio_sdclk(struct brcmf_sdio *bus, bool on) { brcmf_dbg(SDIO, "Enter\n"); if (on) bus->clkstate = CLK_SDONLY; else bus->clkstate = CLK_NONE; return 0; } /* Transition SD and backplane clock readiness */ static int brcmf_sdio_clkctl(struct brcmf_sdio *bus, uint target, bool pendok) { #ifdef DEBUG uint oldstate = bus->clkstate; #endif /* DEBUG */ brcmf_dbg(SDIO, "Enter\n"); /* Early exit if we're already there */ if (bus->clkstate == target) return 0; switch (target) { case CLK_AVAIL: /* Make sure SD clock is available */ if (bus->clkstate == CLK_NONE) brcmf_sdio_sdclk(bus, true); /* Now request HT Avail on the backplane */ brcmf_sdio_htclk(bus, true, pendok); break; case CLK_SDONLY: /* Remove HT request, or bring up SD clock */ if (bus->clkstate == CLK_NONE) brcmf_sdio_sdclk(bus, true); else if (bus->clkstate == CLK_AVAIL) brcmf_sdio_htclk(bus, false, false); else brcmf_err("request for %d -> %d\n", bus->clkstate, target); break; case CLK_NONE: /* Make sure to remove HT request */ if (bus->clkstate == CLK_AVAIL) brcmf_sdio_htclk(bus, false, false); /* Now remove the SD clock */ brcmf_sdio_sdclk(bus, false); break; } #ifdef DEBUG brcmf_dbg(SDIO, "%d -> %d\n", oldstate, bus->clkstate); #endif /* DEBUG */ return 0; } static int brcmf_sdio_bus_sleep(struct brcmf_sdio *bus, bool sleep, bool pendok) { int err = 0; u8 clkcsr; brcmf_dbg(SDIO, "Enter: request %s currently %s\n", (sleep ? "SLEEP" : "WAKE"), (bus->sleeping ? "SLEEP" : "WAKE")); /* If SR is enabled control bus state with KSO */ if (bus->sr_enabled) { /* Done if we're already in the requested state */ if (sleep == bus->sleeping) goto end; /* Going to sleep */ if (sleep) { clkcsr = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, &err); if ((clkcsr & SBSDIO_CSR_MASK) == 0) { brcmf_dbg(SDIO, "no clock, set ALP\n"); brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, SBSDIO_ALP_AVAIL_REQ, &err); } err = brcmf_sdio_kso_control(bus, false); } else { err = brcmf_sdio_kso_control(bus, true); } if (err) { brcmf_err("error while changing bus sleep state %d\n", err); goto done; } } end: /* control clocks */ if (sleep) { if (!bus->sr_enabled) brcmf_sdio_clkctl(bus, CLK_NONE, pendok); } else { brcmf_sdio_clkctl(bus, CLK_AVAIL, pendok); brcmf_sdio_wd_timer(bus, true); } bus->sleeping = sleep; brcmf_dbg(SDIO, "new state %s\n", (sleep ? "SLEEP" : "WAKE")); done: brcmf_dbg(SDIO, "Exit: err=%d\n", err); return err; } #ifdef DEBUG static inline bool brcmf_sdio_valid_shared_address(u32 addr) { return !(addr == 0 || ((~addr >> 16) & 0xffff) == (addr & 0xffff)); } static int brcmf_sdio_readshared(struct brcmf_sdio *bus, struct sdpcm_shared *sh) { u32 addr = 0; int rv; u32 shaddr = 0; struct sdpcm_shared_le sh_le; __le32 addr_le; sdio_claim_host(bus->sdiodev->func1); brcmf_sdio_bus_sleep(bus, false, false); /* * Read last word in socram to determine * address of sdpcm_shared structure */ shaddr = bus->ci->rambase + bus->ci->ramsize - 4; if (!bus->ci->rambase && brcmf_chip_sr_capable(bus->ci)) shaddr -= bus->ci->srsize; rv = brcmf_sdiod_ramrw(bus->sdiodev, false, shaddr, (u8 *)&addr_le, 4); if (rv < 0) goto fail; /* * Check if addr is valid. * NVRAM length at the end of memory should have been overwritten. */ addr = le32_to_cpu(addr_le); if (!brcmf_sdio_valid_shared_address(addr)) { brcmf_err("invalid sdpcm_shared address 0x%08X\n", addr); rv = -EINVAL; goto fail; } brcmf_dbg(INFO, "sdpcm_shared address 0x%08X\n", addr); /* Read hndrte_shared structure */ rv = brcmf_sdiod_ramrw(bus->sdiodev, false, addr, (u8 *)&sh_le, sizeof(struct sdpcm_shared_le)); if (rv < 0) goto fail; sdio_release_host(bus->sdiodev->func1); /* Endianness */ sh->flags = le32_to_cpu(sh_le.flags); sh->trap_addr = le32_to_cpu(sh_le.trap_addr); sh->assert_exp_addr = le32_to_cpu(sh_le.assert_exp_addr); sh->assert_file_addr = le32_to_cpu(sh_le.assert_file_addr); sh->assert_line = le32_to_cpu(sh_le.assert_line); sh->console_addr = le32_to_cpu(sh_le.console_addr); sh->msgtrace_addr = le32_to_cpu(sh_le.msgtrace_addr); if ((sh->flags & SDPCM_SHARED_VERSION_MASK) > SDPCM_SHARED_VERSION) { brcmf_err("sdpcm shared version unsupported: dhd %d dongle %d\n", SDPCM_SHARED_VERSION, sh->flags & SDPCM_SHARED_VERSION_MASK); return -EPROTO; } return 0; fail: brcmf_err("unable to obtain sdpcm_shared info: rv=%d (addr=0x%x)\n", rv, addr); sdio_release_host(bus->sdiodev->func1); return rv; } static void brcmf_sdio_get_console_addr(struct brcmf_sdio *bus) { struct sdpcm_shared sh; if (brcmf_sdio_readshared(bus, &sh) == 0) bus->console_addr = sh.console_addr; } #else static void brcmf_sdio_get_console_addr(struct brcmf_sdio *bus) { } #endif /* DEBUG */ static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus) { struct brcmf_sdio_dev *sdiod = bus->sdiodev; struct brcmf_core *core = bus->sdio_core; u32 intstatus = 0; u32 hmb_data; u8 fcbits; int ret; brcmf_dbg(SDIO, "Enter\n"); /* Read mailbox data and ack that we did so */ hmb_data = brcmf_sdiod_readl(sdiod, core->base + SD_REG(tohostmailboxdata), &ret); if (!ret) brcmf_sdiod_writel(sdiod, core->base + SD_REG(tosbmailbox), SMB_INT_ACK, &ret); bus->sdcnt.f1regdata += 2; /* dongle indicates the firmware has halted/crashed */ if (hmb_data & HMB_DATA_FWHALT) { brcmf_dbg(SDIO, "mailbox indicates firmware halted\n"); brcmf_fw_crashed(&sdiod->func1->dev); } /* Dongle recomposed rx frames, accept them again */ if (hmb_data & HMB_DATA_NAKHANDLED) { brcmf_dbg(SDIO, "Dongle reports NAK handled, expect rtx of %d\n", bus->rx_seq); if (!bus->rxskip) brcmf_err("unexpected NAKHANDLED!\n"); bus->rxskip = false; intstatus |= I_HMB_FRAME_IND; } /* * DEVREADY does not occur with gSPI. */ if (hmb_data & (HMB_DATA_DEVREADY | HMB_DATA_FWREADY)) { bus->sdpcm_ver = (hmb_data & HMB_DATA_VERSION_MASK) >> HMB_DATA_VERSION_SHIFT; if (bus->sdpcm_ver != SDPCM_PROT_VERSION) brcmf_err("Version mismatch, dongle reports %d, " "expecting %d\n", bus->sdpcm_ver, SDPCM_PROT_VERSION); else brcmf_dbg(SDIO, "Dongle ready, protocol version %d\n", bus->sdpcm_ver); /* * Retrieve console state address now that firmware should have * updated it. */ brcmf_sdio_get_console_addr(bus); } /* * Flow Control has been moved into the RX headers and this out of band * method isn't used any more. * remaining backward compatible with older dongles. */ if (hmb_data & HMB_DATA_FC) { fcbits = (hmb_data & HMB_DATA_FCDATA_MASK) >> HMB_DATA_FCDATA_SHIFT; if (fcbits & ~bus->flowcontrol) bus->sdcnt.fc_xoff++; if (bus->flowcontrol & ~fcbits) bus->sdcnt.fc_xon++; bus->sdcnt.fc_rcvd++; bus->flowcontrol = fcbits; } /* Shouldn't be any others */ if (hmb_data & ~(HMB_DATA_DEVREADY | HMB_DATA_NAKHANDLED | HMB_DATA_FC | HMB_DATA_FWREADY | HMB_DATA_FWHALT | HMB_DATA_FCDATA_MASK | HMB_DATA_VERSION_MASK)) brcmf_err("Unknown mailbox data content: 0x%02x\n", hmb_data); return intstatus; } static void brcmf_sdio_rxfail(struct brcmf_sdio *bus, bool abort, bool rtx) { struct brcmf_sdio_dev *sdiod = bus->sdiodev; struct brcmf_core *core = bus->sdio_core; uint retries = 0; u16 lastrbc; u8 hi, lo; int err; brcmf_err("%sterminate frame%s\n", abort ? "abort command, " : "", rtx ? ", send NAK" : ""); if (abort) brcmf_sdiod_abort(bus->sdiodev, bus->sdiodev->func2); brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_FRAMECTRL, SFC_RF_TERM, &err); bus->sdcnt.f1regdata++; /* Wait until the packet has been flushed (device/FIFO stable) */ for (lastrbc = retries = 0xffff; retries > 0; retries--) { hi = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_RFRAMEBCHI, &err); lo = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_RFRAMEBCLO, &err); bus->sdcnt.f1regdata += 2; if ((hi == 0) && (lo == 0)) break; if ((hi > (lastrbc >> 8)) && (lo > (lastrbc & 0x00ff))) { brcmf_err("count growing: last 0x%04x now 0x%04x\n", lastrbc, (hi << 8) + lo); } lastrbc = (hi << 8) + lo; } if (!retries) brcmf_err("count never zeroed: last 0x%04x\n", lastrbc); else brcmf_dbg(SDIO, "flush took %d iterations\n", 0xffff - retries); if (rtx) { bus->sdcnt.rxrtx++; brcmf_sdiod_writel(sdiod, core->base + SD_REG(tosbmailbox), SMB_NAK, &err); bus->sdcnt.f1regdata++; if (err == 0) bus->rxskip = true; } /* Clear partial in any case */ bus->cur_read.len = 0; } static void brcmf_sdio_txfail(struct brcmf_sdio *bus) { struct brcmf_sdio_dev *sdiodev = bus->sdiodev; u8 i, hi, lo; /* On failure, abort the command and terminate the frame */ brcmf_err("sdio error, abort command and terminate frame\n"); bus->sdcnt.tx_sderrs++; brcmf_sdiod_abort(sdiodev, sdiodev->func2); brcmf_sdiod_writeb(sdiodev, SBSDIO_FUNC1_FRAMECTRL, SFC_WF_TERM, NULL); bus->sdcnt.f1regdata++; for (i = 0; i < 3; i++) { hi = brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_WFRAMEBCHI, NULL); lo = brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_WFRAMEBCLO, NULL); bus->sdcnt.f1regdata += 2; if ((hi == 0) && (lo == 0)) break; } } /* return total length of buffer chain */ static uint brcmf_sdio_glom_len(struct brcmf_sdio *bus) { struct sk_buff *p; uint total; total = 0; skb_queue_walk(&bus->glom, p) total += p->len; return total; } static void brcmf_sdio_free_glom(struct brcmf_sdio *bus) { struct sk_buff *cur, *next; skb_queue_walk_safe(&bus->glom, cur, next) { skb_unlink(cur, &bus->glom); brcmu_pkt_buf_free_skb(cur); } } /* * brcmfmac sdio bus specific header * This is the lowest layer header wrapped on the packets transmitted between * host and WiFi dongle which contains information needed for SDIO core and * firmware * * It consists of 3 parts: hardware header, hardware extension header and * software header * hardware header (frame tag) - 4 bytes * Byte 0~1: Frame length * Byte 2~3: Checksum, bit-wise inverse of frame length * hardware extension header - 8 bytes * Tx glom mode only, N/A for Rx or normal Tx * Byte 0~1: Packet length excluding hw frame tag * Byte 2: Reserved * Byte 3: Frame flags, bit 0: last frame indication * Byte 4~5: Reserved * Byte 6~7: Tail padding length * software header - 8 bytes * Byte 0: Rx/Tx sequence number * Byte 1: 4 MSB Channel number, 4 LSB arbitrary flag * Byte 2: Length of next data frame, reserved for Tx * Byte 3: Data offset * Byte 4: Flow control bits, reserved for Tx * Byte 5: Maximum Sequence number allowed by firmware for Tx, N/A for Tx packet * Byte 6~7: Reserved */ #define SDPCM_HWHDR_LEN 4 #define SDPCM_HWEXT_LEN 8 #define SDPCM_SWHDR_LEN 8 #define SDPCM_HDRLEN (SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN) /* software header */ #define SDPCM_SEQ_MASK 0x000000ff #define SDPCM_SEQ_WRAP 256 #define SDPCM_CHANNEL_MASK 0x00000f00 #define SDPCM_CHANNEL_SHIFT 8 #define SDPCM_CONTROL_CHANNEL 0 /* Control */ #define SDPCM_EVENT_CHANNEL 1 /* Asyc Event Indication */ #define SDPCM_DATA_CHANNEL 2 /* Data Xmit/Recv */ #define SDPCM_GLOM_CHANNEL 3 /* Coalesced packets */ #define SDPCM_TEST_CHANNEL 15 /* Test/debug packets */ #define SDPCM_GLOMDESC(p) (((u8 *)p)[1] & 0x80) #define SDPCM_NEXTLEN_MASK 0x00ff0000 #define SDPCM_NEXTLEN_SHIFT 16 #define SDPCM_DOFFSET_MASK 0xff000000 #define SDPCM_DOFFSET_SHIFT 24 #define SDPCM_FCMASK_MASK 0x000000ff #define SDPCM_WINDOW_MASK 0x0000ff00 #define SDPCM_WINDOW_SHIFT 8 static inline u8 brcmf_sdio_getdatoffset(u8 *swheader) { u32 hdrvalue; hdrvalue = le32_to_cpu(*(__le32 *)swheader); return (u8)((hdrvalue & SDPCM_DOFFSET_MASK) >> SDPCM_DOFFSET_SHIFT); } static inline bool brcmf_sdio_fromevntchan(u8 *swheader) { u32 hdrvalue; u8 ret; hdrvalue = le32_to_cpu(*(__le32 *)swheader); ret = (u8)((hdrvalue & SDPCM_CHANNEL_MASK) >> SDPCM_CHANNEL_SHIFT); return (ret == SDPCM_EVENT_CHANNEL); } static int brcmf_sdio_hdparse(struct brcmf_sdio *bus, u8 *header, struct brcmf_sdio_hdrinfo *rd, enum brcmf_sdio_frmtype type) { u16 len, checksum; u8 rx_seq, fc, tx_seq_max; u32 swheader; trace_brcmf_sdpcm_hdr(SDPCM_RX, header); /* hw header */ len = get_unaligned_le16(header); checksum = get_unaligned_le16(header + sizeof(u16)); /* All zero means no more to read */ if (!(len | checksum)) { bus->rxpending = false; return -ENODATA; } if ((u16)(~(len ^ checksum))) { brcmf_err("HW header checksum error\n"); bus->sdcnt.rx_badhdr++; brcmf_sdio_rxfail(bus, false, false); return -EIO; } if (len < SDPCM_HDRLEN) { brcmf_err("HW header length error\n"); return -EPROTO; } if (type == BRCMF_SDIO_FT_SUPER && (roundup(len, bus->blocksize) != rd->len)) { brcmf_err("HW superframe header length error\n"); return -EPROTO; } if (type == BRCMF_SDIO_FT_SUB && len > rd->len) { brcmf_err("HW subframe header length error\n"); return -EPROTO; } rd->len = len; /* software header */ header += SDPCM_HWHDR_LEN; swheader = le32_to_cpu(*(__le32 *)header); if (type == BRCMF_SDIO_FT_SUPER && SDPCM_GLOMDESC(header)) { brcmf_err("Glom descriptor found in superframe head\n"); rd->len = 0; return -EINVAL; } rx_seq = (u8)(swheader & SDPCM_SEQ_MASK); rd->channel = (swheader & SDPCM_CHANNEL_MASK) >> SDPCM_CHANNEL_SHIFT; if (len > MAX_RX_DATASZ && rd->channel != SDPCM_CONTROL_CHANNEL && type != BRCMF_SDIO_FT_SUPER) { brcmf_err("HW header length too long\n"); bus->sdcnt.rx_toolong++; brcmf_sdio_rxfail(bus, false, false); rd->len = 0; return -EPROTO; } if (type == BRCMF_SDIO_FT_SUPER && rd->channel != SDPCM_GLOM_CHANNEL) { brcmf_err("Wrong channel for superframe\n"); rd->len = 0; return -EINVAL; } if (type == BRCMF_SDIO_FT_SUB && rd->channel != SDPCM_DATA_CHANNEL && rd->channel != SDPCM_EVENT_CHANNEL) { brcmf_err("Wrong channel for subframe\n"); rd->len = 0; return -EINVAL; } rd->dat_offset = brcmf_sdio_getdatoffset(header); if (rd->dat_offset < SDPCM_HDRLEN || rd->dat_offset > rd->len) { brcmf_err("seq %d: bad data offset\n", rx_seq); bus->sdcnt.rx_badhdr++; brcmf_sdio_rxfail(bus, false, false); rd->len = 0; return -ENXIO; } if (rd->seq_num != rx_seq) { brcmf_dbg(SDIO, "seq %d, expected %d\n", rx_seq, rd->seq_num); bus->sdcnt.rx_badseq++; rd->seq_num = rx_seq; } /* no need to check the reset for subframe */ if (type == BRCMF_SDIO_FT_SUB) return 0; rd->len_nxtfrm = (swheader & SDPCM_NEXTLEN_MASK) >> SDPCM_NEXTLEN_SHIFT; if (rd->len_nxtfrm << 4 > MAX_RX_DATASZ) { /* only warm for NON glom packet */ if (rd->channel != SDPCM_GLOM_CHANNEL) brcmf_err("seq %d: next length error\n", rx_seq); rd->len_nxtfrm = 0; } swheader = le32_to_cpu(*(__le32 *)(header + 4)); fc = swheader & SDPCM_FCMASK_MASK; if (bus->flowcontrol != fc) { if (~bus->flowcontrol & fc) bus->sdcnt.fc_xoff++; if (bus->flowcontrol & ~fc) bus->sdcnt.fc_xon++; bus->sdcnt.fc_rcvd++; bus->flowcontrol = fc; } tx_seq_max = (swheader & SDPCM_WINDOW_MASK) >> SDPCM_WINDOW_SHIFT; if ((u8)(tx_seq_max - bus->tx_seq) > 0x40) { brcmf_err("seq %d: max tx seq number error\n", rx_seq); tx_seq_max = bus->tx_seq + 2; } bus->tx_max = tx_seq_max; return 0; } static inline void brcmf_sdio_update_hwhdr(u8 *header, u16 frm_length) { *(__le16 *)header = cpu_to_le16(frm_length); *(((__le16 *)header) + 1) = cpu_to_le16(~frm_length); } static void brcmf_sdio_hdpack(struct brcmf_sdio *bus, u8 *header, struct brcmf_sdio_hdrinfo *hd_info) { u32 hdrval; u8 hdr_offset; brcmf_sdio_update_hwhdr(header, hd_info->len); hdr_offset = SDPCM_HWHDR_LEN; if (bus->txglom) { hdrval = (hd_info->len - hdr_offset) | (hd_info->lastfrm << 24); *((__le32 *)(header + hdr_offset)) = cpu_to_le32(hdrval); hdrval = (u16)hd_info->tail_pad << 16; *(((__le32 *)(header + hdr_offset)) + 1) = cpu_to_le32(hdrval); hdr_offset += SDPCM_HWEXT_LEN; } hdrval = hd_info->seq_num; hdrval |= (hd_info->channel << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK; hdrval |= (hd_info->dat_offset << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK; *((__le32 *)(header + hdr_offset)) = cpu_to_le32(hdrval); *(((__le32 *)(header + hdr_offset)) + 1) = 0; trace_brcmf_sdpcm_hdr(SDPCM_TX + !!(bus->txglom), header); } static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq) { u16 dlen, totlen; u8 *dptr, num = 0; u16 sublen; struct sk_buff *pfirst, *pnext; int errcode; u8 doff; struct brcmf_sdio_hdrinfo rd_new; /* If packets, issue read(s) and send up packet chain */ /* Return sequence numbers consumed? */ brcmf_dbg(SDIO, "start: glomd %p glom %p\n", bus->glomd, skb_peek(&bus->glom)); /* If there's a descriptor, generate the packet chain */ if (bus->glomd) { pfirst = pnext = NULL; dlen = (u16) (bus->glomd->len); dptr = bus->glomd->data; if (!dlen || (dlen & 1)) { brcmf_err("bad glomd len(%d), ignore descriptor\n", dlen); dlen = 0; } for (totlen = num = 0; dlen; num++) { /* Get (and move past) next length */ sublen = get_unaligned_le16(dptr); dlen -= sizeof(u16); dptr += sizeof(u16); if ((sublen < SDPCM_HDRLEN) || ((num == 0) && (sublen < (2 * SDPCM_HDRLEN)))) { brcmf_err("descriptor len %d bad: %d\n", num, sublen); pnext = NULL; break; } if (sublen % bus->sgentry_align) { brcmf_err("sublen %d not multiple of %d\n", sublen, bus->sgentry_align); } totlen += sublen; /* For last frame, adjust read len so total is a block multiple */ if (!dlen) { sublen += (roundup(totlen, bus->blocksize) - totlen); totlen = roundup(totlen, bus->blocksize); } /* Allocate/chain packet for next subframe */ pnext = brcmu_pkt_buf_get_skb(sublen + bus->sgentry_align); if (pnext == NULL) { brcmf_err("bcm_pkt_buf_get_skb failed, num %d len %d\n", num, sublen); break; } skb_queue_tail(&bus->glom, pnext); /* Adhere to start alignment requirements */ pkt_align(pnext, sublen, bus->sgentry_align); } /* If all allocations succeeded, save packet chain in bus structure */ if (pnext) { brcmf_dbg(GLOM, "allocated %d-byte packet chain for %d subframes\n", totlen, num); if (BRCMF_GLOM_ON() && bus->cur_read.len && totlen != bus->cur_read.len) { brcmf_dbg(GLOM, "glomdesc mismatch: nextlen %d glomdesc %d rxseq %d\n", bus->cur_read.len, totlen, rxseq); } pfirst = pnext = NULL; } else { brcmf_sdio_free_glom(bus); num = 0; } /* Done with descriptor packet */ brcmu_pkt_buf_free_skb(bus->glomd); bus->glomd = NULL; bus->cur_read.len = 0; } /* Ok -- either we just generated a packet chain, or had one from before */ if (!skb_queue_empty(&bus->glom)) { if (BRCMF_GLOM_ON()) { brcmf_dbg(GLOM, "try superframe read, packet chain:\n"); skb_queue_walk(&bus->glom, pnext) { brcmf_dbg(GLOM, " %p: %p len 0x%04x (%d)\n", pnext, (u8 *) (pnext->data), pnext->len, pnext->len); } } pfirst = skb_peek(&bus->glom); dlen = (u16) brcmf_sdio_glom_len(bus); /* Do an SDIO read for the superframe. Configurable iovar to * read directly into the chained packet, or allocate a large * packet and copy into the chain. */ sdio_claim_host(bus->sdiodev->func1); errcode = brcmf_sdiod_recv_chain(bus->sdiodev, &bus->glom, dlen); sdio_release_host(bus->sdiodev->func1); bus->sdcnt.f2rxdata++; /* On failure, kill the superframe */ if (errcode < 0) { brcmf_err("glom read of %d bytes failed: %d\n", dlen, errcode); sdio_claim_host(bus->sdiodev->func1); brcmf_sdio_rxfail(bus, true, false); bus->sdcnt.rxglomfail++; brcmf_sdio_free_glom(bus); sdio_release_host(bus->sdiodev->func1); return 0; } brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), pfirst->data, min_t(int, pfirst->len, 48), "SUPERFRAME:\n"); rd_new.seq_num = rxseq; rd_new.len = dlen; sdio_claim_host(bus->sdiodev->func1); errcode = brcmf_sdio_hdparse(bus, pfirst->data, &rd_new, BRCMF_SDIO_FT_SUPER); sdio_release_host(bus->sdiodev->func1); bus->cur_read.len = rd_new.len_nxtfrm << 4; /* Remove superframe header, remember offset */ skb_pull(pfirst, rd_new.dat_offset); num = 0; /* Validate all the subframe headers */ skb_queue_walk(&bus->glom, pnext) { /* leave when invalid subframe is found */ if (errcode) break; rd_new.len = pnext->len; rd_new.seq_num = rxseq++; sdio_claim_host(bus->sdiodev->func1); errcode = brcmf_sdio_hdparse(bus, pnext->data, &rd_new, BRCMF_SDIO_FT_SUB); sdio_release_host(bus->sdiodev->func1); brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), pnext->data, 32, "subframe:\n"); num++; } if (errcode) { /* Terminate frame on error */ sdio_claim_host(bus->sdiodev->func1); brcmf_sdio_rxfail(bus, true, false); bus->sdcnt.rxglomfail++; brcmf_sdio_free_glom(bus); sdio_release_host(bus->sdiodev->func1); bus->cur_read.len = 0; return 0; } /* Basic SD framing looks ok - process each packet (header) */ skb_queue_walk_safe(&bus->glom, pfirst, pnext) { dptr = (u8 *) (pfirst->data); sublen = get_unaligned_le16(dptr); doff = brcmf_sdio_getdatoffset(&dptr[SDPCM_HWHDR_LEN]); brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && BRCMF_DATA_ON(), dptr, pfirst->len, "Rx Subframe Data:\n"); __skb_trim(pfirst, sublen); skb_pull(pfirst, doff); if (pfirst->len == 0) { skb_unlink(pfirst, &bus->glom); brcmu_pkt_buf_free_skb(pfirst); continue; } brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), pfirst->data, min_t(int, pfirst->len, 32), "subframe %d to stack, %p (%p/%d) nxt/lnk %p/%p\n", bus->glom.qlen, pfirst, pfirst->data, pfirst->len, pfirst->next, pfirst->prev); skb_unlink(pfirst, &bus->glom); if (brcmf_sdio_fromevntchan(&dptr[SDPCM_HWHDR_LEN])) brcmf_rx_event(bus->sdiodev->dev, pfirst); else brcmf_rx_frame(bus->sdiodev->dev, pfirst, false, false); bus->sdcnt.rxglompkts++; } bus->sdcnt.rxglomframes++; } return num; } static int brcmf_sdio_dcmd_resp_wait(struct brcmf_sdio *bus, uint *condition, bool *pending) { DECLARE_WAITQUEUE(wait, current); int timeout = DCMD_RESP_TIMEOUT; /* Wait until control frame is available */ add_wait_queue(&bus->dcmd_resp_wait, &wait); set_current_state(TASK_INTERRUPTIBLE); while (!(*condition) && (!signal_pending(current) && timeout)) timeout = schedule_timeout(timeout); if (signal_pending(current)) *pending = true; set_current_state(TASK_RUNNING); remove_wait_queue(&bus->dcmd_resp_wait, &wait); return timeout; } static int brcmf_sdio_dcmd_resp_wake(struct brcmf_sdio *bus) { wake_up_interruptible(&bus->dcmd_resp_wait); return 0; } static void brcmf_sdio_read_control(struct brcmf_sdio *bus, u8 *hdr, uint len, uint doff) { uint rdlen, pad; u8 *buf = NULL, *rbuf; int sdret; brcmf_dbg(SDIO, "Enter\n"); if (bus->rxblen) buf = vzalloc(bus->rxblen); if (!buf) goto done; rbuf = bus->rxbuf; pad = ((unsigned long)rbuf % bus->head_align); if (pad) rbuf += (bus->head_align - pad); /* Copy the already-read portion over */ memcpy(buf, hdr, BRCMF_FIRSTREAD); if (len <= BRCMF_FIRSTREAD) goto gotpkt; /* Raise rdlen to next SDIO block to avoid tail command */ rdlen = len - BRCMF_FIRSTREAD; if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) { pad = bus->blocksize - (rdlen % bus->blocksize); if ((pad <= bus->roundup) && (pad < bus->blocksize) && ((len + pad) < bus->sdiodev->bus_if->maxctl)) rdlen += pad; } else if (rdlen % bus->head_align) { rdlen += bus->head_align - (rdlen % bus->head_align); } /* Drop if the read is too big or it exceeds our maximum */ if ((rdlen + BRCMF_FIRSTREAD) > bus->sdiodev->bus_if->maxctl) { brcmf_err("%d-byte control read exceeds %d-byte buffer\n", rdlen, bus->sdiodev->bus_if->maxctl); brcmf_sdio_rxfail(bus, false, false); goto done; } if ((len - doff) > bus->sdiodev->bus_if->maxctl) { brcmf_err("%d-byte ctl frame (%d-byte ctl data) exceeds %d-byte limit\n", len, len - doff, bus->sdiodev->bus_if->maxctl); bus->sdcnt.rx_toolong++; brcmf_sdio_rxfail(bus, false, false); goto done; } /* Read remain of frame body */ sdret = brcmf_sdiod_recv_buf(bus->sdiodev, rbuf, rdlen); bus->sdcnt.f2rxdata++; /* Control frame failures need retransmission */ if (sdret < 0) { brcmf_err("read %d control bytes failed: %d\n", rdlen, sdret); bus->sdcnt.rxc_errors++; brcmf_sdio_rxfail(bus, true, true); goto done; } else memcpy(buf + BRCMF_FIRSTREAD, rbuf, rdlen); gotpkt: brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && BRCMF_CTL_ON(), buf, len, "RxCtrl:\n"); /* Point to valid data and indicate its length */ spin_lock_bh(&bus->rxctl_lock); if (bus->rxctl) { brcmf_err("last control frame is being processed.\n"); spin_unlock_bh(&bus->rxctl_lock); vfree(buf); goto done; } bus->rxctl = buf + doff; bus->rxctl_orig = buf; bus->rxlen = len - doff; spin_unlock_bh(&bus->rxctl_lock); done: /* Awake any waiters */ brcmf_sdio_dcmd_resp_wake(bus); } /* Pad read to blocksize for efficiency */ static void brcmf_sdio_pad(struct brcmf_sdio *bus, u16 *pad, u16 *rdlen) { if (bus->roundup && bus->blocksize && *rdlen > bus->blocksize) { *pad = bus->blocksize - (*rdlen % bus->blocksize); if (*pad <= bus->roundup && *pad < bus->blocksize && *rdlen + *pad + BRCMF_FIRSTREAD < MAX_RX_DATASZ) *rdlen += *pad; } else if (*rdlen % bus->head_align) { *rdlen += bus->head_align - (*rdlen % bus->head_align); } } static uint brcmf_sdio_readframes(struct brcmf_sdio *bus, uint maxframes) { struct sk_buff *pkt; /* Packet for event or data frames */ u16 pad; /* Number of pad bytes to read */ uint rxleft = 0; /* Remaining number of frames allowed */ int ret; /* Return code from calls */ uint rxcount = 0; /* Total frames read */ struct brcmf_sdio_hdrinfo *rd = &bus->cur_read, rd_new; u8 head_read = 0; brcmf_dbg(SDIO, "Enter\n"); /* Not finished unless we encounter no more frames indication */ bus->rxpending = true; for (rd->seq_num = bus->rx_seq, rxleft = maxframes; !bus->rxskip && rxleft && bus->sdiodev->state == BRCMF_SDIOD_DATA; rd->seq_num++, rxleft--) { /* Handle glomming separately */ if (bus->glomd || !skb_queue_empty(&bus->glom)) { u8 cnt; brcmf_dbg(GLOM, "calling rxglom: glomd %p, glom %p\n", bus->glomd, skb_peek(&bus->glom)); cnt = brcmf_sdio_rxglom(bus, rd->seq_num); brcmf_dbg(GLOM, "rxglom returned %d\n", cnt); rd->seq_num += cnt - 1; rxleft = (rxleft > cnt) ? (rxleft - cnt) : 1; continue; } rd->len_left = rd->len; /* read header first for unknown frame length */ sdio_claim_host(bus->sdiodev->func1); if (!rd->len) { ret = brcmf_sdiod_recv_buf(bus->sdiodev, bus->rxhdr, BRCMF_FIRSTREAD); bus->sdcnt.f2rxhdrs++; if (ret < 0) { brcmf_err("RXHEADER FAILED: %d\n", ret); bus->sdcnt.rx_hdrfail++; brcmf_sdio_rxfail(bus, true, true); sdio_release_host(bus->sdiodev->func1); continue; } brcmf_dbg_hex_dump(BRCMF_BYTES_ON() || BRCMF_HDRS_ON(), bus->rxhdr, SDPCM_HDRLEN, "RxHdr:\n"); if (brcmf_sdio_hdparse(bus, bus->rxhdr, rd, BRCMF_SDIO_FT_NORMAL)) { sdio_release_host(bus->sdiodev->func1); if (!bus->rxpending) break; else continue; } if (rd->channel == SDPCM_CONTROL_CHANNEL) { brcmf_sdio_read_control(bus, bus->rxhdr, rd->len, rd->dat_offset); /* prepare the descriptor for the next read */ rd->len = rd->len_nxtfrm << 4; rd->len_nxtfrm = 0; /* treat all packet as event if we don't know */ rd->channel = SDPCM_EVENT_CHANNEL; sdio_release_host(bus->sdiodev->func1); continue; } rd->len_left = rd->len > BRCMF_FIRSTREAD ? rd->len - BRCMF_FIRSTREAD : 0; head_read = BRCMF_FIRSTREAD; } brcmf_sdio_pad(bus, &pad, &rd->len_left); pkt = brcmu_pkt_buf_get_skb(rd->len_left + head_read + bus->head_align); if (!pkt) { /* Give up on data, request rtx of events */ brcmf_err("brcmu_pkt_buf_get_skb failed\n"); brcmf_sdio_rxfail(bus, false, RETRYCHAN(rd->channel)); sdio_release_host(bus->sdiodev->func1); continue; } skb_pull(pkt, head_read); pkt_align(pkt, rd->len_left, bus->head_align); ret = brcmf_sdiod_recv_pkt(bus->sdiodev, pkt); bus->sdcnt.f2rxdata++; sdio_release_host(bus->sdiodev->func1); if (ret < 0) { brcmf_err("read %d bytes from channel %d failed: %d\n", rd->len, rd->channel, ret); brcmu_pkt_buf_free_skb(pkt); sdio_claim_host(bus->sdiodev->func1); brcmf_sdio_rxfail(bus, true, RETRYCHAN(rd->channel)); sdio_release_host(bus->sdiodev->func1); continue; } if (head_read) { skb_push(pkt, head_read); memcpy(pkt->data, bus->rxhdr, head_read); head_read = 0; } else { memcpy(bus->rxhdr, pkt->data, SDPCM_HDRLEN); rd_new.seq_num = rd->seq_num; sdio_claim_host(bus->sdiodev->func1); if (brcmf_sdio_hdparse(bus, bus->rxhdr, &rd_new, BRCMF_SDIO_FT_NORMAL)) { rd->len = 0; brcmf_sdio_rxfail(bus, true, true); sdio_release_host(bus->sdiodev->func1); brcmu_pkt_buf_free_skb(pkt); continue; } bus->sdcnt.rx_readahead_cnt++; if (rd->len != roundup(rd_new.len, 16)) { brcmf_err("frame length mismatch:read %d, should be %d\n", rd->len, roundup(rd_new.len, 16) >> 4); rd->len = 0; brcmf_sdio_rxfail(bus, true, true); sdio_release_host(bus->sdiodev->func1); brcmu_pkt_buf_free_skb(pkt); continue; } sdio_release_host(bus->sdiodev->func1); rd->len_nxtfrm = rd_new.len_nxtfrm; rd->channel = rd_new.channel; rd->dat_offset = rd_new.dat_offset; brcmf_dbg_hex_dump(!(BRCMF_BYTES_ON() && BRCMF_DATA_ON()) && BRCMF_HDRS_ON(), bus->rxhdr, SDPCM_HDRLEN, "RxHdr:\n"); if (rd_new.channel == SDPCM_CONTROL_CHANNEL) { brcmf_err("readahead on control packet %d?\n", rd_new.seq_num); /* Force retry w/normal header read */ rd->len = 0; sdio_claim_host(bus->sdiodev->func1); brcmf_sdio_rxfail(bus, false, true); sdio_release_host(bus->sdiodev->func1); brcmu_pkt_buf_free_skb(pkt); continue; } } brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && BRCMF_DATA_ON(), pkt->data, rd->len, "Rx Data:\n"); /* Save superframe descriptor and allocate packet frame */ if (rd->channel == SDPCM_GLOM_CHANNEL) { if (SDPCM_GLOMDESC(&bus->rxhdr[SDPCM_HWHDR_LEN])) { brcmf_dbg(GLOM, "glom descriptor, %d bytes:\n", rd->len); brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), pkt->data, rd->len, "Glom Data:\n"); __skb_trim(pkt, rd->len); skb_pull(pkt, SDPCM_HDRLEN); bus->glomd = pkt; } else { brcmf_err("%s: glom superframe w/o " "descriptor!\n", __func__); sdio_claim_host(bus->sdiodev->func1); brcmf_sdio_rxfail(bus, false, false); sdio_release_host(bus->sdiodev->func1); } /* prepare the descriptor for the next read */ rd->len = rd->len_nxtfrm << 4; rd->len_nxtfrm = 0; /* treat all packet as event if we don't know */ rd->channel = SDPCM_EVENT_CHANNEL; continue; } /* Fill in packet len and prio, deliver upward */ __skb_trim(pkt, rd->len); skb_pull(pkt, rd->dat_offset); if (pkt->len == 0) brcmu_pkt_buf_free_skb(pkt); else if (rd->channel == SDPCM_EVENT_CHANNEL) brcmf_rx_event(bus->sdiodev->dev, pkt); else brcmf_rx_frame(bus->sdiodev->dev, pkt, false, false); /* prepare the descriptor for the next read */ rd->len = rd->len_nxtfrm << 4; rd->len_nxtfrm = 0; /* treat all packet as event if we don't know */ rd->channel = SDPCM_EVENT_CHANNEL; } rxcount = maxframes - rxleft; /* Message if we hit the limit */ if (!rxleft) brcmf_dbg(DATA, "hit rx limit of %d frames\n", maxframes); else brcmf_dbg(DATA, "processed %d frames\n", rxcount); /* Back off rxseq if awaiting rtx, update rx_seq */ if (bus->rxskip) rd->seq_num--; bus->rx_seq = rd->seq_num; return rxcount; } static void brcmf_sdio_wait_event_wakeup(struct brcmf_sdio *bus) { wake_up_interruptible(&bus->ctrl_wait); return; } static int brcmf_sdio_txpkt_hdalign(struct brcmf_sdio *bus, struct sk_buff *pkt) { struct brcmf_bus_stats *stats; u16 head_pad; u8 *dat_buf; dat_buf = (u8 *)(pkt->data); /* Check head padding */ head_pad = ((unsigned long)dat_buf % bus->head_align); if (head_pad) { if (skb_headroom(pkt) < head_pad) { stats = &bus->sdiodev->bus_if->stats; atomic_inc(&stats->pktcowed); if (skb_cow_head(pkt, head_pad)) { atomic_inc(&stats->pktcow_failed); return -ENOMEM; } head_pad = 0; } skb_push(pkt, head_pad); dat_buf = (u8 *)(pkt->data); } memset(dat_buf, 0, head_pad + bus->tx_hdrlen); return head_pad; } /* * struct brcmf_skbuff_cb reserves first two bytes in sk_buff::cb for * bus layer usage. */ /* flag marking a dummy skb added for DMA alignment requirement */ #define ALIGN_SKB_FLAG 0x8000 /* bit mask of data length chopped from the previous packet */ #define ALIGN_SKB_CHOP_LEN_MASK 0x7fff static int brcmf_sdio_txpkt_prep_sg(struct brcmf_sdio *bus, struct sk_buff_head *pktq, struct sk_buff *pkt, u16 total_len) { struct brcmf_sdio_dev *sdiodev; struct sk_buff *pkt_pad; u16 tail_pad, tail_chop, chain_pad; unsigned int blksize; bool lastfrm; int ntail, ret; sdiodev = bus->sdiodev; blksize = sdiodev->func2->cur_blksize; /* sg entry alignment should be a divisor of block size */ WARN_ON(blksize % bus->sgentry_align); /* Check tail padding */ lastfrm = skb_queue_is_last(pktq, pkt); tail_pad = 0; tail_chop = pkt->len % bus->sgentry_align; if (tail_chop) tail_pad = bus->sgentry_align - tail_chop; chain_pad = (total_len + tail_pad) % blksize; if (lastfrm && chain_pad) tail_pad += blksize - chain_pad; if (skb_tailroom(pkt) < tail_pad && pkt->len > blksize) { pkt_pad = brcmu_pkt_buf_get_skb(tail_pad + tail_chop + bus->head_align); if (pkt_pad == NULL) return -ENOMEM; ret = brcmf_sdio_txpkt_hdalign(bus, pkt_pad); if (unlikely(ret < 0)) { kfree_skb(pkt_pad); return ret; } memcpy(pkt_pad->data, pkt->data + pkt->len - tail_chop, tail_chop); *(u16 *)(pkt_pad->cb) = ALIGN_SKB_FLAG + tail_chop; skb_trim(pkt, pkt->len - tail_chop); skb_trim(pkt_pad, tail_pad + tail_chop); __skb_queue_after(pktq, pkt, pkt_pad); } else { ntail = pkt->data_len + tail_pad - (pkt->end - pkt->tail); if (skb_cloned(pkt) || ntail > 0) if (pskb_expand_head(pkt, 0, ntail, GFP_ATOMIC)) return -ENOMEM; if (skb_linearize(pkt)) return -ENOMEM; __skb_put(pkt, tail_pad); } return tail_pad; } /** * brcmf_sdio_txpkt_prep - packet preparation for transmit * @bus: brcmf_sdio structure pointer * @pktq: packet list pointer * @chan: virtual channel to transmit the packet * * Processes to be applied to the packet * - Align data buffer pointer * - Align data buffer length * - Prepare header * Return: negative value if there is error */ static int brcmf_sdio_txpkt_prep(struct brcmf_sdio *bus, struct sk_buff_head *pktq, uint chan) { u16 head_pad, total_len; struct sk_buff *pkt_next; u8 txseq; int ret; struct brcmf_sdio_hdrinfo hd_info = {0}; txseq = bus->tx_seq; total_len = 0; skb_queue_walk(pktq, pkt_next) { /* alignment packet inserted in previous * loop cycle can be skipped as it is * already properly aligned and does not * need an sdpcm header. */ if (*(u16 *)(pkt_next->cb) & ALIGN_SKB_FLAG) continue; /* align packet data pointer */ ret = brcmf_sdio_txpkt_hdalign(bus, pkt_next); if (ret < 0) return ret; head_pad = (u16)ret; if (head_pad) memset(pkt_next->data + bus->tx_hdrlen, 0, head_pad); total_len += pkt_next->len; hd_info.len = pkt_next->len; hd_info.lastfrm = skb_queue_is_last(pktq, pkt_next); if (bus->txglom && pktq->qlen > 1) { ret = brcmf_sdio_txpkt_prep_sg(bus, pktq, pkt_next, total_len); if (ret < 0) return ret; hd_info.tail_pad = (u16)ret; total_len += (u16)ret; } hd_info.channel = chan; hd_info.dat_offset = head_pad + bus->tx_hdrlen; hd_info.seq_num = txseq++; /* Now fill the header */ brcmf_sdio_hdpack(bus, pkt_next->data, &hd_info); if (BRCMF_BYTES_ON() && ((BRCMF_CTL_ON() && chan == SDPCM_CONTROL_CHANNEL) || (BRCMF_DATA_ON() && chan != SDPCM_CONTROL_CHANNEL))) brcmf_dbg_hex_dump(true, pkt_next->data, hd_info.len, "Tx Frame:\n"); else if (BRCMF_HDRS_ON()) brcmf_dbg_hex_dump(true, pkt_next->data, head_pad + bus->tx_hdrlen, "Tx Header:\n"); } /* Hardware length tag of the first packet should be total * length of the chain (including padding) */ if (bus->txglom) brcmf_sdio_update_hwhdr(__skb_peek(pktq)->data, total_len); return 0; } /** * brcmf_sdio_txpkt_postp - packet post processing for transmit * @bus: brcmf_sdio structure pointer * @pktq: packet list pointer * * Processes to be applied to the packet * - Remove head padding * - Remove tail padding */ static void brcmf_sdio_txpkt_postp(struct brcmf_sdio *bus, struct sk_buff_head *pktq) { u8 *hdr; u32 dat_offset; u16 tail_pad; u16 dummy_flags, chop_len; struct sk_buff *pkt_next, *tmp, *pkt_prev; skb_queue_walk_safe(pktq, pkt_next, tmp) { dummy_flags = *(u16 *)(pkt_next->cb); if (dummy_flags & ALIGN_SKB_FLAG) { chop_len = dummy_flags & ALIGN_SKB_CHOP_LEN_MASK; if (chop_len) { pkt_prev = pkt_next->prev; skb_put(pkt_prev, chop_len); } __skb_unlink(pkt_next, pktq); brcmu_pkt_buf_free_skb(pkt_next); } else { hdr = pkt_next->data + bus->tx_hdrlen - SDPCM_SWHDR_LEN; dat_offset = le32_to_cpu(*(__le32 *)hdr); dat_offset = (dat_offset & SDPCM_DOFFSET_MASK) >> SDPCM_DOFFSET_SHIFT; skb_pull(pkt_next, dat_offset); if (bus->txglom) { tail_pad = le16_to_cpu(*(__le16 *)(hdr - 2)); skb_trim(pkt_next, pkt_next->len - tail_pad); } } } } /* Writes a HW/SW header into the packet and sends it. */ /* Assumes: (a) header space already there, (b) caller holds lock */ static int brcmf_sdio_txpkt(struct brcmf_sdio *bus, struct sk_buff_head *pktq, uint chan) { int ret; struct sk_buff *pkt_next, *tmp; brcmf_dbg(TRACE, "Enter\n"); ret = brcmf_sdio_txpkt_prep(bus, pktq, chan); if (ret) goto done; sdio_claim_host(bus->sdiodev->func1); ret = brcmf_sdiod_send_pkt(bus->sdiodev, pktq); bus->sdcnt.f2txdata++; if (ret < 0) brcmf_sdio_txfail(bus); sdio_release_host(bus->sdiodev->func1); done: brcmf_sdio_txpkt_postp(bus, pktq); if (ret == 0) bus->tx_seq = (bus->tx_seq + pktq->qlen) % SDPCM_SEQ_WRAP; skb_queue_walk_safe(pktq, pkt_next, tmp) { __skb_unlink(pkt_next, pktq); brcmf_proto_bcdc_txcomplete(bus->sdiodev->dev, pkt_next, ret == 0); } return ret; } static uint brcmf_sdio_sendfromq(struct brcmf_sdio *bus, uint maxframes) { struct sk_buff *pkt; struct sk_buff_head pktq; u32 intstat_addr = bus->sdio_core->base + SD_REG(intstatus); u32 intstatus = 0; int ret = 0, prec_out, i; uint cnt = 0; u8 tx_prec_map, pkt_num; brcmf_dbg(TRACE, "Enter\n"); tx_prec_map = ~bus->flowcontrol; /* Send frames until the limit or some other event */ for (cnt = 0; (cnt < maxframes) && data_ok(bus);) { pkt_num = 1; if (bus->txglom) pkt_num = min_t(u8, bus->tx_max - bus->tx_seq, bus->sdiodev->txglomsz); pkt_num = min_t(u32, pkt_num, brcmu_pktq_mlen(&bus->txq, ~bus->flowcontrol)); __skb_queue_head_init(&pktq); spin_lock_bh(&bus->txq_lock); for (i = 0; i < pkt_num; i++) { pkt = brcmu_pktq_mdeq(&bus->txq, tx_prec_map, &prec_out); if (pkt == NULL) break; __skb_queue_tail(&pktq, pkt); } spin_unlock_bh(&bus->txq_lock); if (i == 0) break; ret = brcmf_sdio_txpkt(bus, &pktq, SDPCM_DATA_CHANNEL); cnt += i; /* In poll mode, need to check for other events */ if (!bus->intr) { /* Check device status, signal pending interrupt */ sdio_claim_host(bus->sdiodev->func1); intstatus = brcmf_sdiod_readl(bus->sdiodev, intstat_addr, &ret); sdio_release_host(bus->sdiodev->func1); bus->sdcnt.f2txdata++; if (ret != 0) break; if (intstatus & bus->hostintmask) atomic_set(&bus->ipend, 1); } } /* Deflow-control stack if needed */ if ((bus->sdiodev->state == BRCMF_SDIOD_DATA) && bus->txoff && (pktq_len(&bus->txq) < TXLOW)) { bus->txoff = false; brcmf_proto_bcdc_txflowblock(bus->sdiodev->dev, false); } return cnt; } static int brcmf_sdio_tx_ctrlframe(struct brcmf_sdio *bus, u8 *frame, u16 len) { u8 doff; u16 pad; uint retries = 0; struct brcmf_sdio_hdrinfo hd_info = {0}; int ret; brcmf_dbg(SDIO, "Enter\n"); /* Back the pointer to make room for bus header */ frame -= bus->tx_hdrlen; len += bus->tx_hdrlen; /* Add alignment padding (optional for ctl frames) */ doff = ((unsigned long)frame % bus->head_align); if (doff) { frame -= doff; len += doff; memset(frame + bus->tx_hdrlen, 0, doff); } /* Round send length to next SDIO block */ pad = 0; if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { pad = bus->blocksize - (len % bus->blocksize); if ((pad > bus->roundup) || (pad >= bus->blocksize)) pad = 0; } else if (len % bus->head_align) { pad = bus->head_align - (len % bus->head_align); } len += pad; hd_info.len = len - pad; hd_info.channel = SDPCM_CONTROL_CHANNEL; hd_info.dat_offset = doff + bus->tx_hdrlen; hd_info.seq_num = bus->tx_seq; hd_info.lastfrm = true; hd_info.tail_pad = pad; brcmf_sdio_hdpack(bus, frame, &hd_info); if (bus->txglom) brcmf_sdio_update_hwhdr(frame, len); brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && BRCMF_CTL_ON(), frame, len, "Tx Frame:\n"); brcmf_dbg_hex_dump(!(BRCMF_BYTES_ON() && BRCMF_CTL_ON()) && BRCMF_HDRS_ON(), frame, min_t(u16, len, 16), "TxHdr:\n"); do { ret = brcmf_sdiod_send_buf(bus->sdiodev, frame, len); if (ret < 0) brcmf_sdio_txfail(bus); else bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQ_WRAP; } while (ret < 0 && retries++ < TXRETRIES); return ret; } static bool brcmf_chip_is_ulp(struct brcmf_chip *ci) { if (ci->chip == CY_CC_43012_CHIP_ID) return true; else return false; } static void brcmf_sdio_bus_stop(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; struct brcmf_core *core = bus->sdio_core; u32 local_hostintmask; u8 saveclk, bpreq; int err; brcmf_dbg(TRACE, "Enter\n"); if (bus->watchdog_tsk) { send_sig(SIGTERM, bus->watchdog_tsk, 1); kthread_stop(bus->watchdog_tsk); bus->watchdog_tsk = NULL; } if (sdiodev->state != BRCMF_SDIOD_NOMEDIUM) { sdio_claim_host(sdiodev->func1); /* Enable clock for device interrupts */ brcmf_sdio_bus_sleep(bus, false, false); /* Disable and clear interrupts at the chip level also */ brcmf_sdiod_writel(sdiodev, core->base + SD_REG(hostintmask), 0, NULL); local_hostintmask = bus->hostintmask; bus->hostintmask = 0; /* Force backplane clocks to assure F2 interrupt propagates */ saveclk = brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, &err); if (!err) { bpreq = saveclk; bpreq |= brcmf_chip_is_ulp(bus->ci) ? SBSDIO_HT_AVAIL_REQ : SBSDIO_FORCE_HT; brcmf_sdiod_writeb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, bpreq, &err); } if (err) brcmf_err("Failed to force clock for F2: err %d\n", err); /* Turn off the bus (F2), free any pending packets */ brcmf_dbg(INTR, "disable SDIO interrupts\n"); sdio_disable_func(sdiodev->func2); /* Clear any pending interrupts now that F2 is disabled */ brcmf_sdiod_writel(sdiodev, core->base + SD_REG(intstatus), local_hostintmask, NULL); sdio_release_host(sdiodev->func1); } /* Clear the data packet queues */ brcmu_pktq_flush(&bus->txq, true, NULL, NULL); /* Clear any held glomming stuff */ brcmu_pkt_buf_free_skb(bus->glomd); brcmf_sdio_free_glom(bus); /* Clear rx control and wake any waiters */ spin_lock_bh(&bus->rxctl_lock); bus->rxlen = 0; spin_unlock_bh(&bus->rxctl_lock); brcmf_sdio_dcmd_resp_wake(bus); /* Reset some F2 state stuff */ bus->rxskip = false; bus->tx_seq = bus->rx_seq = 0; } static inline void brcmf_sdio_clrintr(struct brcmf_sdio *bus) { struct brcmf_sdio_dev *sdiodev; unsigned long flags; sdiodev = bus->sdiodev; if (sdiodev->oob_irq_requested) { spin_lock_irqsave(&sdiodev->irq_en_lock, flags); if (!sdiodev->irq_en && !atomic_read(&bus->ipend)) { enable_irq(sdiodev->settings->bus.sdio.oob_irq_nr); sdiodev->irq_en = true; } spin_unlock_irqrestore(&sdiodev->irq_en_lock, flags); } } static int brcmf_sdio_intr_rstatus(struct brcmf_sdio *bus) { struct brcmf_core *core = bus->sdio_core; u32 addr; unsigned long val; int ret; addr = core->base + SD_REG(intstatus); val = brcmf_sdiod_readl(bus->sdiodev, addr, &ret); bus->sdcnt.f1regdata++; if (ret != 0) return ret; val &= bus->hostintmask; atomic_set(&bus->fcstate, !!(val & I_HMB_FC_STATE)); /* Clear interrupts */ if (val) { brcmf_sdiod_writel(bus->sdiodev, addr, val, &ret); bus->sdcnt.f1regdata++; atomic_or(val, &bus->intstatus); } return ret; } static void brcmf_sdio_dpc(struct brcmf_sdio *bus) { struct brcmf_sdio_dev *sdiod = bus->sdiodev; u32 newstatus = 0; u32 intstat_addr = bus->sdio_core->base + SD_REG(intstatus); unsigned long intstatus; uint txlimit = bus->txbound; /* Tx frames to send before resched */ uint framecnt; /* Temporary counter of tx/rx frames */ int err = 0; brcmf_dbg(SDIO, "Enter\n"); sdio_claim_host(bus->sdiodev->func1); /* If waiting for HTAVAIL, check status */ if (!bus->sr_enabled && bus->clkstate == CLK_PENDING) { u8 clkctl, devctl = 0; #ifdef DEBUG /* Check for inconsistent device control */ devctl = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_DEVICE_CTL, &err); #endif /* DEBUG */ /* Read CSR, if clock on switch to AVAIL, else ignore */ clkctl = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, &err); brcmf_dbg(SDIO, "DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n", devctl, clkctl); if (SBSDIO_HTAV(clkctl)) { devctl = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_DEVICE_CTL, &err); devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_DEVICE_CTL, devctl, &err); bus->clkstate = CLK_AVAIL; } } /* Make sure backplane clock is on */ brcmf_sdio_bus_sleep(bus, false, true); /* Pending interrupt indicates new device status */ if (atomic_read(&bus->ipend) > 0) { atomic_set(&bus->ipend, 0); err = brcmf_sdio_intr_rstatus(bus); } /* Start with leftover status bits */ intstatus = atomic_xchg(&bus->intstatus, 0); /* Handle flow-control change: read new state in case our ack * crossed another change interrupt. If change still set, assume * FC ON for safety, let next loop through do the debounce. */ if (intstatus & I_HMB_FC_CHANGE) { intstatus &= ~I_HMB_FC_CHANGE; brcmf_sdiod_writel(sdiod, intstat_addr, I_HMB_FC_CHANGE, &err); newstatus = brcmf_sdiod_readl(sdiod, intstat_addr, &err); bus->sdcnt.f1regdata += 2; atomic_set(&bus->fcstate, !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE))); intstatus |= (newstatus & bus->hostintmask); } /* Handle host mailbox indication */ if (intstatus & I_HMB_HOST_INT) { intstatus &= ~I_HMB_HOST_INT; intstatus |= brcmf_sdio_hostmail(bus); } sdio_release_host(bus->sdiodev->func1); /* Generally don't ask for these, can get CRC errors... */ if (intstatus & I_WR_OOSYNC) { brcmf_err("Dongle reports WR_OOSYNC\n"); intstatus &= ~I_WR_OOSYNC; } if (intstatus & I_RD_OOSYNC) { brcmf_err("Dongle reports RD_OOSYNC\n"); intstatus &= ~I_RD_OOSYNC; } if (intstatus & I_SBINT) { brcmf_err("Dongle reports SBINT\n"); intstatus &= ~I_SBINT; } /* Would be active due to wake-wlan in gSPI */ if (intstatus & I_CHIPACTIVE) { brcmf_dbg(SDIO, "Dongle reports CHIPACTIVE\n"); intstatus &= ~I_CHIPACTIVE; } /* Ignore frame indications if rxskip is set */ if (bus->rxskip) intstatus &= ~I_HMB_FRAME_IND; /* On frame indication, read available frames */ if ((intstatus & I_HMB_FRAME_IND) && (bus->clkstate == CLK_AVAIL)) { brcmf_sdio_readframes(bus, bus->rxbound); if (!bus->rxpending) intstatus &= ~I_HMB_FRAME_IND; } /* Keep still-pending events for next scheduling */ if (intstatus) atomic_or(intstatus, &bus->intstatus); brcmf_sdio_clrintr(bus); if (bus->ctrl_frame_stat && (bus->clkstate == CLK_AVAIL) && txctl_ok(bus)) { sdio_claim_host(bus->sdiodev->func1); if (bus->ctrl_frame_stat) { err = brcmf_sdio_tx_ctrlframe(bus, bus->ctrl_frame_buf, bus->ctrl_frame_len); bus->ctrl_frame_err = err; wmb(); bus->ctrl_frame_stat = false; if (err) brcmf_err("sdio ctrlframe tx failed err=%d\n", err); } sdio_release_host(bus->sdiodev->func1); brcmf_sdio_wait_event_wakeup(bus); } /* Send queued frames (limit 1 if rx may still be pending) */ if ((bus->clkstate == CLK_AVAIL) && !atomic_read(&bus->fcstate) && brcmu_pktq_mlen(&bus->txq, ~bus->flowcontrol) && txlimit && data_ok(bus)) { framecnt = bus->rxpending ? min(txlimit, bus->txminmax) : txlimit; brcmf_sdio_sendfromq(bus, framecnt); } if ((bus->sdiodev->state != BRCMF_SDIOD_DATA) || (err != 0)) { brcmf_err("failed backplane access over SDIO, halting operation\n"); atomic_set(&bus->intstatus, 0); if (bus->ctrl_frame_stat) { sdio_claim_host(bus->sdiodev->func1); if (bus->ctrl_frame_stat) { bus->ctrl_frame_err = -ENODEV; wmb(); bus->ctrl_frame_stat = false; brcmf_sdio_wait_event_wakeup(bus); } sdio_release_host(bus->sdiodev->func1); } } else if (atomic_read(&bus->intstatus) || atomic_read(&bus->ipend) > 0 || (!atomic_read(&bus->fcstate) && brcmu_pktq_mlen(&bus->txq, ~bus->flowcontrol) && data_ok(bus))) { bus->dpc_triggered = true; } } static struct pktq *brcmf_sdio_bus_gettxq(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; return &bus->txq; } static bool brcmf_sdio_prec_enq(struct pktq *q, struct sk_buff *pkt, int prec) { struct sk_buff *p; int eprec = -1; /* precedence to evict from */ /* Fast case, precedence queue is not full and we are also not * exceeding total queue length */ if (!pktq_pfull(q, prec) && !pktq_full(q)) { brcmu_pktq_penq(q, prec, pkt); return true; } /* Determine precedence from which to evict packet, if any */ if (pktq_pfull(q, prec)) { eprec = prec; } else if (pktq_full(q)) { p = brcmu_pktq_peek_tail(q, &eprec); if (eprec > prec) return false; } /* Evict if needed */ if (eprec >= 0) { /* Detect queueing to unconfigured precedence */ if (eprec == prec) return false; /* refuse newer (incoming) packet */ /* Evict packet according to discard policy */ p = brcmu_pktq_pdeq_tail(q, eprec); if (p == NULL) brcmf_err("brcmu_pktq_pdeq_tail() failed\n"); brcmu_pkt_buf_free_skb(p); } /* Enqueue */ p = brcmu_pktq_penq(q, prec, pkt); if (p == NULL) brcmf_err("brcmu_pktq_penq() failed\n"); return p != NULL; } static int brcmf_sdio_bus_txdata(struct device *dev, struct sk_buff *pkt) { int ret = -EBADE; uint prec; struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; brcmf_dbg(TRACE, "Enter: pkt: data %p len %d\n", pkt->data, pkt->len); if (sdiodev->state != BRCMF_SDIOD_DATA) return -EIO; /* Add space for the header */ skb_push(pkt, bus->tx_hdrlen); /* precondition: IS_ALIGNED((unsigned long)(pkt->data), 2) */ /* In WLAN, priority is always set by the AP using WMM parameters * and this need not always follow the standard 802.1d priority. * Based on AP WMM config, map from 802.1d priority to corresponding * precedence level. */ prec = brcmf_map_prio_to_prec(bus_if->drvr->config, (pkt->priority & PRIOMASK)); /* Check for existing queue, current flow-control, pending event, or pending clock */ brcmf_dbg(TRACE, "deferring pktq len %d\n", pktq_len(&bus->txq)); bus->sdcnt.fcqueued++; /* Priority based enq */ spin_lock_bh(&bus->txq_lock); /* reset bus_flags in packet cb */ *(u16 *)(pkt->cb) = 0; if (!brcmf_sdio_prec_enq(&bus->txq, pkt, prec)) { skb_pull(pkt, bus->tx_hdrlen); brcmf_err("out of bus->txq !!!\n"); ret = -ENOSR; } else { ret = 0; } if (pktq_len(&bus->txq) >= TXHI) { bus->txoff = true; brcmf_proto_bcdc_txflowblock(dev, true); } spin_unlock_bh(&bus->txq_lock); #ifdef DEBUG if (pktq_plen(&bus->txq, prec) > qcount[prec]) qcount[prec] = pktq_plen(&bus->txq, prec); #endif brcmf_sdio_trigger_dpc(bus); return ret; } #ifdef DEBUG #define CONSOLE_LINE_MAX 192 static int brcmf_sdio_readconsole(struct brcmf_sdio *bus) { struct brcmf_console *c = &bus->console; u8 line[CONSOLE_LINE_MAX], ch; u32 n, idx, addr; int rv; /* Don't do anything until FWREADY updates console address */ if (bus->console_addr == 0) return 0; /* Read console log struct */ addr = bus->console_addr + offsetof(struct rte_console, log_le); rv = brcmf_sdiod_ramrw(bus->sdiodev, false, addr, (u8 *)&c->log_le, sizeof(c->log_le)); if (rv < 0) return rv; /* Allocate console buffer (one time only) */ if (c->buf == NULL) { c->bufsize = le32_to_cpu(c->log_le.buf_size); c->buf = kmalloc(c->bufsize, GFP_ATOMIC); if (c->buf == NULL) return -ENOMEM; } idx = le32_to_cpu(c->log_le.idx); /* Protect against corrupt value */ if (idx > c->bufsize) return -EBADE; /* Skip reading the console buffer if the index pointer has not moved */ if (idx == c->last) return 0; /* Read the console buffer */ addr = le32_to_cpu(c->log_le.buf); rv = brcmf_sdiod_ramrw(bus->sdiodev, false, addr, c->buf, c->bufsize); if (rv < 0) return rv; while (c->last != idx) { for (n = 0; n < CONSOLE_LINE_MAX - 2; n++) { if (c->last == idx) { /* This would output a partial line. * Instead, back up * the buffer pointer and output this * line next time around. */ if (c->last >= n) c->last -= n; else c->last = c->bufsize - n; goto break2; } ch = c->buf[c->last]; c->last = (c->last + 1) % c->bufsize; if (ch == '\n') break; line[n] = ch; } if (n > 0) { if (line[n - 1] == '\r') n--; line[n] = 0; pr_debug("CONSOLE: %s\n", line); } } break2: return 0; } #endif /* DEBUG */ static int brcmf_sdio_bus_txctl(struct device *dev, unsigned char *msg, uint msglen) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; int ret; brcmf_dbg(TRACE, "Enter\n"); if (sdiodev->state != BRCMF_SDIOD_DATA) return -EIO; /* Send from dpc */ bus->ctrl_frame_buf = msg; bus->ctrl_frame_len = msglen; wmb(); bus->ctrl_frame_stat = true; brcmf_sdio_trigger_dpc(bus); wait_event_interruptible_timeout(bus->ctrl_wait, !bus->ctrl_frame_stat, CTL_DONE_TIMEOUT); ret = 0; if (bus->ctrl_frame_stat) { sdio_claim_host(bus->sdiodev->func1); if (bus->ctrl_frame_stat) { brcmf_dbg(SDIO, "ctrl_frame timeout\n"); bus->ctrl_frame_stat = false; ret = -ETIMEDOUT; } sdio_release_host(bus->sdiodev->func1); } if (!ret) { brcmf_dbg(SDIO, "ctrl_frame complete, err=%d\n", bus->ctrl_frame_err); rmb(); ret = bus->ctrl_frame_err; } if (ret) bus->sdcnt.tx_ctlerrs++; else bus->sdcnt.tx_ctlpkts++; return ret; } #ifdef DEBUG static int brcmf_sdio_dump_console(struct seq_file *seq, struct brcmf_sdio *bus, struct sdpcm_shared *sh) { u32 addr, console_ptr, console_size, console_index; char *conbuf = NULL; __le32 sh_val; int rv; /* obtain console information from device memory */ addr = sh->console_addr + offsetof(struct rte_console, log_le); rv = brcmf_sdiod_ramrw(bus->sdiodev, false, addr, (u8 *)&sh_val, sizeof(u32)); if (rv < 0) return rv; console_ptr = le32_to_cpu(sh_val); addr = sh->console_addr + offsetof(struct rte_console, log_le.buf_size); rv = brcmf_sdiod_ramrw(bus->sdiodev, false, addr, (u8 *)&sh_val, sizeof(u32)); if (rv < 0) return rv; console_size = le32_to_cpu(sh_val); addr = sh->console_addr + offsetof(struct rte_console, log_le.idx); rv = brcmf_sdiod_ramrw(bus->sdiodev, false, addr, (u8 *)&sh_val, sizeof(u32)); if (rv < 0) return rv; console_index = le32_to_cpu(sh_val); /* allocate buffer for console data */ if (console_size <= CONSOLE_BUFFER_MAX) conbuf = vzalloc(console_size+1); if (!conbuf) return -ENOMEM; /* obtain the console data from device */ conbuf[console_size] = '\0'; rv = brcmf_sdiod_ramrw(bus->sdiodev, false, console_ptr, (u8 *)conbuf, console_size); if (rv < 0) goto done; rv = seq_write(seq, conbuf + console_index, console_size - console_index); if (rv < 0) goto done; if (console_index > 0) rv = seq_write(seq, conbuf, console_index - 1); done: vfree(conbuf); return rv; } static int brcmf_sdio_trap_info(struct seq_file *seq, struct brcmf_sdio *bus, struct sdpcm_shared *sh) { int error; struct brcmf_trap_info tr; if ((sh->flags & SDPCM_SHARED_TRAP) == 0) { brcmf_dbg(INFO, "no trap in firmware\n"); return 0; } error = brcmf_sdiod_ramrw(bus->sdiodev, false, sh->trap_addr, (u8 *)&tr, sizeof(struct brcmf_trap_info)); if (error < 0) return error; if (seq) seq_printf(seq, "dongle trap info: type 0x%x @ epc 0x%08x\n" " cpsr 0x%08x spsr 0x%08x sp 0x%08x\n" " lr 0x%08x pc 0x%08x offset 0x%x\n" " r0 0x%08x r1 0x%08x r2 0x%08x r3 0x%08x\n" " r4 0x%08x r5 0x%08x r6 0x%08x r7 0x%08x\n", le32_to_cpu(tr.type), le32_to_cpu(tr.epc), le32_to_cpu(tr.cpsr), le32_to_cpu(tr.spsr), le32_to_cpu(tr.r13), le32_to_cpu(tr.r14), le32_to_cpu(tr.pc), sh->trap_addr, le32_to_cpu(tr.r0), le32_to_cpu(tr.r1), le32_to_cpu(tr.r2), le32_to_cpu(tr.r3), le32_to_cpu(tr.r4), le32_to_cpu(tr.r5), le32_to_cpu(tr.r6), le32_to_cpu(tr.r7)); else pr_debug("dongle trap info: type 0x%x @ epc 0x%08x\n" " cpsr 0x%08x spsr 0x%08x sp 0x%08x\n" " lr 0x%08x pc 0x%08x offset 0x%x\n" " r0 0x%08x r1 0x%08x r2 0x%08x r3 0x%08x\n" " r4 0x%08x r5 0x%08x r6 0x%08x r7 0x%08x\n", le32_to_cpu(tr.type), le32_to_cpu(tr.epc), le32_to_cpu(tr.cpsr), le32_to_cpu(tr.spsr), le32_to_cpu(tr.r13), le32_to_cpu(tr.r14), le32_to_cpu(tr.pc), sh->trap_addr, le32_to_cpu(tr.r0), le32_to_cpu(tr.r1), le32_to_cpu(tr.r2), le32_to_cpu(tr.r3), le32_to_cpu(tr.r4), le32_to_cpu(tr.r5), le32_to_cpu(tr.r6), le32_to_cpu(tr.r7)); return 0; } static int brcmf_sdio_assert_info(struct seq_file *seq, struct brcmf_sdio *bus, struct sdpcm_shared *sh) { int error = 0; char file[80] = "?"; char expr[80] = ""; if ((sh->flags & SDPCM_SHARED_ASSERT_BUILT) == 0) { brcmf_dbg(INFO, "firmware not built with -assert\n"); return 0; } else if ((sh->flags & SDPCM_SHARED_ASSERT) == 0) { brcmf_dbg(INFO, "no assert in dongle\n"); return 0; } sdio_claim_host(bus->sdiodev->func1); if (sh->assert_file_addr != 0) { error = brcmf_sdiod_ramrw(bus->sdiodev, false, sh->assert_file_addr, (u8 *)file, 80); if (error < 0) return error; } if (sh->assert_exp_addr != 0) { error = brcmf_sdiod_ramrw(bus->sdiodev, false, sh->assert_exp_addr, (u8 *)expr, 80); if (error < 0) return error; } sdio_release_host(bus->sdiodev->func1); seq_printf(seq, "dongle assert: %s:%d: assert(%s)\n", file, sh->assert_line, expr); return 0; } static int brcmf_sdio_checkdied(struct brcmf_sdio *bus) { int error; struct sdpcm_shared sh; error = brcmf_sdio_readshared(bus, &sh); if (error < 0) return error; if ((sh.flags & SDPCM_SHARED_ASSERT_BUILT) == 0) brcmf_dbg(INFO, "firmware not built with -assert\n"); else if (sh.flags & SDPCM_SHARED_ASSERT) brcmf_err("assertion in dongle\n"); if (sh.flags & SDPCM_SHARED_TRAP) { brcmf_err("firmware trap in dongle\n"); brcmf_sdio_trap_info(NULL, bus, &sh); } return 0; } static int brcmf_sdio_died_dump(struct seq_file *seq, struct brcmf_sdio *bus) { int error = 0; struct sdpcm_shared sh; error = brcmf_sdio_readshared(bus, &sh); if (error < 0) goto done; error = brcmf_sdio_assert_info(seq, bus, &sh); if (error < 0) goto done; error = brcmf_sdio_trap_info(seq, bus, &sh); if (error < 0) goto done; error = brcmf_sdio_dump_console(seq, bus, &sh); done: return error; } static int brcmf_sdio_forensic_read(struct seq_file *seq, void *data) { struct brcmf_bus *bus_if = dev_get_drvdata(seq->private); struct brcmf_sdio *bus = bus_if->bus_priv.sdio->bus; return brcmf_sdio_died_dump(seq, bus); } static int brcmf_debugfs_sdio_count_read(struct seq_file *seq, void *data) { struct brcmf_bus *bus_if = dev_get_drvdata(seq->private); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio_count *sdcnt = &sdiodev->bus->sdcnt; seq_printf(seq, "intrcount: %u\nlastintrs: %u\n" "pollcnt: %u\nregfails: %u\n" "tx_sderrs: %u\nfcqueued: %u\n" "rxrtx: %u\nrx_toolong: %u\n" "rxc_errors: %u\nrx_hdrfail: %u\n" "rx_badhdr: %u\nrx_badseq: %u\n" "fc_rcvd: %u\nfc_xoff: %u\n" "fc_xon: %u\nrxglomfail: %u\n" "rxglomframes: %u\nrxglompkts: %u\n" "f2rxhdrs: %u\nf2rxdata: %u\n" "f2txdata: %u\nf1regdata: %u\n" "tickcnt: %u\ntx_ctlerrs: %lu\n" "tx_ctlpkts: %lu\nrx_ctlerrs: %lu\n" "rx_ctlpkts: %lu\nrx_readahead: %lu\n", sdcnt->intrcount, sdcnt->lastintrs, sdcnt->pollcnt, sdcnt->regfails, sdcnt->tx_sderrs, sdcnt->fcqueued, sdcnt->rxrtx, sdcnt->rx_toolong, sdcnt->rxc_errors, sdcnt->rx_hdrfail, sdcnt->rx_badhdr, sdcnt->rx_badseq, sdcnt->fc_rcvd, sdcnt->fc_xoff, sdcnt->fc_xon, sdcnt->rxglomfail, sdcnt->rxglomframes, sdcnt->rxglompkts, sdcnt->f2rxhdrs, sdcnt->f2rxdata, sdcnt->f2txdata, sdcnt->f1regdata, sdcnt->tickcnt, sdcnt->tx_ctlerrs, sdcnt->tx_ctlpkts, sdcnt->rx_ctlerrs, sdcnt->rx_ctlpkts, sdcnt->rx_readahead_cnt); return 0; } static void brcmf_sdio_debugfs_create(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_pub *drvr = bus_if->drvr; struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; struct dentry *dentry = brcmf_debugfs_get_devdir(drvr); if (IS_ERR_OR_NULL(dentry)) return; bus->console_interval = BRCMF_CONSOLE; brcmf_debugfs_add_entry(drvr, "forensics", brcmf_sdio_forensic_read); brcmf_debugfs_add_entry(drvr, "counters", brcmf_debugfs_sdio_count_read); debugfs_create_u32("console_interval", 0644, dentry, &bus->console_interval); } #else static int brcmf_sdio_checkdied(struct brcmf_sdio *bus) { return 0; } static void brcmf_sdio_debugfs_create(struct device *dev) { } #endif /* DEBUG */ static int brcmf_sdio_bus_rxctl(struct device *dev, unsigned char *msg, uint msglen) { int timeleft; uint rxlen = 0; bool pending; u8 *buf; struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; brcmf_dbg(TRACE, "Enter\n"); if (sdiodev->state != BRCMF_SDIOD_DATA) return -EIO; /* Wait until control frame is available */ timeleft = brcmf_sdio_dcmd_resp_wait(bus, &bus->rxlen, &pending); spin_lock_bh(&bus->rxctl_lock); rxlen = bus->rxlen; memcpy(msg, bus->rxctl, min(msglen, rxlen)); bus->rxctl = NULL; buf = bus->rxctl_orig; bus->rxctl_orig = NULL; bus->rxlen = 0; spin_unlock_bh(&bus->rxctl_lock); vfree(buf); if (rxlen) { brcmf_dbg(CTL, "resumed on rxctl frame, got %d expected %d\n", rxlen, msglen); } else if (timeleft == 0) { brcmf_err("resumed on timeout\n"); brcmf_sdio_checkdied(bus); } else if (pending) { brcmf_dbg(CTL, "cancelled\n"); return -ERESTARTSYS; } else { brcmf_dbg(CTL, "resumed for unknown reason?\n"); brcmf_sdio_checkdied(bus); } if (rxlen) bus->sdcnt.rx_ctlpkts++; else bus->sdcnt.rx_ctlerrs++; return rxlen ? (int)rxlen : -ETIMEDOUT; } #ifdef DEBUG static bool brcmf_sdio_verifymemory(struct brcmf_sdio_dev *sdiodev, u32 ram_addr, u8 *ram_data, uint ram_sz) { char *ram_cmp; int err; bool ret = true; int address; int offset; int len; /* read back and verify */ brcmf_dbg(INFO, "Compare RAM dl & ul at 0x%08x; size=%d\n", ram_addr, ram_sz); ram_cmp = kmalloc(MEMBLOCK, GFP_KERNEL); /* do not proceed while no memory but */ if (!ram_cmp) return true; address = ram_addr; offset = 0; while (offset < ram_sz) { len = ((offset + MEMBLOCK) < ram_sz) ? MEMBLOCK : ram_sz - offset; err = brcmf_sdiod_ramrw(sdiodev, false, address, ram_cmp, len); if (err) { brcmf_err("error %d on reading %d membytes at 0x%08x\n", err, len, address); ret = false; break; } else if (memcmp(ram_cmp, &ram_data[offset], len)) { brcmf_err("Downloaded RAM image is corrupted, block offset is %d, len is %d\n", offset, len); ret = false; break; } offset += len; address += len; } kfree(ram_cmp); return ret; } #else /* DEBUG */ static bool brcmf_sdio_verifymemory(struct brcmf_sdio_dev *sdiodev, u32 ram_addr, u8 *ram_data, uint ram_sz) { return true; } #endif /* DEBUG */ static int brcmf_sdio_download_code_file(struct brcmf_sdio *bus, const struct firmware *fw) { int err; brcmf_dbg(TRACE, "Enter\n"); err = brcmf_sdiod_ramrw(bus->sdiodev, true, bus->ci->rambase, (u8 *)fw->data, fw->size); if (err) brcmf_err("error %d on writing %d membytes at 0x%08x\n", err, (int)fw->size, bus->ci->rambase); else if (!brcmf_sdio_verifymemory(bus->sdiodev, bus->ci->rambase, (u8 *)fw->data, fw->size)) err = -EIO; return err; } static int brcmf_sdio_download_nvram(struct brcmf_sdio *bus, void *vars, u32 varsz) { int address; int err; brcmf_dbg(TRACE, "Enter\n"); address = bus->ci->ramsize - varsz + bus->ci->rambase; err = brcmf_sdiod_ramrw(bus->sdiodev, true, address, vars, varsz); if (err) brcmf_err("error %d on writing %d nvram bytes at 0x%08x\n", err, varsz, address); else if (!brcmf_sdio_verifymemory(bus->sdiodev, address, vars, varsz)) err = -EIO; return err; } static int brcmf_sdio_download_firmware(struct brcmf_sdio *bus, const struct firmware *fw, void *nvram, u32 nvlen) { int bcmerror; u32 rstvec; sdio_claim_host(bus->sdiodev->func1); brcmf_sdio_clkctl(bus, CLK_AVAIL, false); rstvec = get_unaligned_le32(fw->data); brcmf_dbg(SDIO, "firmware rstvec: %x\n", rstvec); bcmerror = brcmf_sdio_download_code_file(bus, fw); release_firmware(fw); if (bcmerror) { brcmf_err("dongle image file download failed\n"); brcmf_fw_nvram_free(nvram); goto err; } bcmerror = brcmf_sdio_download_nvram(bus, nvram, nvlen); brcmf_fw_nvram_free(nvram); if (bcmerror) { brcmf_err("dongle nvram file download failed\n"); goto err; } /* Take arm out of reset */ if (!brcmf_chip_set_active(bus->ci, rstvec)) { brcmf_err("error getting out of ARM core reset\n"); bcmerror = -EIO; goto err; } err: brcmf_sdio_clkctl(bus, CLK_SDONLY, false); sdio_release_host(bus->sdiodev->func1); return bcmerror; } static bool brcmf_sdio_aos_no_decode(struct brcmf_sdio *bus) { if (bus->ci->chip == BRCM_CC_43751_CHIP_ID || - bus->ci->chip == CY_CC_43012_CHIP_ID || - bus->ci->chip == CY_CC_43752_CHIP_ID) + bus->ci->chip == BRCM_CC_43752_CHIP_ID || + bus->ci->chip == CY_CC_43012_CHIP_ID) return true; else return false; } static void brcmf_sdio_sr_init(struct brcmf_sdio *bus) { int err = 0; u8 val; u8 wakeupctrl; u8 cardcap; u8 chipclkcsr; brcmf_dbg(TRACE, "Enter\n"); if (brcmf_chip_is_ulp(bus->ci)) { wakeupctrl = SBSDIO_FUNC1_WCTRL_ALPWAIT_SHIFT; chipclkcsr = SBSDIO_HT_AVAIL_REQ; } else { wakeupctrl = SBSDIO_FUNC1_WCTRL_HTWAIT_SHIFT; chipclkcsr = SBSDIO_FORCE_HT; } if (brcmf_sdio_aos_no_decode(bus)) { cardcap = SDIO_CCCR_BRCM_CARDCAP_CMD_NODEC; } else { cardcap = (SDIO_CCCR_BRCM_CARDCAP_CMD14_SUPPORT | SDIO_CCCR_BRCM_CARDCAP_CMD14_EXT); } val = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_WAKEUPCTRL, &err); if (err) { brcmf_err("error reading SBSDIO_FUNC1_WAKEUPCTRL\n"); return; } val |= 1 << wakeupctrl; brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_WAKEUPCTRL, val, &err); if (err) { brcmf_err("error writing SBSDIO_FUNC1_WAKEUPCTRL\n"); return; } /* Add CMD14 Support */ brcmf_sdiod_func0_wb(bus->sdiodev, SDIO_CCCR_BRCM_CARDCAP, cardcap, &err); if (err) { brcmf_err("error writing SDIO_CCCR_BRCM_CARDCAP\n"); return; } brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, chipclkcsr, &err); if (err) { brcmf_err("error writing SBSDIO_FUNC1_CHIPCLKCSR\n"); return; } /* set flag */ bus->sr_enabled = true; brcmf_dbg(INFO, "SR enabled\n"); } /* enable KSO bit */ static int brcmf_sdio_kso_init(struct brcmf_sdio *bus) { struct brcmf_core *core = bus->sdio_core; u8 val; int err = 0; brcmf_dbg(TRACE, "Enter\n"); /* KSO bit added in SDIO core rev 12 */ if (core->rev < 12) return 0; val = brcmf_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, &err); if (err) { brcmf_err("error reading SBSDIO_FUNC1_SLEEPCSR\n"); return err; } if (!(val & SBSDIO_FUNC1_SLEEPCSR_KSO_MASK)) { val |= (SBSDIO_FUNC1_SLEEPCSR_KSO_EN << SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT); brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, val, &err); if (err) { brcmf_err("error writing SBSDIO_FUNC1_SLEEPCSR\n"); return err; } } return 0; } static int brcmf_sdio_bus_preinit(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; struct brcmf_core *core = bus->sdio_core; u32 value; __le32 iovar; int err; /* maxctl provided by common layer */ if (WARN_ON(!bus_if->maxctl)) return -EINVAL; /* Allocate control receive buffer */ bus_if->maxctl += bus->roundup; value = roundup((bus_if->maxctl + SDPCM_HDRLEN), ALIGNMENT); value += bus->head_align; bus->rxbuf = kmalloc(value, GFP_ATOMIC); if (bus->rxbuf) bus->rxblen = value; /* the commands below use the terms tx and rx from * a device perspective, ie. bus:txglom affects the * bus transfers from device to host. */ if (core->rev < 12) { /* for sdio core rev < 12, disable txgloming */ iovar = 0; err = brcmf_iovar_data_set(dev, "bus:txglom", &iovar, sizeof(iovar)); } else { /* otherwise, set txglomalign */ value = sdiodev->settings->bus.sdio.sd_sgentry_align; /* SDIO ADMA requires at least 32 bit alignment */ iovar = cpu_to_le32(max_t(u32, value, ALIGNMENT)); err = brcmf_iovar_data_set(dev, "bus:txglomalign", &iovar, sizeof(iovar)); } if (err < 0) goto done; bus->tx_hdrlen = SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN; if (sdiodev->sg_support) { bus->txglom = false; iovar = cpu_to_le32(1); err = brcmf_iovar_data_set(bus->sdiodev->dev, "bus:rxglom", &iovar, sizeof(iovar)); if (err < 0) { /* bus:rxglom is allowed to fail */ err = 0; } else { bus->txglom = true; bus->tx_hdrlen += SDPCM_HWEXT_LEN; } } brcmf_bus_add_txhdrlen(bus->sdiodev->dev, bus->tx_hdrlen); done: return err; } static size_t brcmf_sdio_bus_get_ramsize(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; return bus->ci->ramsize - bus->ci->srsize; } static int brcmf_sdio_bus_get_memdump(struct device *dev, void *data, size_t mem_size) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiodev->bus; int err; int address; int offset; int len; brcmf_dbg(INFO, "dump at 0x%08x: size=%zu\n", bus->ci->rambase, mem_size); address = bus->ci->rambase; offset = err = 0; sdio_claim_host(sdiodev->func1); while (offset < mem_size) { len = ((offset + MEMBLOCK) < mem_size) ? MEMBLOCK : mem_size - offset; err = brcmf_sdiod_ramrw(sdiodev, false, address, data, len); if (err) { brcmf_err("error %d on reading %d membytes at 0x%08x\n", err, len, address); goto done; } data += len; offset += len; address += len; } done: sdio_release_host(sdiodev->func1); return err; } void brcmf_sdio_trigger_dpc(struct brcmf_sdio *bus) { if (!bus->dpc_triggered) { bus->dpc_triggered = true; queue_work(bus->brcmf_wq, &bus->datawork); } } void brcmf_sdio_isr(struct brcmf_sdio *bus, bool in_isr) { brcmf_dbg(TRACE, "Enter\n"); if (!bus) { brcmf_err("bus is null pointer, exiting\n"); return; } /* Count the interrupt call */ bus->sdcnt.intrcount++; if (in_isr) atomic_set(&bus->ipend, 1); else if (brcmf_sdio_intr_rstatus(bus)) { brcmf_err("failed backplane access\n"); } /* Disable additional interrupts (is this needed now)? */ if (!bus->intr) brcmf_err("isr w/o interrupt configured!\n"); bus->dpc_triggered = true; queue_work(bus->brcmf_wq, &bus->datawork); } static void brcmf_sdio_bus_watchdog(struct brcmf_sdio *bus) { brcmf_dbg(TIMER, "Enter\n"); /* Poll period: check device if appropriate. */ if (!bus->sr_enabled && bus->poll && (++bus->polltick >= bus->pollrate)) { u32 intstatus = 0; /* Reset poll tick */ bus->polltick = 0; /* Check device if no interrupts */ if (!bus->intr || (bus->sdcnt.intrcount == bus->sdcnt.lastintrs)) { if (!bus->dpc_triggered) { u8 devpend; sdio_claim_host(bus->sdiodev->func1); devpend = brcmf_sdiod_func0_rb(bus->sdiodev, SDIO_CCCR_INTx, NULL); sdio_release_host(bus->sdiodev->func1); intstatus = devpend & (INTR_STATUS_FUNC1 | INTR_STATUS_FUNC2); } /* If there is something, make like the ISR and schedule the DPC */ if (intstatus) { bus->sdcnt.pollcnt++; atomic_set(&bus->ipend, 1); bus->dpc_triggered = true; queue_work(bus->brcmf_wq, &bus->datawork); } } /* Update interrupt tracking */ bus->sdcnt.lastintrs = bus->sdcnt.intrcount; } #ifdef DEBUG /* Poll for console output periodically */ if (bus->sdiodev->state == BRCMF_SDIOD_DATA && BRCMF_FWCON_ON() && bus->console_interval != 0) { bus->console.count += jiffies_to_msecs(BRCMF_WD_POLL); if (bus->console.count >= bus->console_interval) { bus->console.count -= bus->console_interval; sdio_claim_host(bus->sdiodev->func1); /* Make sure backplane clock is on */ brcmf_sdio_bus_sleep(bus, false, false); if (brcmf_sdio_readconsole(bus) < 0) /* stop on error */ bus->console_interval = 0; sdio_release_host(bus->sdiodev->func1); } } #endif /* DEBUG */ /* On idle timeout clear activity flag and/or turn off clock */ if (!bus->dpc_triggered) { rmb(); if ((!bus->dpc_running) && (bus->idletime > 0) && (bus->clkstate == CLK_AVAIL)) { bus->idlecount++; if (bus->idlecount > bus->idletime) { brcmf_dbg(SDIO, "idle\n"); sdio_claim_host(bus->sdiodev->func1); #ifdef DEBUG if (!BRCMF_FWCON_ON() || bus->console_interval == 0) #endif brcmf_sdio_wd_timer(bus, false); bus->idlecount = 0; brcmf_sdio_bus_sleep(bus, true, false); sdio_release_host(bus->sdiodev->func1); } } else { bus->idlecount = 0; } } else { bus->idlecount = 0; } } static void brcmf_sdio_dataworker(struct work_struct *work) { struct brcmf_sdio *bus = container_of(work, struct brcmf_sdio, datawork); bus->dpc_running = true; wmb(); while (READ_ONCE(bus->dpc_triggered)) { bus->dpc_triggered = false; brcmf_sdio_dpc(bus); bus->idlecount = 0; } bus->dpc_running = false; if (brcmf_sdiod_freezing(bus->sdiodev)) { brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DOWN); brcmf_sdiod_try_freeze(bus->sdiodev); brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DATA); } } static void brcmf_sdio_drivestrengthinit(struct brcmf_sdio_dev *sdiodev, struct brcmf_chip *ci, u32 drivestrength) { const struct sdiod_drive_str *str_tab = NULL; u32 str_mask; u32 str_shift; u32 i; u32 drivestrength_sel = 0; u32 cc_data_temp; u32 addr; if (!(ci->cc_caps & CC_CAP_PMU)) return; switch (SDIOD_DRVSTR_KEY(ci->chip, ci->pmurev)) { case SDIOD_DRVSTR_KEY(BRCM_CC_4330_CHIP_ID, 12): str_tab = sdiod_drvstr_tab1_1v8; str_mask = 0x00003800; str_shift = 11; break; case SDIOD_DRVSTR_KEY(BRCM_CC_4334_CHIP_ID, 17): str_tab = sdiod_drvstr_tab6_1v8; str_mask = 0x00001800; str_shift = 11; break; case SDIOD_DRVSTR_KEY(BRCM_CC_43143_CHIP_ID, 17): /* note: 43143 does not support tristate */ i = ARRAY_SIZE(sdiod_drvstr_tab2_3v3) - 1; if (drivestrength >= sdiod_drvstr_tab2_3v3[i].strength) { str_tab = sdiod_drvstr_tab2_3v3; str_mask = 0x00000007; str_shift = 0; } else brcmf_err("Invalid SDIO Drive strength for chip %s, strength=%d\n", ci->name, drivestrength); break; case SDIOD_DRVSTR_KEY(BRCM_CC_43362_CHIP_ID, 13): str_tab = sdiod_drive_strength_tab5_1v8; str_mask = 0x00003800; str_shift = 11; break; default: brcmf_dbg(INFO, "No SDIO driver strength init needed for chip %s rev %d pmurev %d\n", ci->name, ci->chiprev, ci->pmurev); break; } if (str_tab != NULL) { struct brcmf_core *pmu = brcmf_chip_get_pmu(ci); for (i = 0; str_tab[i].strength != 0; i++) { if (drivestrength >= str_tab[i].strength) { drivestrength_sel = str_tab[i].sel; break; } } addr = CORE_CC_REG(pmu->base, chipcontrol_addr); brcmf_sdiod_writel(sdiodev, addr, 1, NULL); cc_data_temp = brcmf_sdiod_readl(sdiodev, addr, NULL); cc_data_temp &= ~str_mask; drivestrength_sel <<= str_shift; cc_data_temp |= drivestrength_sel; brcmf_sdiod_writel(sdiodev, addr, cc_data_temp, NULL); brcmf_dbg(INFO, "SDIO: %d mA (req=%d mA) drive strength selected, set to 0x%08x\n", str_tab[i].strength, drivestrength, cc_data_temp); } } static int brcmf_sdio_buscoreprep(void *ctx) { struct brcmf_sdio_dev *sdiodev = ctx; int err = 0; u8 clkval, clkset; /* Try forcing SDIO core to do ALPAvail request only */ clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ; brcmf_sdiod_writeb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, clkset, &err); if (err) { brcmf_err("error writing for HT off\n"); return err; } /* If register supported, wait for ALPAvail and then force ALP */ /* This may take up to 15 milliseconds */ clkval = brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, NULL); if ((clkval & ~SBSDIO_AVBITS) != clkset) { brcmf_err("ChipClkCSR access: wrote 0x%02x read 0x%02x\n", clkset, clkval); return -EACCES; } SPINWAIT(((clkval = brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, NULL)), !SBSDIO_ALPAV(clkval)), PMU_MAX_TRANSITION_DLY); if (!SBSDIO_ALPAV(clkval)) { brcmf_err("timeout on ALPAV wait, clkval 0x%02x\n", clkval); return -EBUSY; } clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP; brcmf_sdiod_writeb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, clkset, &err); udelay(65); /* Also, disable the extra SDIO pull-ups */ brcmf_sdiod_writeb(sdiodev, SBSDIO_FUNC1_SDIOPULLUP, 0, NULL); return 0; } static void brcmf_sdio_buscore_activate(void *ctx, struct brcmf_chip *chip, u32 rstvec) { struct brcmf_sdio_dev *sdiodev = ctx; struct brcmf_core *core = sdiodev->bus->sdio_core; u32 reg_addr; /* clear all interrupts */ reg_addr = core->base + SD_REG(intstatus); brcmf_sdiod_writel(sdiodev, reg_addr, 0xFFFFFFFF, NULL); if (rstvec) /* Write reset vector to address 0 */ brcmf_sdiod_ramrw(sdiodev, true, 0, (void *)&rstvec, sizeof(rstvec)); } static u32 brcmf_sdio_buscore_read32(void *ctx, u32 addr) { struct brcmf_sdio_dev *sdiodev = ctx; u32 val, rev; val = brcmf_sdiod_readl(sdiodev, addr, NULL); /* * this is a bit of special handling if reading the chipcommon chipid * register. The 4339 is a next-gen of the 4335. It uses the same * SDIO device id as 4335 and the chipid register returns 4335 as well. * It can be identified as 4339 by looking at the chip revision. It * is corrected here so the chip.c module has the right info. */ if (addr == CORE_CC_REG(SI_ENUM_BASE_DEFAULT, chipid) && (sdiodev->func1->device == SDIO_DEVICE_ID_BROADCOM_4339 || sdiodev->func1->device == SDIO_DEVICE_ID_BROADCOM_4335_4339)) { rev = (val & CID_REV_MASK) >> CID_REV_SHIFT; if (rev >= 2) { val &= ~CID_ID_MASK; val |= BRCM_CC_4339_CHIP_ID; } } return val; } static void brcmf_sdio_buscore_write32(void *ctx, u32 addr, u32 val) { struct brcmf_sdio_dev *sdiodev = ctx; brcmf_sdiod_writel(sdiodev, addr, val, NULL); } static const struct brcmf_buscore_ops brcmf_sdio_buscore_ops = { .prepare = brcmf_sdio_buscoreprep, .activate = brcmf_sdio_buscore_activate, .read32 = brcmf_sdio_buscore_read32, .write32 = brcmf_sdio_buscore_write32, }; static int brcmf_sdio_probe_attach(struct brcmf_sdio *bus) { struct brcmf_sdio_dev *sdiodev; u8 clkctl = 0; int err = 0; int reg_addr; u32 reg_val; u32 drivestrength; u32 enum_base; int ret = -EBADE; sdiodev = bus->sdiodev; sdio_claim_host(sdiodev->func1); enum_base = brcmf_chip_enum_base(sdiodev->func1->device); pr_debug("F1 signature read @0x%08x=0x%4x\n", enum_base, brcmf_sdiod_readl(sdiodev, enum_base, NULL)); /* * Force PLL off until brcmf_chip_attach() * programs PLL control regs */ brcmf_sdiod_writeb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, BRCMF_INIT_CLKCTL1, &err); if (!err) clkctl = brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, &err); if (err || ((clkctl & ~SBSDIO_AVBITS) != BRCMF_INIT_CLKCTL1)) { brcmf_err("ChipClkCSR access: err %d wrote 0x%02x read 0x%02x\n", err, BRCMF_INIT_CLKCTL1, clkctl); goto fail; } bus->ci = brcmf_chip_attach(sdiodev, sdiodev->func1->device, &brcmf_sdio_buscore_ops); if (IS_ERR(bus->ci)) { brcmf_err("brcmf_chip_attach failed!\n"); bus->ci = NULL; goto fail; } /* Pick up the SDIO core info struct from chip.c */ bus->sdio_core = brcmf_chip_get_core(bus->ci, BCMA_CORE_SDIO_DEV); if (!bus->sdio_core) goto fail; /* Pick up the CHIPCOMMON core info struct, for bulk IO in bcmsdh.c */ sdiodev->cc_core = brcmf_chip_get_core(bus->ci, BCMA_CORE_CHIPCOMMON); if (!sdiodev->cc_core) goto fail; sdiodev->settings = brcmf_get_module_param(sdiodev->dev, BRCMF_BUSTYPE_SDIO, bus->ci->chip, bus->ci->chiprev); if (IS_ERR_OR_NULL(sdiodev->settings)) { brcmf_err("Failed to get device parameters\n"); ret = PTR_ERR_OR_ZERO(sdiodev->settings); goto fail; } /* platform specific configuration: * alignments must be at least 4 bytes for ADMA */ bus->head_align = ALIGNMENT; bus->sgentry_align = ALIGNMENT; if (sdiodev->settings->bus.sdio.sd_head_align > ALIGNMENT) bus->head_align = sdiodev->settings->bus.sdio.sd_head_align; if (sdiodev->settings->bus.sdio.sd_sgentry_align > ALIGNMENT) bus->sgentry_align = sdiodev->settings->bus.sdio.sd_sgentry_align; /* allocate scatter-gather table. sg support * will be disabled upon allocation failure. */ brcmf_sdiod_sgtable_alloc(sdiodev); /* wowl can be supported when KEEP_POWER is true and (WAKE_SDIO_IRQ * is true or when platform data OOB irq is true). */ if (IS_ENABLED(CONFIG_PM_SLEEP) && (sdio_get_host_pm_caps(sdiodev->func1) & MMC_PM_KEEP_POWER) && ((sdio_get_host_pm_caps(sdiodev->func1) & MMC_PM_WAKE_SDIO_IRQ) || (sdiodev->settings->bus.sdio.oob_irq_supported))) sdiodev->bus_if->wowl_supported = true; if (brcmf_sdio_kso_init(bus)) { brcmf_err("error enabling KSO\n"); goto fail; } if (sdiodev->settings->bus.sdio.drive_strength) drivestrength = sdiodev->settings->bus.sdio.drive_strength; else drivestrength = DEFAULT_SDIO_DRIVE_STRENGTH; brcmf_sdio_drivestrengthinit(sdiodev, bus->ci, drivestrength); /* Set card control so an SDIO card reset does a WLAN backplane reset */ reg_val = brcmf_sdiod_func0_rb(sdiodev, SDIO_CCCR_BRCM_CARDCTRL, &err); if (err) goto fail; reg_val |= SDIO_CCCR_BRCM_CARDCTRL_WLANRESET; brcmf_sdiod_func0_wb(sdiodev, SDIO_CCCR_BRCM_CARDCTRL, reg_val, &err); if (err) goto fail; /* set PMUControl so a backplane reset does PMU state reload */ reg_addr = CORE_CC_REG(brcmf_chip_get_pmu(bus->ci)->base, pmucontrol); reg_val = brcmf_sdiod_readl(sdiodev, reg_addr, &err); if (err) goto fail; reg_val |= (BCMA_CC_PMU_CTL_RES_RELOAD << BCMA_CC_PMU_CTL_RES_SHIFT); brcmf_sdiod_writel(sdiodev, reg_addr, reg_val, &err); if (err) goto fail; sdio_release_host(sdiodev->func1); brcmu_pktq_init(&bus->txq, (PRIOMASK + 1), TXQLEN); /* allocate header buffer */ bus->hdrbuf = kzalloc(MAX_HDR_READ + bus->head_align, GFP_KERNEL); if (!bus->hdrbuf) return -ENOMEM; /* Locate an appropriately-aligned portion of hdrbuf */ bus->rxhdr = (u8 *) roundup((unsigned long)&bus->hdrbuf[0], bus->head_align); /* Set the poll and/or interrupt flags */ bus->intr = true; bus->poll = false; if (bus->poll) bus->pollrate = 1; return 0; fail: sdio_release_host(sdiodev->func1); return ret; } static int brcmf_sdio_watchdog_thread(void *data) { struct brcmf_sdio *bus = (struct brcmf_sdio *)data; int wait; allow_signal(SIGTERM); /* Run until signal received */ brcmf_sdiod_freezer_count(bus->sdiodev); while (1) { if (kthread_should_stop()) break; brcmf_sdiod_freezer_uncount(bus->sdiodev); wait = wait_for_completion_interruptible(&bus->watchdog_wait); brcmf_sdiod_freezer_count(bus->sdiodev); brcmf_sdiod_try_freeze(bus->sdiodev); if (!wait) { brcmf_sdio_bus_watchdog(bus); /* Count the tick for reference */ bus->sdcnt.tickcnt++; reinit_completion(&bus->watchdog_wait); } else break; } return 0; } static void brcmf_sdio_watchdog(struct timer_list *t) { struct brcmf_sdio *bus = timer_container_of(bus, t, timer); if (bus->watchdog_tsk) { complete(&bus->watchdog_wait); /* Reschedule the watchdog */ if (bus->wd_active) mod_timer(&bus->timer, jiffies + BRCMF_WD_POLL); } } static int brcmf_sdio_get_blob(struct device *dev, const struct firmware **fw, enum brcmf_blob_type type) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; switch (type) { case BRCMF_BLOB_CLM: *fw = sdiodev->clm_fw; sdiodev->clm_fw = NULL; break; default: return -ENOENT; } if (!*fw) return -ENOENT; return 0; } static int brcmf_sdio_bus_reset(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; brcmf_dbg(SDIO, "Enter\n"); /* start by unregistering irqs */ brcmf_sdiod_intr_unregister(sdiodev); brcmf_sdiod_remove(sdiodev); /* reset the adapter */ sdio_claim_host(sdiodev->func1); mmc_hw_reset(sdiodev->func1->card); sdio_release_host(sdiodev->func1); brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_DOWN); return 0; } static void brcmf_sdio_bus_remove(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiod = bus_if->bus_priv.sdio; device_release_driver(&sdiod->func2->dev); device_release_driver(&sdiod->func1->dev); } static const struct brcmf_bus_ops brcmf_sdio_bus_ops = { .stop = brcmf_sdio_bus_stop, .preinit = brcmf_sdio_bus_preinit, .txdata = brcmf_sdio_bus_txdata, .txctl = brcmf_sdio_bus_txctl, .rxctl = brcmf_sdio_bus_rxctl, .gettxq = brcmf_sdio_bus_gettxq, .wowl_config = brcmf_sdio_wowl_config, .get_ramsize = brcmf_sdio_bus_get_ramsize, .get_memdump = brcmf_sdio_bus_get_memdump, .get_blob = brcmf_sdio_get_blob, .debugfs_create = brcmf_sdio_debugfs_create, .reset = brcmf_sdio_bus_reset, .remove = brcmf_sdio_bus_remove, }; #define BRCMF_SDIO_FW_CODE 0 #define BRCMF_SDIO_FW_NVRAM 1 #define BRCMF_SDIO_FW_CLM 2 static void brcmf_sdio_firmware_callback(struct device *dev, int err, struct brcmf_fw_request *fwreq) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiod = bus_if->bus_priv.sdio; struct brcmf_sdio *bus = sdiod->bus; struct brcmf_core *core = bus->sdio_core; const struct firmware *code; void *nvram; u32 nvram_len; u8 saveclk, bpreq; u8 devctl; brcmf_dbg(TRACE, "Enter: dev=%s, err=%d\n", dev_name(dev), err); if (err) goto fail; code = fwreq->items[BRCMF_SDIO_FW_CODE].binary; nvram = fwreq->items[BRCMF_SDIO_FW_NVRAM].nv_data.data; nvram_len = fwreq->items[BRCMF_SDIO_FW_NVRAM].nv_data.len; sdiod->clm_fw = fwreq->items[BRCMF_SDIO_FW_CLM].binary; kfree(fwreq); /* try to download image and nvram to the dongle */ bus->alp_only = true; err = brcmf_sdio_download_firmware(bus, code, nvram, nvram_len); if (err) goto fail; bus->alp_only = false; /* Start the watchdog timer */ bus->sdcnt.tickcnt = 0; brcmf_sdio_wd_timer(bus, true); sdio_claim_host(sdiod->func1); /* Make sure backplane clock is on, needed to generate F2 interrupt */ brcmf_sdio_clkctl(bus, CLK_AVAIL, false); if (bus->clkstate != CLK_AVAIL) goto release; /* Force clocks on backplane to be sure F2 interrupt propagates */ saveclk = brcmf_sdiod_readb(sdiod, SBSDIO_FUNC1_CHIPCLKCSR, &err); if (!err) { bpreq = saveclk; bpreq |= brcmf_chip_is_ulp(bus->ci) ? SBSDIO_HT_AVAIL_REQ : SBSDIO_FORCE_HT; brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_CHIPCLKCSR, bpreq, &err); } if (err) { brcmf_err("Failed to force clock for F2: err %d\n", err); goto release; } /* Enable function 2 (frame transfers) */ brcmf_sdiod_writel(sdiod, core->base + SD_REG(tosbmailboxdata), SDPCM_PROT_VERSION << SMB_DATA_VERSION_SHIFT, NULL); err = sdio_enable_func(sdiod->func2); brcmf_dbg(INFO, "enable F2: err=%d\n", err); /* If F2 successfully enabled, set core and enable interrupts */ if (!err) { /* Set up the interrupt mask and enable interrupts */ bus->hostintmask = HOSTINTMASK; brcmf_sdiod_writel(sdiod, core->base + SD_REG(hostintmask), bus->hostintmask, NULL); switch (sdiod->func1->device) { case SDIO_DEVICE_ID_BROADCOM_43751: + case SDIO_DEVICE_ID_BROADCOM_43752: case SDIO_DEVICE_ID_BROADCOM_CYPRESS_4373: - case SDIO_DEVICE_ID_BROADCOM_CYPRESS_43752: brcmf_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n", CY_4373_F2_WATERMARK); brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, CY_4373_F2_WATERMARK, &err); devctl = brcmf_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL, &err); devctl |= SBSDIO_DEVCTL_F2WM_ENAB; brcmf_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl, &err); brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, CY_4373_F1_MESBUSYCTRL, &err); break; case SDIO_DEVICE_ID_BROADCOM_CYPRESS_43012: brcmf_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n", CY_43012_F2_WATERMARK); brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, CY_43012_F2_WATERMARK, &err); devctl = brcmf_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL, &err); devctl |= SBSDIO_DEVCTL_F2WM_ENAB; brcmf_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl, &err); brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, CY_43012_MESBUSYCTRL, &err); break; case SDIO_DEVICE_ID_BROADCOM_4329: case SDIO_DEVICE_ID_BROADCOM_4339: brcmf_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n", CY_4339_F2_WATERMARK); brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, CY_4339_F2_WATERMARK, &err); devctl = brcmf_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL, &err); devctl |= SBSDIO_DEVCTL_F2WM_ENAB; brcmf_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl, &err); brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, CY_4339_MESBUSYCTRL, &err); break; case SDIO_DEVICE_ID_BROADCOM_43455: brcmf_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n", CY_43455_F2_WATERMARK); brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, CY_43455_F2_WATERMARK, &err); devctl = brcmf_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL, &err); devctl |= SBSDIO_DEVCTL_F2WM_ENAB; brcmf_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl, &err); brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, CY_43455_MESBUSYCTRL, &err); break; case SDIO_DEVICE_ID_BROADCOM_4359: case SDIO_DEVICE_ID_BROADCOM_4354: case SDIO_DEVICE_ID_BROADCOM_4356: brcmf_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n", CY_435X_F2_WATERMARK); brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, CY_435X_F2_WATERMARK, &err); devctl = brcmf_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL, &err); devctl |= SBSDIO_DEVCTL_F2WM_ENAB; brcmf_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl, &err); brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, CY_435X_F1_MESBUSYCTRL, &err); break; default: brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, DEFAULT_F2_WATERMARK, &err); break; } } else { /* Disable F2 again */ sdio_disable_func(sdiod->func2); goto checkdied; } if (brcmf_chip_sr_capable(bus->ci)) { brcmf_sdio_sr_init(bus); } else { /* Restore previous clock setting */ brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_CHIPCLKCSR, saveclk, &err); } if (err == 0) { /* Assign bus interface call back */ sdiod->bus_if->dev = sdiod->dev; sdiod->bus_if->ops = &brcmf_sdio_bus_ops; sdiod->bus_if->chip = bus->ci->chip; sdiod->bus_if->chiprev = bus->ci->chiprev; /* Allow full data communication using DPC from now on. */ brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DATA); err = brcmf_sdiod_intr_register(sdiod); if (err != 0) brcmf_err("intr register failed:%d\n", err); } /* If we didn't come up, turn off backplane clock */ if (err != 0) { brcmf_sdio_clkctl(bus, CLK_NONE, false); goto checkdied; } sdio_release_host(sdiod->func1); err = brcmf_alloc(sdiod->dev, sdiod->settings); if (err) { brcmf_err("brcmf_alloc failed\n"); goto claim; } /* Attach to the common layer, reserve hdr space */ err = brcmf_attach(sdiod->dev); if (err != 0) { brcmf_err("brcmf_attach failed\n"); goto free; } /* ready */ return; free: brcmf_free(sdiod->dev); claim: sdio_claim_host(sdiod->func1); checkdied: brcmf_sdio_checkdied(bus); release: sdio_release_host(sdiod->func1); fail: brcmf_dbg(TRACE, "failed: dev=%s, err=%d\n", dev_name(dev), err); device_release_driver(&sdiod->func2->dev); device_release_driver(dev); } static struct brcmf_fw_request * brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus) { struct brcmf_fw_request *fwreq; struct brcmf_fw_name fwnames[] = { { ".bin", bus->sdiodev->fw_name }, { ".txt", bus->sdiodev->nvram_name }, { ".clm_blob", bus->sdiodev->clm_name }, }; fwreq = brcmf_fw_alloc_request(bus->ci->chip, bus->ci->chiprev, brcmf_sdio_fwnames, ARRAY_SIZE(brcmf_sdio_fwnames), fwnames, ARRAY_SIZE(fwnames)); if (!fwreq) return NULL; fwreq->items[BRCMF_SDIO_FW_CODE].type = BRCMF_FW_TYPE_BINARY; fwreq->items[BRCMF_SDIO_FW_NVRAM].type = BRCMF_FW_TYPE_NVRAM; fwreq->items[BRCMF_SDIO_FW_CLM].type = BRCMF_FW_TYPE_BINARY; fwreq->items[BRCMF_SDIO_FW_CLM].flags = BRCMF_FW_REQF_OPTIONAL; fwreq->board_types[0] = bus->sdiodev->settings->board_type; return fwreq; } struct brcmf_sdio *brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev) { int ret; struct brcmf_sdio *bus; struct workqueue_struct *wq; struct brcmf_fw_request *fwreq; brcmf_dbg(TRACE, "Enter\n"); /* Allocate private bus interface state */ bus = kzalloc(sizeof(*bus), GFP_ATOMIC); if (!bus) { ret = -ENOMEM; goto fail; } bus->sdiodev = sdiodev; sdiodev->bus = bus; skb_queue_head_init(&bus->glom); bus->txbound = BRCMF_TXBOUND; bus->rxbound = BRCMF_RXBOUND; bus->txminmax = BRCMF_TXMINMAX; bus->tx_seq = SDPCM_SEQ_WRAP - 1; /* single-threaded workqueue */ wq = alloc_ordered_workqueue("brcmf_wq/%s", WQ_MEM_RECLAIM | WQ_HIGHPRI, dev_name(&sdiodev->func1->dev)); if (!wq) { brcmf_err("insufficient memory to create txworkqueue\n"); ret = -ENOMEM; goto fail; } brcmf_sdiod_freezer_count(sdiodev); INIT_WORK(&bus->datawork, brcmf_sdio_dataworker); bus->brcmf_wq = wq; /* attempt to attach to the dongle */ ret = brcmf_sdio_probe_attach(bus); if (ret < 0) { brcmf_err("brcmf_sdio_probe_attach failed\n"); goto fail; } spin_lock_init(&bus->rxctl_lock); spin_lock_init(&bus->txq_lock); init_waitqueue_head(&bus->ctrl_wait); init_waitqueue_head(&bus->dcmd_resp_wait); /* Set up the watchdog timer */ timer_setup(&bus->timer, brcmf_sdio_watchdog, 0); /* Initialize watchdog thread */ init_completion(&bus->watchdog_wait); bus->watchdog_tsk = kthread_run(brcmf_sdio_watchdog_thread, bus, "brcmf_wdog/%s", dev_name(&sdiodev->func1->dev)); if (IS_ERR(bus->watchdog_tsk)) { pr_warn("brcmf_watchdog thread failed to start\n"); bus->watchdog_tsk = NULL; } /* Initialize DPC thread */ bus->dpc_triggered = false; bus->dpc_running = false; /* default sdio bus header length for tx packet */ bus->tx_hdrlen = SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN; /* Query the F2 block size, set roundup accordingly */ bus->blocksize = bus->sdiodev->func2->cur_blksize; bus->roundup = min(max_roundup, bus->blocksize); sdio_claim_host(bus->sdiodev->func1); /* Disable F2 to clear any intermediate frame state on the dongle */ sdio_disable_func(bus->sdiodev->func2); bus->rxflow = false; /* Done with backplane-dependent accesses, can drop clock... */ brcmf_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL); sdio_release_host(bus->sdiodev->func1); /* ...and initialize clock/power states */ bus->clkstate = CLK_SDONLY; bus->idletime = BRCMF_IDLE_INTERVAL; bus->idleclock = BRCMF_IDLE_ACTIVE; /* SR state */ bus->sr_enabled = false; brcmf_dbg(INFO, "completed!!\n"); fwreq = brcmf_sdio_prepare_fw_request(bus); if (!fwreq) { ret = -ENOMEM; goto fail; } ret = brcmf_fw_get_firmwares(sdiodev->dev, fwreq, brcmf_sdio_firmware_callback); if (ret != 0) { brcmf_err("async firmware request failed: %d\n", ret); kfree(fwreq); goto fail; } return bus; fail: brcmf_sdio_remove(bus); return ERR_PTR(ret); } /* Detach and free everything */ void brcmf_sdio_remove(struct brcmf_sdio *bus) { brcmf_dbg(TRACE, "Enter\n"); if (bus) { /* Stop watchdog task */ if (bus->watchdog_tsk) { send_sig(SIGTERM, bus->watchdog_tsk, 1); kthread_stop(bus->watchdog_tsk); bus->watchdog_tsk = NULL; } /* De-register interrupt handler */ brcmf_sdiod_intr_unregister(bus->sdiodev); brcmf_detach(bus->sdiodev->dev); brcmf_free(bus->sdiodev->dev); cancel_work_sync(&bus->datawork); if (bus->brcmf_wq) destroy_workqueue(bus->brcmf_wq); if (bus->ci) { if (bus->sdiodev->state != BRCMF_SDIOD_NOMEDIUM) { sdio_claim_host(bus->sdiodev->func1); brcmf_sdio_wd_timer(bus, false); brcmf_sdio_clkctl(bus, CLK_AVAIL, false); /* Leave the device in state where it is * 'passive'. This is done by resetting all * necessary cores. */ msleep(20); brcmf_chip_set_passive(bus->ci); brcmf_sdio_clkctl(bus, CLK_NONE, false); sdio_release_host(bus->sdiodev->func1); } brcmf_chip_detach(bus->ci); } if (bus->sdiodev->settings) brcmf_release_module_param(bus->sdiodev->settings); release_firmware(bus->sdiodev->clm_fw); bus->sdiodev->clm_fw = NULL; kfree(bus->rxbuf); kfree(bus->hdrbuf); kfree(bus); } brcmf_dbg(TRACE, "Disconnected\n"); } void brcmf_sdio_wd_timer(struct brcmf_sdio *bus, bool active) { /* Totally stop the timer */ if (!active && bus->wd_active) { timer_delete_sync(&bus->timer); bus->wd_active = false; return; } /* don't start the wd until fw is loaded */ if (bus->sdiodev->state != BRCMF_SDIOD_DATA) return; if (active) { if (!bus->wd_active) { /* Create timer again when watchdog period is dynamically changed or in the first instance */ bus->timer.expires = jiffies + BRCMF_WD_POLL; add_timer(&bus->timer); bus->wd_active = true; } else { /* Re arm the timer, at last watchdog period */ mod_timer(&bus->timer, jiffies + BRCMF_WD_POLL); } } } int brcmf_sdio_sleep(struct brcmf_sdio *bus, bool sleep) { int ret; sdio_claim_host(bus->sdiodev->func1); ret = brcmf_sdio_bus_sleep(bus, sleep, false); sdio_release_host(bus->sdiodev->func1); return ret; } diff --git a/sys/contrib/dev/broadcom/brcm80211/include/brcm_hw_ids.h b/sys/contrib/dev/broadcom/brcm80211/include/brcm_hw_ids.h index b39c5c1ee18b..df3b67ba4db2 100644 --- a/sys/contrib/dev/broadcom/brcm80211/include/brcm_hw_ids.h +++ b/sys/contrib/dev/broadcom/brcm80211/include/brcm_hw_ids.h @@ -1,116 +1,115 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2010 Broadcom Corporation */ #ifndef _BRCM_HW_IDS_H_ #define _BRCM_HW_IDS_H_ #include #include #define BRCM_USB_VENDOR_ID_BROADCOM 0x0a5c #define BRCM_USB_VENDOR_ID_LG 0x043e #define BRCM_USB_VENDOR_ID_LINKSYS 0x13b1 #define CY_USB_VENDOR_ID_CYPRESS 0x04b4 #define BRCM_PCIE_VENDOR_ID_BROADCOM PCI_VENDOR_ID_BROADCOM /* Chipcommon Core Chip IDs */ #define BRCM_CC_43143_CHIP_ID 43143 #define BRCM_CC_43235_CHIP_ID 43235 #define BRCM_CC_43236_CHIP_ID 43236 #define BRCM_CC_43238_CHIP_ID 43238 #define BRCM_CC_43241_CHIP_ID 0x4324 #define BRCM_CC_43242_CHIP_ID 43242 #define BRCM_CC_4329_CHIP_ID 0x4329 #define BRCM_CC_4330_CHIP_ID 0x4330 #define BRCM_CC_4334_CHIP_ID 0x4334 #define BRCM_CC_43340_CHIP_ID 43340 #define BRCM_CC_43341_CHIP_ID 43341 #define BRCM_CC_43362_CHIP_ID 43362 #define BRCM_CC_4335_CHIP_ID 0x4335 #define BRCM_CC_4339_CHIP_ID 0x4339 #define BRCM_CC_43430_CHIP_ID 43430 #define BRCM_CC_4345_CHIP_ID 0x4345 #define BRCM_CC_43454_CHIP_ID 43454 #define BRCM_CC_43465_CHIP_ID 43465 #define BRCM_CC_4350_CHIP_ID 0x4350 #define BRCM_CC_43525_CHIP_ID 43525 #define BRCM_CC_4354_CHIP_ID 0x4354 #define BRCM_CC_4355_CHIP_ID 0x4355 #define BRCM_CC_4356_CHIP_ID 0x4356 #define BRCM_CC_43566_CHIP_ID 43566 #define BRCM_CC_43567_CHIP_ID 43567 #define BRCM_CC_43569_CHIP_ID 43569 #define BRCM_CC_43570_CHIP_ID 43570 #define BRCM_CC_4358_CHIP_ID 0x4358 #define BRCM_CC_4359_CHIP_ID 0x4359 #define BRCM_CC_43602_CHIP_ID 43602 #define BRCM_CC_4364_CHIP_ID 0x4364 #define BRCM_CC_4365_CHIP_ID 0x4365 #define BRCM_CC_4366_CHIP_ID 0x4366 #define BRCM_CC_43664_CHIP_ID 43664 #define BRCM_CC_43666_CHIP_ID 43666 #define BRCM_CC_4371_CHIP_ID 0x4371 #define BRCM_CC_43751_CHIP_ID 43751 #define BRCM_CC_43752_CHIP_ID 43752 #define BRCM_CC_4377_CHIP_ID 0x4377 #define BRCM_CC_4378_CHIP_ID 0x4378 #define BRCM_CC_4387_CHIP_ID 0x4387 #define CY_CC_4373_CHIP_ID 0x4373 #define CY_CC_43012_CHIP_ID 43012 #define CY_CC_43439_CHIP_ID 43439 -#define CY_CC_43752_CHIP_ID 43752 /* USB Device IDs */ #define BRCM_USB_43143_DEVICE_ID 0xbd1e #define BRCM_USB_43235_LINKSYS_DEVICE_ID 0x0039 #define BRCM_USB_43236_DEVICE_ID 0xbd17 #define BRCM_USB_43242_DEVICE_ID 0xbd1f #define BRCM_USB_43242_LG_DEVICE_ID 0x3101 #define BRCM_USB_43569_DEVICE_ID 0xbd27 #define BRCM_USB_BCMFW_DEVICE_ID 0x0bdc #define CY_USB_4373_DEVICE_ID 0xbd29 /* PCIE Device IDs */ #define BRCM_PCIE_4350_DEVICE_ID 0x43a3 #define BRCM_PCIE_4354_DEVICE_ID 0x43df #define BRCM_PCIE_4354_RAW_DEVICE_ID 0x4354 #define BRCM_PCIE_4355_DEVICE_ID 0x43dc #define BRCM_PCIE_4356_DEVICE_ID 0x43ec #define BRCM_PCIE_43567_DEVICE_ID 0x43d3 #define BRCM_PCIE_43570_DEVICE_ID 0x43d9 #define BRCM_PCIE_43570_RAW_DEVICE_ID 0xaa31 #define BRCM_PCIE_4358_DEVICE_ID 0x43e9 #define BRCM_PCIE_4359_DEVICE_ID 0x43ef #define BRCM_PCIE_43602_DEVICE_ID 0x43ba #define BRCM_PCIE_43602_2G_DEVICE_ID 0x43bb #define BRCM_PCIE_43602_5G_DEVICE_ID 0x43bc #define BRCM_PCIE_43602_RAW_DEVICE_ID 43602 #define BRCM_PCIE_4364_DEVICE_ID 0x4464 #define BRCM_PCIE_4365_DEVICE_ID 0x43ca #define BRCM_PCIE_4365_2G_DEVICE_ID 0x43cb #define BRCM_PCIE_4365_5G_DEVICE_ID 0x43cc #define BRCM_PCIE_4366_DEVICE_ID 0x43c3 #define BRCM_PCIE_4366_2G_DEVICE_ID 0x43c4 #define BRCM_PCIE_4366_5G_DEVICE_ID 0x43c5 #define BRCM_PCIE_4371_DEVICE_ID 0x440d #define BRCM_PCIE_43596_DEVICE_ID 0x4415 #define BRCM_PCIE_43752_DEVICE_ID 0x449d #define BRCM_PCIE_4377_DEVICE_ID 0x4488 #define BRCM_PCIE_4378_DEVICE_ID 0x4425 #define BRCM_PCIE_4387_DEVICE_ID 0x4433 #define CY_PCIE_54591_DEVICE_ID 0x4417 /* brcmsmac IDs */ #define BCM4313_D11N2G_ID 0x4727 /* 4313 802.11n 2.4G device */ #define BCM43224_D11N_ID 0x4353 /* 43224 802.11n dualband device */ #define BCM43224_D11N_ID_VEN1 0x0576 /* Vendor specific 43224 802.11n db */ #define BCM43225_D11N2G_ID 0x4357 /* 43225 802.11n 2.4GHz device */ #define BCM43236_D11N_ID 0x4346 /* 43236 802.11n dualband device */ #define BCM43236_D11N2G_ID 0x4347 /* 43236 802.11n 2.4GHz device */ #define BCM4313_CHIP_ID 0x4313 #define BCM43224_CHIP_ID 43224 #endif /* _BRCM_HW_IDS_H_ */