From: patrick Date: Fri, 9 Feb 2018 02:21:16 +0000 (+0000) Subject: Implement the bwfm(4) SDIO bus logic. This is the bus layer that X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=d2df058d982ba7f23bb1bdd81ab77eb3a7559656;p=openbsd Implement the bwfm(4) SDIO bus logic. This is the bus layer that converts the logic of the upper layers (sending control messages, sending data messages, receiving event or data messages) into the corresponding work that has to be done on the lowest layer. SDIO is not the fastest bus for exchanging network packets, but maybe there is room for tuning. Actual TX/RX is being done in a worker task that serializes access to the hardware. This is good enough to attach to WiFi networks and do network transfers. Developed and tested on a Cubox-i. --- diff --git a/sys/dev/sdmmc/if_bwfm_sdio.c b/sys/dev/sdmmc/if_bwfm_sdio.c index 65c64f1b144..6271b20ca13 100644 --- a/sys/dev/sdmmc/if_bwfm_sdio.c +++ b/sys/dev/sdmmc/if_bwfm_sdio.c @@ -1,4 +1,4 @@ -/* $OpenBSD: if_bwfm_sdio.c,v 1.3 2018/02/07 22:08:24 patrick Exp $ */ +/* $OpenBSD: if_bwfm_sdio.c,v 1.4 2018/02/09 02:21:16 patrick Exp $ */ /* * Copyright (c) 2010-2016 Broadcom Corporation * Copyright (c) 2016,2017 Patrick Wildt @@ -53,6 +53,7 @@ #define BWFM_SDIO_CCCR_BRCM_CARDCTRL_WLANRESET 0x02 #define BWFM_SDIO_CCCR_BRCM_SEPINT 0xf2 +/* #define BWFM_DEBUG */ #ifdef BWFM_DEBUG #define DPRINTF(x) do { if (bwfm_debug > 0) printf x; } while (0) #define DPRINTFN(n, x) do { if (bwfm_debug >= (n)) printf x; } while (0) @@ -65,30 +66,83 @@ static int bwfm_debug = 1; #undef DEVNAME #define DEVNAME(sc) ((sc)->sc_sc.sc_dev.dv_xname) +enum bwfm_sdio_clkstate { + CLK_NONE, + CLK_SDONLY, + CLK_PENDING, + CLK_AVAIL, +}; + struct bwfm_sdio_softc { struct bwfm_softc sc_sc; struct sdmmc_function **sc_sf; + struct rwlock *sc_lock; + void *sc_ih; + uint32_t sc_bar0; + int sc_clkstate; + int sc_alp_only; + int sc_sr_enabled; + + struct bwfm_core *sc_cc; + + uint8_t sc_tx_seq; + char *sc_txctl_buf; + size_t sc_txctl_len; + struct mbuf *sc_rxctl_buf; + char *sc_rxdata_buf; + struct mbuf_queue sc_txdata_queue; + + struct task sc_task; }; int bwfm_sdio_match(struct device *, void *, void *); void bwfm_sdio_attach(struct device *, struct device *, void *); +void bwfm_sdio_attachhook(struct device *); int bwfm_sdio_detach(struct device *, int); +int bwfm_sdio_intr(void *); +void bwfm_sdio_task(void *); +int bwfm_sdio_load_microcode(struct bwfm_sdio_softc *, + u_char *, size_t, u_char *, size_t); + +void bwfm_sdio_clkctl(struct bwfm_sdio_softc *, + enum bwfm_sdio_clkstate, int); +void bwfm_sdio_htclk(struct bwfm_sdio_softc *, int, int); + void bwfm_sdio_backplane(struct bwfm_sdio_softc *, uint32_t); uint8_t bwfm_sdio_read_1(struct bwfm_sdio_softc *, uint32_t); uint32_t bwfm_sdio_read_4(struct bwfm_sdio_softc *, uint32_t); void bwfm_sdio_write_1(struct bwfm_sdio_softc *, uint32_t, - uint8_t); + uint8_t); void bwfm_sdio_write_4(struct bwfm_sdio_softc *, uint32_t, - uint32_t); + uint32_t); +int bwfm_sdio_buf_read(struct bwfm_sdio_softc *, + struct sdmmc_function *, uint32_t, char *, size_t); +int bwfm_sdio_buf_write(struct bwfm_sdio_softc *, + struct sdmmc_function *, uint32_t, char *, size_t); +uint32_t bwfm_sdio_ram_read_write(struct bwfm_sdio_softc *, + uint32_t, char *, size_t, int); +uint32_t bwfm_sdio_frame_read_write(struct bwfm_sdio_softc *, + char *, size_t, int); + +uint32_t bwfm_sdio_dev_read(struct bwfm_sdio_softc *, uint32_t); +void bwfm_sdio_dev_write(struct bwfm_sdio_softc *, uint32_t, + uint32_t); uint32_t bwfm_sdio_buscore_read(struct bwfm_softc *, uint32_t); void bwfm_sdio_buscore_write(struct bwfm_softc *, uint32_t, - uint32_t); + uint32_t); int bwfm_sdio_buscore_prepare(struct bwfm_softc *); void bwfm_sdio_buscore_activate(struct bwfm_softc *, uint32_t); +struct mbuf * bwfm_sdio_newbuf(void); +void bwfm_sdio_tx_ctrlframe(struct bwfm_sdio_softc *); +void bwfm_sdio_tx_dataframe(struct bwfm_sdio_softc *); +void bwfm_sdio_rx_frames(struct bwfm_sdio_softc *); +void bwfm_sdio_rx_glom(struct bwfm_sdio_softc *, uint16_t *, int); + +int bwfm_sdio_txcheck(struct bwfm_softc *); int bwfm_sdio_txdata(struct bwfm_softc *, struct mbuf *); int bwfm_sdio_txctl(struct bwfm_softc *, char *, size_t); int bwfm_sdio_rxctl(struct bwfm_softc *, char *, size_t *); @@ -96,6 +150,7 @@ int bwfm_sdio_rxctl(struct bwfm_softc *, char *, size_t *); struct bwfm_bus_ops bwfm_sdio_bus_ops = { .bs_init = NULL, .bs_stop = NULL, + .bs_txcheck = bwfm_sdio_txcheck, .bs_txdata = bwfm_sdio_txdata, .bs_txctl = bwfm_sdio_txctl, .bs_rxctl = bwfm_sdio_rxctl, @@ -152,10 +207,16 @@ bwfm_sdio_attach(struct device *parent, struct device *self, void *aux) struct sdmmc_attach_args *saa = aux; struct sdmmc_function *sf = saa->sf; struct bwfm_core *core; + uint32_t reg; printf("\n"); + task_set(&sc->sc_task, bwfm_sdio_task, sc); + mq_init(&sc->sc_txdata_queue, 16, IPL_SOFTNET); + sc->sc_rxdata_buf = malloc(64 * 1024, M_DEVBUF, M_WAITOK); + rw_assert_wrlock(&sf->sc->sc_lock); + sc->sc_lock = &sf->sc->sc_lock; sc->sc_sf = mallocarray(sf->sc->sc_function_count + 1, sizeof(struct sdmmc_function *), M_DEVBUF, M_WAITOK); @@ -166,12 +227,8 @@ bwfm_sdio_attach(struct device *parent, struct device *self, void *aux) } sf = saa->sf; - /* - * TODO: set block size to 64 for func 1, 512 for func 2. - * We might need to work on the SDMMC stack to be able to set - * a block size per function. Currently the IO code uses the - * SDHC controller's maximum block length. - */ + sdmmc_io_set_block_size(sc->sc_sf[1], 64); + sdmmc_io_set_block_size(sc->sc_sf[2], 512); /* Enable Function 1. */ if (sdmmc_io_function_enable(sc->sc_sf[1]) != 0) { @@ -193,6 +250,21 @@ bwfm_sdio_attach(struct device *parent, struct device *self, void *aux) goto err; } + sc->sc_cc = bwfm_chip_get_core(&sc->sc_sc, BWFM_AGENT_CORE_CHIPCOMMON); + if (sc->sc_cc == NULL) { + printf("%s: cannot find chipcommon core\n", DEVNAME(sc)); + goto err; + } + + core = bwfm_chip_get_core(&sc->sc_sc, BWFM_AGENT_CORE_SDIO_DEV); + if (core->co_rev >= 12) { + reg = bwfm_sdio_read_1(sc, BWFM_SDIO_FUNC1_SLEEPCSR); + if (!(reg & BWFM_SDIO_FUNC1_SLEEPCSR_KSO)) { + reg |= BWFM_SDIO_FUNC1_SLEEPCSR_KSO; + bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_SLEEPCSR, reg); + } + } + /* TODO: drive strength */ bwfm_sdio_write_1(sc, BWFM_SDIO_CCCR_BRCM_CARDCTRL, @@ -205,16 +277,333 @@ bwfm_sdio_attach(struct device *parent, struct device *self, void *aux) (BWFM_CHIP_REG_PMUCONTROL_RES_RELOAD << BWFM_CHIP_REG_PMUCONTROL_RES_SHIFT)); - sc->sc_sc.sc_bus_ops = &bwfm_sdio_bus_ops; - sc->sc_sc.sc_proto_ops = &bwfm_proto_bcdc_ops; - bwfm_attach(&sc->sc_sc); + sdmmc_io_function_disable(sc->sc_sf[2]); + bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR, 0); + sc->sc_clkstate = CLK_SDONLY; + + config_mountroot(self, bwfm_sdio_attachhook); return; err: free(sc->sc_sf, M_DEVBUF, 0); } +void +bwfm_sdio_attachhook(struct device *self) +{ + struct bwfm_sdio_softc *sc = (struct bwfm_sdio_softc *)self; + struct bwfm_softc *bwfm = (void *)sc; + const char *name = NULL; + const char *nvname = NULL; + uint32_t clk, reg; + u_char *ucode, *nvram; + size_t size, nvlen; + + rw_enter_write(sc->sc_lock); + + switch (bwfm->sc_chip.ch_chip) + { + case BRCM_CC_4330_CHIP_ID: + name = "brcmfmac4330-sdio.bin"; + nvname = "brcmfmac4330-sdio.txt"; + break; + case BRCM_CC_4334_CHIP_ID: + name = "brcmfmac4334-sdio.bin"; + nvname = "brcmfmac4334-sdio.name"; + break; + case BRCM_CC_43340_CHIP_ID: + name = "brcmfmac43340-sdio.bin"; + nvname = "brcmfmac43340-sdio.name"; + break; + default: + printf("%s: unknown firmware for chip %s\n", + DEVNAME(sc), bwfm->sc_chip.ch_name); + return; + } + + if (loadfirmware(name, &ucode, &size) != 0) { + printf("%s: failed loadfirmware of file %s\n", + DEVNAME(sc), name); + return; + } + + if (loadfirmware(nvname, &nvram, &nvlen) != 0) { + printf("%s: failed loadfirmware of file %s\n", + DEVNAME(sc), nvname); + free(ucode, M_DEVBUF, size); + return; + } + + sc->sc_alp_only = 1; + if (bwfm_sdio_load_microcode(sc, ucode, size, + nvram, nvlen) != 0) { + printf("%s: could not load microcode\n", + DEVNAME(sc)); + free(ucode, M_DEVBUF, size); + free(nvram, M_DEVBUF, nvlen); + return; + } + sc->sc_alp_only = 0; + free(ucode, M_DEVBUF, size); + free(nvram, M_DEVBUF, nvlen); + + bwfm_sdio_clkctl(sc, CLK_AVAIL, 0); + if (sc->sc_clkstate != CLK_AVAIL) + return; + + clk = bwfm_sdio_read_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR); + bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR, + clk | BWFM_SDIO_FUNC1_CHIPCLKCSR_FORCE_HT); + + bwfm_sdio_dev_write(sc, SDPCMD_TOSBMAILBOXDATA, + SDPCM_PROT_VERSION << SDPCM_PROT_VERSION_SHIFT); + if (sdmmc_io_function_enable(sc->sc_sf[2]) != 0) { + printf("%s: cannot enable function 2\n", DEVNAME(sc)); + return; + } + + bwfm_sdio_dev_write(sc, SDPCMD_HOSTINTMASK, + SDPCMD_INTSTATUS_HMB_SW_MASK|SDPCMD_INTSTATUS_CHIPACTIVE); + bwfm_sdio_write_1(sc, BWFM_SDIO_WATERMARK, 8); + + if (bwfm_chip_sr_capable(bwfm)) { + reg = bwfm_sdio_read_1(sc, BWFM_SDIO_FUNC1_WAKEUPCTRL); + reg |= BWFM_SDIO_FUNC1_WAKEUPCTRL_HTWAIT; + bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_WAKEUPCTRL, reg); + bwfm_sdio_write_1(sc, BWFM_SDIO_CCCR_CARDCAP, + BWFM_SDIO_CCCR_CARDCAP_CMD14_SUPPORT | + BWFM_SDIO_CCCR_CARDCAP_CMD14_EXT); + bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR, + BWFM_SDIO_FUNC1_CHIPCLKCSR_FORCE_HT); + sc->sc_sr_enabled = 1; + } else { + bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR, clk); + } + + /* if interrupt establish fails */ + sc->sc_ih = sdmmc_intr_establish(bwfm->sc_dev.dv_parent, + bwfm_sdio_intr, sc, DEVNAME(sc)); + if (sc->sc_ih == NULL) { + printf("%s: can't establish interrupt\n", DEVNAME(sc)); + bwfm_sdio_clkctl(sc, CLK_NONE, 0); + return; + } + sdmmc_intr_enable(sc->sc_sf[1]); + rw_exit(sc->sc_lock); + + sc->sc_sc.sc_bus_ops = &bwfm_sdio_bus_ops; + sc->sc_sc.sc_proto_ops = &bwfm_proto_bcdc_ops; + bwfm_attach(&sc->sc_sc); +} + +int +bwfm_sdio_load_microcode(struct bwfm_sdio_softc *sc, u_char *ucode, size_t size, + u_char *nvram, size_t nvlen) +{ + struct bwfm_softc *bwfm = (void *)sc; + char *verify = NULL; + int err = 0; + + bwfm_sdio_clkctl(sc, CLK_AVAIL, 0); + + /* Upload firmware */ + err = bwfm_sdio_ram_read_write(sc, bwfm->sc_chip.ch_rambase, + ucode, size, 1); + if (err) + goto out; + + /* Verify firmware */ + verify = malloc(size, M_TEMP, M_WAITOK | M_ZERO); + err = bwfm_sdio_ram_read_write(sc, bwfm->sc_chip.ch_rambase, + verify, size, 0); + if (err || memcmp(verify, ucode, size)) { + printf("%s: firmware verification failed\n", + DEVNAME(sc)); + free(verify, M_TEMP, size); + goto out; + } + free(verify, M_TEMP, size); + + /* Upload nvram */ + err = bwfm_sdio_ram_read_write(sc, bwfm->sc_chip.ch_rambase + + bwfm->sc_chip.ch_ramsize - nvlen, nvram, nvlen, 1); + if (err) + goto out; + + /* Verify nvram */ + verify = malloc(nvlen, M_TEMP, M_WAITOK | M_ZERO); + err = bwfm_sdio_ram_read_write(sc, bwfm->sc_chip.ch_rambase + + bwfm->sc_chip.ch_ramsize - nvlen, verify, nvlen, 0); + if (err || memcmp(verify, nvram, nvlen)) { + printf("%s: nvram verification failed\n", + DEVNAME(sc)); + free(verify, M_TEMP, nvlen); + goto out; + } + free(verify, M_TEMP, nvlen); + + /* Load reset vector from firmware and kickstart core. */ + bwfm_chip_set_active(bwfm, *(uint32_t *)ucode); + +out: + bwfm_sdio_clkctl(sc, CLK_SDONLY, 0); + return err; +} + +void +bwfm_sdio_clkctl(struct bwfm_sdio_softc *sc, enum bwfm_sdio_clkstate newstate, + int pendok) +{ + enum bwfm_sdio_clkstate oldstate; + + oldstate = sc->sc_clkstate; + if (sc->sc_clkstate == newstate) + return; + + switch (newstate) { + case CLK_AVAIL: + if (sc->sc_clkstate == CLK_NONE) + sc->sc_clkstate = CLK_SDONLY; + bwfm_sdio_htclk(sc, 1, pendok); + break; + case CLK_SDONLY: + if (sc->sc_clkstate == CLK_NONE) + sc->sc_clkstate = CLK_SDONLY; + else if (sc->sc_clkstate == CLK_AVAIL) + bwfm_sdio_htclk(sc, 0, 0); + else + printf("%s: request for %d -> %d\n", + DEVNAME(sc), sc->sc_clkstate, newstate); + break; + case CLK_NONE: + if (sc->sc_clkstate == CLK_AVAIL) + bwfm_sdio_htclk(sc, 0, 0); + sc->sc_clkstate = CLK_NONE; + break; + default: + break; + } + + DPRINTF(("%s: %d -> %d = %d\n", DEVNAME(sc), oldstate, newstate, + sc->sc_clkstate)); +} + +void +bwfm_sdio_htclk(struct bwfm_sdio_softc *sc, int on, int pendok) +{ + uint32_t clkctl, devctl, req; + int i; + + if (sc->sc_sr_enabled) { + if (on) + sc->sc_clkstate = CLK_AVAIL; + else + sc->sc_clkstate = CLK_SDONLY; + return; + } + + if (on) { + if (sc->sc_alp_only) + req = BWFM_SDIO_FUNC1_CHIPCLKCSR_ALP_AVAIL_REQ; + else + req = BWFM_SDIO_FUNC1_CHIPCLKCSR_HT_AVAIL_REQ; + bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR, req); + + clkctl = bwfm_sdio_read_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR); + if (!BWFM_SDIO_FUNC1_CHIPCLKCSR_CLKAV(clkctl, sc->sc_alp_only) + && pendok) { + devctl = bwfm_sdio_read_1(sc, BWFM_SDIO_DEVICE_CTL); + devctl |= BWFM_SDIO_DEVICE_CTL_CA_INT_ONLY; + bwfm_sdio_write_1(sc, BWFM_SDIO_DEVICE_CTL, devctl); + sc->sc_clkstate = CLK_PENDING; + return; + } else if (sc->sc_clkstate == CLK_PENDING) { + devctl = bwfm_sdio_read_1(sc, BWFM_SDIO_DEVICE_CTL); + devctl &= ~BWFM_SDIO_DEVICE_CTL_CA_INT_ONLY; + bwfm_sdio_write_1(sc, BWFM_SDIO_DEVICE_CTL, devctl); + } + + for (i = 0; i < 1000; i++) { + if (BWFM_SDIO_FUNC1_CHIPCLKCSR_CLKAV(clkctl, + sc->sc_alp_only)) + break; + clkctl = bwfm_sdio_read_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR); + delay(1000); + } + if (!BWFM_SDIO_FUNC1_CHIPCLKCSR_CLKAV(clkctl, sc->sc_alp_only)) { + printf("%s: HT avail timeout\n", DEVNAME(sc)); + return; + } + + sc->sc_clkstate = CLK_AVAIL; + } else { + if (sc->sc_clkstate == CLK_PENDING) { + devctl = bwfm_sdio_read_1(sc, BWFM_SDIO_DEVICE_CTL); + devctl &= ~BWFM_SDIO_DEVICE_CTL_CA_INT_ONLY; + bwfm_sdio_write_1(sc, BWFM_SDIO_DEVICE_CTL, devctl); + } + sc->sc_clkstate = CLK_SDONLY; + bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR, 0); + } +} + +int +bwfm_sdio_intr(void *v) +{ + struct bwfm_sdio_softc *sc = (void *)v; + task_add(systq, &sc->sc_task); + return 1; +} + +void +bwfm_sdio_task(void *v) +{ + struct bwfm_sdio_softc *sc = (void *)v; + uint32_t clkctl, devctl, intstat, hostint; + + rw_enter_write(sc->sc_lock); + + if (!sc->sc_sr_enabled && sc->sc_clkstate == CLK_PENDING) { + clkctl = bwfm_sdio_read_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR); + if (BWFM_SDIO_FUNC1_CHIPCLKCSR_HTAV(clkctl)) { + devctl = bwfm_sdio_read_1(sc, BWFM_SDIO_DEVICE_CTL); + devctl &= ~BWFM_SDIO_DEVICE_CTL_CA_INT_ONLY; + bwfm_sdio_write_1(sc, BWFM_SDIO_DEVICE_CTL, devctl); + sc->sc_clkstate = CLK_AVAIL; + } + } + + intstat = bwfm_sdio_dev_read(sc, BWFM_SDPCMD_INTSTATUS); + intstat &= (SDPCMD_INTSTATUS_HMB_SW_MASK|SDPCMD_INTSTATUS_CHIPACTIVE); + /* XXX fc state */ + if (intstat) + bwfm_sdio_dev_write(sc, BWFM_SDPCMD_INTSTATUS, intstat); + + if (intstat & SDPCMD_INTSTATUS_HMB_HOST_INT) { + hostint = bwfm_sdio_dev_read(sc, SDPCMD_TOHOSTMAILBOXDATA); + bwfm_sdio_dev_write(sc, SDPCMD_TOSBMAILBOX, + SDPCMD_TOSBMAILBOX_INT_ACK); + if (hostint & SDPCMD_TOHOSTMAILBOXDATA_NAKHANDLED) + intstat |= SDPCMD_INTSTATUS_HMB_FRAME_IND; + } + + /* FIXME: Might stall if we don't when not set. */ + if (1 || intstat & SDPCMD_INTSTATUS_HMB_FRAME_IND) { + bwfm_sdio_rx_frames(sc); + } + + if (sc->sc_txctl_buf) { + bwfm_sdio_tx_ctrlframe(sc); + } + + if (!mq_empty(&sc->sc_txdata_queue)) { + bwfm_sdio_tx_dataframe(sc); + } + + rw_exit(sc->sc_lock); +} + int bwfm_sdio_detach(struct device *self, int flags) { @@ -334,6 +723,107 @@ bwfm_sdio_write_4(struct bwfm_sdio_softc *sc, uint32_t addr, uint32_t data) sdmmc_io_write_4(sf, addr, data); } +int +bwfm_sdio_buf_read(struct bwfm_sdio_softc *sc, struct sdmmc_function *sf, + uint32_t reg, char *data, size_t size) +{ + int err; + + if (sf == sc->sc_sf[1]) + err = sdmmc_io_read_multi_1(sf, reg, data, size, 1); + else + err = sdmmc_io_read_multi_1(sf, reg, data, size, 0); + + if (err) + printf("%s: error %d\n", __func__, err); + + return err; +} + +int +bwfm_sdio_buf_write(struct bwfm_sdio_softc *sc, struct sdmmc_function *sf, + uint32_t reg, char *data, size_t size) +{ + int err; + + err = sdmmc_io_write_multi_1(sf, reg, data, size, 1); + + if (err) + printf("%s: error %d\n", __func__, err); + + return err; +} + +uint32_t +bwfm_sdio_ram_read_write(struct bwfm_sdio_softc *sc, uint32_t reg, + char *data, size_t left, int write) +{ + uint32_t sbaddr, sdaddr, off; + size_t size; + int err; + + err = off = 0; + while (left > 0) { + sbaddr = reg + off; + bwfm_sdio_backplane(sc, sbaddr); + + sdaddr = (reg + off) & BWFM_SDIO_SB_OFT_ADDR_MASK; + size = min(left, (BWFM_SDIO_SB_OFT_ADDR_PAGE - sdaddr)); + sdaddr |= BWFM_SDIO_SB_ACCESS_2_4B_FLAG; + + if (write) + err = bwfm_sdio_buf_write(sc, sc->sc_sf[1], sdaddr, + data+off, size); + else + err = bwfm_sdio_buf_read(sc, sc->sc_sf[1], sdaddr, + data+off, size); + if (err) + break; + + off += size; + left -= size; + } + + return err; +} + +uint32_t +bwfm_sdio_frame_read_write(struct bwfm_sdio_softc *sc, + char *data, size_t size, int write) +{ + uint32_t addr; + int err; + + addr = sc->sc_cc->co_base; + bwfm_sdio_backplane(sc, addr); + + addr &= BWFM_SDIO_SB_OFT_ADDR_MASK; + addr |= BWFM_SDIO_SB_ACCESS_2_4B_FLAG; + + if (write) + err = bwfm_sdio_buf_write(sc, sc->sc_sf[2], addr, data, size); + else + err = bwfm_sdio_buf_read(sc, sc->sc_sf[2], addr, data, size); + + return err; +} + +uint32_t +bwfm_sdio_dev_read(struct bwfm_sdio_softc *sc, uint32_t reg) +{ + struct bwfm_core *core; + core = bwfm_chip_get_core(&sc->sc_sc, BWFM_AGENT_CORE_SDIO_DEV); + return bwfm_sdio_read_4(sc, core->co_base + reg); +} + +void +bwfm_sdio_dev_write(struct bwfm_sdio_softc *sc, uint32_t reg, uint32_t val) +{ + struct bwfm_core *core; + core = bwfm_chip_get_core(&sc->sc_sc, BWFM_AGENT_CORE_SDIO_DEV); + bwfm_sdio_write_4(sc, core->co_base + reg, val); +} + uint32_t bwfm_sdio_buscore_read(struct bwfm_softc *bwfm, uint32_t reg) { @@ -400,53 +890,387 @@ void bwfm_sdio_buscore_activate(struct bwfm_softc *bwfm, uint32_t rstvec) { struct bwfm_sdio_softc *sc = (void *)bwfm; - struct bwfm_core *core; - core = bwfm_chip_get_core(&sc->sc_sc, BWFM_AGENT_CORE_SDIO_DEV); - bwfm_sdio_buscore_write(&sc->sc_sc, - core->co_base + BWFM_SDPCMD_INTSTATUS, 0xFFFFFFFF); + bwfm_sdio_dev_write(sc, BWFM_SDPCMD_INTSTATUS, 0xFFFFFFFF); -#if notyet if (rstvec) - bwfm_sdio_ram_write(&sc->sc_sc, 0, &rstvec, sizeof(rstvec)); -#endif + bwfm_sdio_ram_read_write(sc, 0, (char *)&rstvec, + sizeof(rstvec), 1); +} + +struct mbuf * +bwfm_sdio_newbuf(void) +{ + struct mbuf *m; + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + return (NULL); + + MCLGET(m, M_DONTWAIT); + if (!(m->m_flags & M_EXT)) { + m_freem(m); + return (NULL); + } + + m->m_len = m->m_pkthdr.len = MCLBYTES; + + return (m); +} + +void +bwfm_sdio_tx_ctrlframe(struct bwfm_sdio_softc *sc) +{ + struct bwfm_sdio_hwhdr *hwhdr; + struct bwfm_sdio_swhdr *swhdr; + char *buf; + size_t len; + + if (sc->sc_txctl_buf == NULL) + return; + + len = sizeof(*hwhdr) + sizeof(*swhdr) + sc->sc_txctl_len; + buf = malloc(len, M_TEMP, M_WAITOK | M_ZERO); + + hwhdr = (void *)buf; + hwhdr->frmlen = htole16(len); + hwhdr->cksum = htole16(~len); + + swhdr = (void *)&hwhdr[1]; + swhdr->seqnr = sc->sc_tx_seq++; + swhdr->chanflag = BWFM_SDIO_SWHDR_CHANNEL_CONTROL; + swhdr->nextlen = 0; + swhdr->dataoff = sizeof(*hwhdr) + sizeof(*swhdr); + swhdr->maxseqnr = 0; + + memcpy(&swhdr[1], sc->sc_txctl_buf, sc->sc_txctl_len); + + bwfm_sdio_frame_read_write(sc, buf, len, 1); + + free(buf, M_TEMP, len); + wakeup(&sc->sc_txctl_buf); +} + +void +bwfm_sdio_tx_dataframe(struct bwfm_sdio_softc *sc) +{ + struct ifnet *ifp = &sc->sc_sc.sc_ic.ic_if; + struct bwfm_sdio_hwhdr *hwhdr; + struct bwfm_sdio_swhdr *swhdr; + struct bwfm_proto_bcdc_hdr *bcdc; + struct mbuf *m; + char *buf; + size_t len; + + for (;;) { + m = mq_dequeue(&sc->sc_txdata_queue); + if (m == NULL) + return; + + len = sizeof(*hwhdr) + sizeof(*swhdr) + sizeof(*bcdc) + + m->m_pkthdr.len; + buf = malloc(len, M_TEMP, M_WAITOK | M_ZERO); + + hwhdr = (void *)buf; + hwhdr->frmlen = htole16(len); + hwhdr->cksum = htole16(~len); + + swhdr = (void *)&hwhdr[1]; + swhdr->seqnr = sc->sc_tx_seq++; + swhdr->chanflag = BWFM_SDIO_SWHDR_CHANNEL_DATA; + swhdr->nextlen = 0; + swhdr->dataoff = sizeof(*hwhdr) + sizeof(*swhdr); + swhdr->maxseqnr = 0; + + bcdc = (void *)&swhdr[1]; + bcdc->data_offset = 0; + bcdc->priority = ieee80211_classify(&sc->sc_sc.sc_ic, m); + bcdc->flags = BWFM_BCDC_FLAG_VER(BWFM_BCDC_FLAG_PROTO_VER); + bcdc->flags2 = 0; + + m_copydata(m, 0, m->m_pkthdr.len, (caddr_t)&bcdc[1]); + + bwfm_sdio_frame_read_write(sc, buf, len, 1); + + free(buf, M_TEMP, len); + m_freem(m); + } + + ifq_restart(&ifp->if_snd); +} + +void +bwfm_sdio_rx_frames(struct bwfm_sdio_softc *sc) +{ + struct bwfm_sdio_hwhdr hwhdr; + struct bwfm_sdio_swhdr swhdr; + uint16_t *sublen; + struct mbuf *m; + int nsub; + size_t flen; + off_t off; + char *buf; + + do { + if (bwfm_sdio_frame_read_write(sc, (char *)&hwhdr, + sizeof(hwhdr), 0)) + break; + + hwhdr.frmlen = letoh16(hwhdr.frmlen); + hwhdr.cksum = letoh16(hwhdr.cksum); + + if (hwhdr.frmlen == 0 && hwhdr.cksum == 0) + break; + + if ((hwhdr.frmlen ^ hwhdr.cksum) != 0xffff) { + printf("%s: checksum error\n", DEVNAME(sc)); + break; + } + + if (hwhdr.frmlen < sizeof(hwhdr) + sizeof(swhdr)) { + printf("%s: length error\n", DEVNAME(sc)); + break; + } + + if (bwfm_sdio_frame_read_write(sc, (char *)&swhdr, + sizeof(swhdr), 0)) + break; + + flen = hwhdr.frmlen - (sizeof(hwhdr) + sizeof(swhdr)); + if (flen == 0) + continue; + + buf = sc->sc_rxdata_buf; + if (bwfm_sdio_frame_read_write(sc, buf, flen, 0)) + break; + + if (swhdr.dataoff < (sizeof(hwhdr) + sizeof(swhdr))) + break; + + off = swhdr.dataoff - (sizeof(hwhdr) + sizeof(swhdr)); + if (off > flen) + break; + + switch (swhdr.chanflag & BWFM_SDIO_SWHDR_CHANNEL_MASK) { + case BWFM_SDIO_SWHDR_CHANNEL_CONTROL: + if (sc->sc_rxctl_buf != NULL) { + printf("%s: new frame but old one still there\n", + DEVNAME(sc)); + break; + } + m = bwfm_sdio_newbuf(); + if (m == NULL) + break; + if (flen - off > m->m_len) { + printf("%s: frame bigger than anticipated\n", + DEVNAME(sc)); + m_free(m); + break; + } + m->m_len = m->m_pkthdr.len = flen - off; + memcpy(mtod(m, char *), buf + off, flen - off); + sc->sc_rxctl_buf = m; + wakeup(&sc->sc_rxctl_buf); + break; + case BWFM_SDIO_SWHDR_CHANNEL_EVENT: + case BWFM_SDIO_SWHDR_CHANNEL_DATA: + m = bwfm_sdio_newbuf(); + if (m == NULL) + break; + m_adj(m, ETHER_ALIGN); + if (flen - off > m->m_len) { + printf("%s: frame bigger than anticipated\n", + DEVNAME(sc)); + m_free(m); + break; + } + m->m_len = m->m_pkthdr.len = flen - off; + memcpy(mtod(m, char *), buf + off, flen - off); + sc->sc_sc.sc_proto_ops->proto_rx(&sc->sc_sc, m); + break; + case BWFM_SDIO_SWHDR_CHANNEL_GLOM: + if ((flen % sizeof(uint16_t)) != 0) + break; + nsub = flen / sizeof(uint16_t); + sublen = mallocarray(nsub, sizeof(uint16_t), + M_DEVBUF, M_WAITOK | M_ZERO); + memcpy(sublen, buf, nsub * sizeof(uint16_t)); + bwfm_sdio_rx_glom(sc, sublen, nsub); + free(sublen, M_DEVBUF, nsub * sizeof(uint16_t)); + break; + default: + printf("%s: unknown channel\n", DEVNAME(sc)); + break; + } + } while (swhdr.nextlen); +} + +void +bwfm_sdio_rx_glom(struct bwfm_sdio_softc *sc, uint16_t *sublen, int nsub) +{ + struct bwfm_sdio_hwhdr hwhdr; + struct bwfm_sdio_swhdr swhdr; + struct mbuf_list ml, drop; + struct mbuf *m; + size_t flen; + off_t off; + int i; + + ml_init(&ml); + ml_init(&drop); + + if (nsub == 0) + return; + + for (i = 0; i < nsub; i++) { + m = bwfm_sdio_newbuf(); + if (m == NULL) { + ml_purge(&ml); + return; + } + ml_enqueue(&ml, m); + if (letoh16(sublen[i]) > m->m_len) { + ml_purge(&ml); + return; + } + if (bwfm_sdio_frame_read_write(sc, mtod(m, char *), + letoh16(sublen[i]), 0)) { + ml_purge(&ml); + return; + } + m->m_len = letoh16(sublen[i]); + } + + /* TODO: Verify actual superframe header */ + m = MBUF_LIST_FIRST(&ml); + m_adj(m, sizeof(struct bwfm_sdio_hwhdr) + sizeof(struct bwfm_sdio_swhdr)); + + while ((m = ml_dequeue(&ml)) != NULL) { + if (m->m_len < sizeof(hwhdr) + sizeof(swhdr)) + goto drop; + + m_copydata(m, 0, sizeof(hwhdr), (caddr_t)&hwhdr); + m_copydata(m, sizeof(hwhdr), sizeof(swhdr), (caddr_t)&swhdr); + + hwhdr.frmlen = letoh16(hwhdr.frmlen); + hwhdr.cksum = letoh16(hwhdr.cksum); + + if (hwhdr.frmlen == 0 && hwhdr.cksum == 0) + goto drop; + + if ((hwhdr.frmlen ^ hwhdr.cksum) != 0xffff) { + printf("%s: checksum error\n", DEVNAME(sc)); + goto drop; + } + + if (hwhdr.frmlen < sizeof(hwhdr) + sizeof(swhdr)) { + printf("%s: length error\n", DEVNAME(sc)); + goto drop; + } + + flen = hwhdr.frmlen - (sizeof(hwhdr) + sizeof(swhdr)); + if (flen == 0) + goto drop; + if (m->m_len < flen) + goto drop; + + if (swhdr.dataoff < (sizeof(hwhdr) + sizeof(swhdr))) + goto drop; + + off = swhdr.dataoff - (sizeof(hwhdr) + sizeof(swhdr)); + if (off > flen) + goto drop; + + switch (swhdr.chanflag & BWFM_SDIO_SWHDR_CHANNEL_MASK) { + case BWFM_SDIO_SWHDR_CHANNEL_CONTROL: + printf("%s: control channel not allowed in glom\n", + DEVNAME(sc)); + goto drop; + case BWFM_SDIO_SWHDR_CHANNEL_EVENT: + case BWFM_SDIO_SWHDR_CHANNEL_DATA: + m_adj(m, swhdr.dataoff); + sc->sc_sc.sc_proto_ops->proto_rx(&sc->sc_sc, m); + break; + case BWFM_SDIO_SWHDR_CHANNEL_GLOM: + printf("%s: glom not allowed in glom\n", + DEVNAME(sc)); + goto drop; + default: + printf("%s: unknown channel\n", DEVNAME(sc)); + goto drop; + } + + continue; +drop: + ml_enqueue(&drop, m); + } + + ml_purge(&drop); +} + +int +bwfm_sdio_txcheck(struct bwfm_softc *bwfm) +{ + struct bwfm_sdio_softc *sc = (void *)bwfm; + + if (mq_full(&sc->sc_txdata_queue)) + return ENOBUFS; + + return 0; } int bwfm_sdio_txdata(struct bwfm_softc *bwfm, struct mbuf *m) { -#ifdef BWFM_DEBUG struct bwfm_sdio_softc *sc = (void *)bwfm; -#endif - int ret = 1; - DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + if (mq_full(&sc->sc_txdata_queue)) + return ENOBUFS; - return ret; + mq_enqueue(&sc->sc_txdata_queue, m); + task_add(systq, &sc->sc_task); + return 0; } int bwfm_sdio_txctl(struct bwfm_softc *bwfm, char *buf, size_t len) { -#ifdef BWFM_DEBUG struct bwfm_sdio_softc *sc = (void *)bwfm; -#endif - int ret = 1; - DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + if (sc->sc_txctl_buf) { + printf("%s: another txctl in flight\n", DEVNAME(sc)); + return 1; + } + + sc->sc_txctl_buf = buf; + sc->sc_txctl_len = len; - return ret; + task_add(systq, &sc->sc_task); + if (tsleep(&sc->sc_txctl_buf, PCATCH, "bwfm", hz)) { + printf("%s: timeout waiting for txctl response\n", + DEVNAME(sc)); + return 1; + } + + sc->sc_txctl_buf = NULL; + return 0; } int bwfm_sdio_rxctl(struct bwfm_softc *bwfm, char *buf, size_t *len) { -#ifdef BWFM_DEBUG struct bwfm_sdio_softc *sc = (void *)bwfm; -#endif - int ret = 1; - DPRINTF(("%s: %s\n", DEVNAME(sc), __func__)); + if (sc->sc_rxctl_buf == NULL) { + tsleep(&sc->sc_rxctl_buf, PCATCH, "bwfm", hz); + if (sc->sc_rxctl_buf == NULL) + return 1; + } + + *len = min(*len, sc->sc_rxctl_buf->m_len); + memcpy(buf, mtod(sc->sc_rxctl_buf, char*), *len); + m_freem(sc->sc_rxctl_buf); + sc->sc_rxctl_buf = NULL; - return ret; + return 0; }