本文总结BCM43438的初始化流程,初始化成功的标志是连上WiFi。
参考WICED SDK、NuttX,以及Linux的源码,它们的流程略有不同。Linux的驱动面面俱到;WICED的驱动虽只针对物联网设备,却写得相当冗杂;nuttx的驱动则相当简洁恰好能用。
- WICED可从Cypress官网上下载,注册个账号就好了。这份软件架构简直是一切以WICED为中心,有bootloader、dct(Device Configuration Table)、OTA(Over The Air)等自创的元素,如果用它支持的平台如stm32的话,那可以非常迅速地出成果,然而如果要移植到别的CPU架构的话它并不太友好。。
- NuttX可从bitbucket/nuttx上git clone一份,或者从SourceForge下载代码包。SourceForge上面的7.24及以前的驱动有问题,我在bitbucket上提交了这份commit。bitbucket在墙内访问相当不稳定。。
- Linux可以直接访问Bootlin的Linux Cross Reference。在Linux内核中,博通SDIO接口的网卡驱动是
brcmfmac
,归类到brcm80211
,这是博通官方提供的驱动,需要non-free的固件。b43
的驱动是社区通过一些逆向工程实现的,只支持老版的PCIe网卡。。
初始化过程纯粹就是面向过程的代码,因此以下通过罗列它们的流程来进行分析。
固件这玩意儿想必是厂商喜闻乐见的东西——在你编写的程序中安插黑盒子,而你又无可奈何。
SDIO卡简述
BCM43438是一个SDIO卡。SDIO卡
跟SD卡
虽然都用了SDIO接口,但是二者不可混为一谈。
- SD卡是SD memory card,驱动通用;
- SDIO卡就是为IO而准备的,为此使用了扩展的SDIO规范。
- 如果卡中既有存储,又有IO,则被称为combo卡。
SDIO卡具有IO空间,通过CMD52
和CMD53
这两条的指令读写其中的数据:
- CMD52,单字节IO读写,数据只通过CMD引脚传输;
- CMD53,多字节IO读写,数据通过DATA[3:0]传输,非常适合传输大批量数据;
IO空间里有多个Function
,一个Function就是一块独立的IO空间:
- 第0个Function叫
CIA(common IO area)
,其中包含了多个寄存器组,基本上只在初始化时候才会用到:- CCCR(Card Common Control Registers),功能诸如开关其他Function、开关中断等等;
- FBR(Function Basic Registers),其中有设置该Function的block size的寄存器,还有指向下面CIS区域的指针;
- CIS(Card Information Structure),除了SDIO specification里面有介绍外,没找到其他资料,而且WICED和nuttx里代码里面也没有用到。。
- 另外最多支持7个Function,里面的寄存器由厂商自己定义。BCM43438有两个Function:
- Function1叫
Backplane
,来源于Sonics Silicon Backplane(SSB),是可以访问整个SOC的地址空间的总线。不过只有老版的网卡使用SSB总线,新版网卡(包括我们的BCM43438)则使用AXI总线,二者的接口并不同,只是名称沿用了下来。 - Function2叫
WLAN
,与固件交互时候使用,传输控制命令以及数据。
- Function1叫
IO空间的编号、寄存器地址编码到CMD52、CMD53命令中,从而进行读写操作。
BCM43438-bringup
博通家SDIO接口的网卡初始化流程比较通用,下面罗列出要做的事情,WICED、nuttx、Linux里的执行顺序略有不同,说明初始化流程并不是唯一的。
- Linux中涉及到的bcmfmac里的文件有:chip.c、sdio.c、firmware.c
- WICED里主要就是WICED/WWD/internal/bus_protocols/SDIO/ wwd_bus_protocol.c
- Nuttx则主要是drivers/wireless/ieee80211/ bcmf_sdio.c
枚举SDIO卡
- WICED:WICED/platform/MCU/xxxxxxxx/WWD/WWD_SDIO.c: host_platform_sdio_enumerate(),是平台相关的部分;
- Nuttx:drivers/wireless/ieee80211/mmc_sdio.c: sdio_probe(), 最终调用平台相关的代码;
- Linux:在mmc层,drivers/mmc/core/core.c:mmc_rescan_try_freq()。mmc层的入口可以说是插卡时候的中断进程mmc_gpio_cd_irqt()(它之前被request成threaded的irq)。
它们的流程都一样:
- 发送
CMD5
,没回应就按照SD存储卡初始化流程走,若有回应则是SDIO卡; - 发送
CMD3
,让卡返回一个自己生成的RCA(Relative Card Address); - 将刚才的RCA作为参数,发送
CMD7
,从而选中那张卡;
使能Function
- WICED:WICED/WWD/internal/bus_protocols/SDIO/wwd_bus_protocol.c: wwd_bus_init()
- Nuttx:drivers/wireless/ieee80211/bcmf_sdio.c: bcmf_probe()和bcmf_businitialize()
- Linux:drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c: brcmf_sdiod_probe(),被sdio_driver的probe函数调用。
首先初始化Function0、1的寄存器。一部分通用寄存器在SDIO specification里讲述,其他的寄存器没有资料。
- 使能Backplane即F1(写F0的CCCR_IOEN寄存器)。
- 设置4位总线宽度(写F0的CCCR_BICTL寄存器)。另外主机也要设置为4bit,并可以提高时钟速度。
- 设置各个Function的block size(写F0的CCCR_FNx_BLKSIZE0、1、2,每个Function各两个寄存器)。它都设为64了,虽然数据手册上说Function0的最大只有32。。。
- 使能Function1、2的中断(写F0的CCCR_INTEN寄存器)。
- 在Function1中,使能“ALP(Active Low-Power)”时钟(写F1的CHIPCLKCSR寄存器,BCM定义的)。
使能了F1之后可以随时读F1的0x18000000
地址处读到设备ID号,在Linux的brcmfmac的驱动中,该地址处的结构体为struct chipregs,第一个元素是chipid。而WICED和nuttx的驱动里面没发现这种结构体——它们都用的是一大堆宏定义的寄存器地址。不同的模块chipid可见Linux内核的drivers/net/wireless/broadcom/brcm80211/include/ brcm_hw_ids.h,其中BCM43438的ID是十进制的43430。
0x18000000这个地址在brcmfmac里被命名为SI_ENUM_BASE,在老版SSB驱动里被命名为SSB_ENUM_BASE。brcmfmac里SSB总线、AXI总线分别实现了,体现了bcm网卡内部总线的发展历史。。。
总线 | chipid最高8位 | 接口前缀 | 头文件 |
---|---|---|---|
SSB | 0 | brcmf_chip_sb_ | linux/ssb/ssb_regs.h |
AXI | 1 | brcmf_chip_ai_ | linux/bcma/bcma_regs.h |
传输固件
固件有三种:
- 两三百kb的大固件
- 一两kb的nvram
- 几kb的clm blob,这不是必需品
在这个阶段首先传前两个固件。大固件从F1的RAM首地址开始放,nvram放在RAM末尾倒数4个字节之前,RAM最后4个字节是编码之后的nvram大小。
nvram的配置
nvram是一个字符串数组,目的在于调试时候设置一些参数,它们最终要烧写到BCM43438的OTP(One Time Programable)存储器里面的。BCM固件收到nvram之后会与OTP空间对比,同名的参数配置以OTP为准。因此实际操作中为了保持一些灵活性,大家都不会把nvram里面所有的东西都写入OTP里面。详细的参数设置可以参考Cypress的这份文档:AN214807:OTP Programming and NVRAM Development in SDIO Mode。
- WICED:在platforms/XXXXXXXXXX/wifi_nvram_image.h中定义的数组。
- NuttX:在configs目录下板级配置中定义的数组,如configs/photon/src/stm32_wlan_firmware.c。
Linux:通用固件nvram_ap6212a.txt
#AP6212A_NVRAM_V1.0.1_20160606 # 2.4 GHz, 20 MHz BW mode # The following parameter values are just placeholders, need to be updated. manfid=0x2d0 prodid=0x0726 vendid=0x14e4 devid=0x43e2 boardtype=0x0726 ...
其中需要留意的参数有:
macaddr
,设置MAC地址。其实现在买到的AP6xxx模块的OTP里面都已经有这个参数了,除非直接订购的是BCM43438芯片。xtalfreq
,设置晶振的频率,单位为kHz。有些板子用的是37.4M晶振,有些用的是26M晶振。一定要设对,否则后面PLL时钟起不来。
需要特别注意的是,最终下载到芯片里的东西必然是WICED、nuttx里面的那种风格的数组,每一个xxxx=xxxx
的字符串键值对之后都有一个0字符,而不是Linux的配置文件那种风格。Linux的nvram.txt是经过处理才下载的,详见drivers/net/wireless/broadcom/brcm80211/brcmfmac/ firmware.c。它使用状态机进行词法分析,滤除注释,然后brcmf_fw_add_defaults(),添加0字符。
下载固件之前
首先需要disable芯片的Cores,包括Cortex M3的核,还有内存控制器的核。
另外BCM43438需要取消SRAM bank 3的remap。Linux写在drivers/net/wireless/broadcom/brcm80211/brcmfmac/ chip.c:brcmf_chip_cm3_set_passive()处;WICED则是WICED/WWD/internal/chips/xxxxx/ wwd_chip_specific_functions.c: wwd_chip_specific_socsram_init()。nuttx目前只是单纯在下载固件之前判断了一下。
下载固件
根据不同的ID可以选择不同的固件。
- Linux:在brcmfmac/sdio.c里定义一堆固件名称,从而可以在brcmf_fw_get_firmwares()处从文件系统里request_firmware。sdio.c中传入callback函数brcmf_sdio_firmware_callback(),随后brcmf_sdio_firmware_callback(),传输大固件以及nvram。
- WICED:层层调用到WICED/WWD/internal/bus_protocols/ wwd_bus_common.c: download_resource(),然后使用 host_platform_resource_read_direct() 或者host_platform_resource_read_indirect()函数,从片内flash或者片外flash里面读取固件,然后wwd_bus_transfer_bytes()下载到BCM芯片内。因为WICED运行时的平台单一,所以它并不考虑通过不同ID号选择不同的固件。
- nuttx:在drivers/wireless/ieee80211/bcmf_sdio.c: bcmf_chipinitialize()里通过ID号switch case一个芯片,随后drivers/wireless/ieee80211/bcmf_core.c: bcmf_core_upload_firmware(),然后bcmf_upload_binary()。目前nuttx的固件直接写为一个巨大的uint8_t数组。。。比如photon的固件,在configs/photon/src/ stm32_wlan_firmware.c里。
等待固件初始化时钟
- Linux:brcmfmac/sdio.c:brcmf_sdio_htclk(),有个循环;
- WICED:WICED/WWD/internal/bus_protocols/SDIO/wwd_bus_protocol.c: wwd_bus_sdio_download_firmware(),下载了firmware和nvram之后有个循环;
- NuttX:drivers/wireless/ieee80211/bcmf_sdio.c: bcmf_sdio_bus_sleep(false),这有个循环。下完固件,设置中断的时候才调用它。
做法就是循环读F1的CHIPCLKCSR寄存器的第7位,命名为High Throughput Clock Available。这一步经常会不过关,原因可能是:
- 大固件不对,比方说将BCM43438-A0的固件下到了A1的芯片上面了。
- nvram不对,可能直接将Linux的哪个txt下进去了,可能是xtalfreq不对,可能是别的东西。。。
- 板子问题,晶振不起振,等等。
- 等待的时间不够长,可能板子焊的不太好,要多等一会儿才能available。
如果这一步过关了,说明这个固件或许可以用,后面或许就轻松了不少。。
然后收尾
- 使能F2、F2的中断
- 初始化save-restore功能。并不是所有固件都支持sr功能。
- WICED:WICED/WWD/internal/chips/xxxxx/ wwd_chip_specific_functions.c: wwd_chip_specific_init()
- Linux:brcmf_sdio_firmware_callback()之后brcmf_sdio_sr_init()
- Nuttx:我照着WICED的代码在bcmf_sdio.c里面添加了bcmf_sdio_sr_init()。之前虽然没有,但是据说也能用。。
与固件交互进行初始化
与固件交互,用的是Function2即WLAN,Linux、nuttx里将该功能注释为“frame transfer”。在Function2传输的数据要符合BCM定义的一套协议,分为两层:
- 上层:bdc、cdc
- bdc用于传输数据包,接入IP协议栈的底部。
- cdc用于传输控制命令,有ioctl和iovar之分。控制命令包括开关WiFi、查询或设置MAC地址等等。也可以说属于带外传输的范畴。
- 下层:sdpcm,用于包装bdc、cdc。
具体实现部分下一篇博客将讨论。这里仅讨论初始化阶段需要做的东西。
- Nuttx:drivers/wireless/ieee80211/bcmf_driver.c
- WICED:WICED/WWD/internal/wwd_management.c
- Linux:brcmfmac/brcmf_c_preinit_dcmds()。之前在brcmfmac/sdio.c:callback里调用brcmf_bus_started(),然后层层调用到它。
这阶段就是发送一些ioctl、iovar去查询一些信息,以及设置一些东西。ioctl
是一些整数指令号,可以附带一些参数;iovar
是字符串命令,也可以附带一些参数。
初始化参数
- 查询该固件的版本。发送名为
"ver"
的iovar,返回一个字符串。比方说它会返回”wl0: Jun 19 2016 22:40:09 version 7.45.45.17 (r644353) FWID 01-dbaba83” - 查询MAC地址。发送名为
"cur_etheraddr"
的iovar,返回6字节的MAC地址(返回的buffer不一定只有6字节,其他的都没用) - 关闭所谓的TX glomming:
"bus:txglom"
的iovar - 开启APSTA模式:
"apsta"
的iovar - 设置A-MPDU的参数。amsdu、ampdu的概念可参考这篇博客。
ampdu_
为前缀的一系列iovar - 设置国家和地区:
"country"
的iovar - 注册事件,使用
"event_msgs"
,参数是一个一百多位的位图,每一位代表一个事件,置一则代表主机打算相应这个事件。Linux在brcmfmac/fweh.h里定义为enum brcmf_fweh_event_code,nuttx在bcmf_ioctl.h里定义为enum wl_event_num_t,WICED在WICED/WWD/include/wwd_events.h里定义为enum wwd_event_num_t。 - WiFi up,发送
WLC_UP
的ioctl,编号是2。ifup这个命令在Linux和nuttx里都有,if的全称应该是interface,比方说ifup wlan0
;它底层就是发送了WLC_UP指令。
另外需要注意roaming(漫游)
的问题。bcmf固件有自动漫游功能,通过"roam_off"
的iovar来开关。不应该关闭;否则,如果现在连接的AP并不是信号最强的话,每隔一段时间就会自行断开当前连接,给上层一个DEAUTH
事件,然后连接信号最强者。Linux的brcmfmac驱动有个参数名叫brcmf_roamoff,默认不roam_off。而nuttx则默认roam_off,上层却缺乏断线重连的逻辑,因此相关的初始化应该去掉。
连接WiFi
就是join一个Access Point(AP),一个AP一般有一个名字叫SSID,以及密码(当然也有隐藏的WiFi和开放的WiFi)。对于Linux和nuttx来说决定要连接WiFi的是应用程序而不是内核,需要调用ioctl来陷入内核从而进行操作。
- Linux:drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c:brcmf_cfg80211_connect()
- WICED:WICED/WWD/internal/wwd_wifi_join()
- NuttX:drivers/wireless/ieee80211/bcmf_driver.c:bcmf_wl_set_ssid(),它在ioctl(SIOCSIWESSID)时候被调用。
流程如下:
- 设置wireless mode为infra或adhoc,使用ioclt为
WLC_SET_INFRA
(WICED & nuttx)或BRCMF_C_SET_INFRA
(Linux); - 设置WPA授权方式的版本号,使用iovar
"bsscfg:sup_wpa"
; - 设置授权方式,如WPA、WPA2。使用ioctl为
WLC_SET_WPA_AUTH
; - 设置加密方式,如WEP、TKIP、CCMP(其核心算法是AES)等。使用ioctl为
WLC_SET_WSEC
和WLC_SET_AUTH
; - 设置WiFi密码,使用ioctl为
WLC_SET_WSEC_PMK
,参数就是密码字符串; - 设置WiFi名,使用ioctl为
WLC_SET_SSID
(WICED & nuttx)或BRCMF_C_SET_SSID
(Linux),参数是WiFi SSID字符串。 - 随后就等待路由器的授权信息。。。
连接上路由器,并不代表获得了IP地址。IP可以静态设置,也可以动态分配(DHCP)。但这些已经不属于MAC层的范畴了,正常的操作系统都会配备相应的应用程序来完成这些功能。
错误
有时候BCM芯片会发一个很大的东西回来,是其内核寄存器的dump。Linux内核中对此命名为struct brcmf_trap_info,并输出log信息。WICED和nuttx都假装没有这种问题。dump之后WiFi芯片将不会响应任何命令。。。
这一般说明固件不配套,可能是发送了不合法的命令,也可能发了几条命令之后片子发现自己不能执行下去了,然后就dump,卡死。这时就要多试几个固件了。。。
板子试验
只讨论WICED和nuttx的板子。
- WICED开发板选用
BCM94343WWCD1-EVB
,主控是STM32F411VEH6,WiFi芯片是BCM4343W。板子带有一片SPI flash,BCM的固件就存放在那里。 - Redbear提供了WICED 6.1的补丁,直接复制到SDK目录下面就行了。
- Photon和Redbear都可以跑nuttx,Redbear需要稍作移植,主要是填充BCM43438的固件,毕竟43362和43438的驱动相当通用。
跑WICED
安装了WICED SDK之后,在43xxx_Wi-Fi
目录下面make。这份SDK提供了make程序从而不必使用系统的make程序。可以直接make它自带的例程,例程都放在apps目录的子目录下面:
apps/
├── demo
│ ├── aliyun_mns
│ ├── apollo
│ ├── appliance
│ └── ...
├── snip
│ ├── scan
│ ├── https_server
│ ├── tcp_client
│ └── ...
├── test
│ └── ...
├── waf
│ ├── bootloader
│ └── ...
└── wwd
├── ping
├── scan
└── ...
make后接的target这样设置:<例程目录>.<例程名称>-<板子名称>
。比方说要跑apps/snip/scan里面的例程:
- ./make snip.scan-BCM94343WWCD1 # 对于BCM94343W开发板
- ./make snip.scan-RB_DUO # 打了补丁之后可用的Redbear开发板
值得一试的例程除了scan,还有tcp_client,后者需要在主机上跑一个Python程序,板子连接主机之后不断发送hello数据。
跑nuttx
目前nuttx有photon开发板的现成配置。nuttx的编译方式是,内核和apps目录分开,在内核目录下面用tools/configure.sh
来配置板子。比方说photon:
- ./tools/configure.sh photon/wlan
然后更改.config文件中的CONFIG_NSH_WAPI_SSID
为WiFi名,CONFIG_NSH_WAPI_PASSPHRASE
为WiFi密码。其他按照默认配置,编译出来的nuttx.bin可用直接用photon的bootloader进行烧写、启动。
- sudo dfu-util -d 2b04:d006 -a 0 -s 0x08020000 -D nuttx.bin
复位之后,板子会自动连上WiFi。在路由器中找板子的ip地址,然后通过telnet来访问板子的nsh。
$ telnet 192.168.2.102
Trying 192.168.2.102...
Connected to 192.168.2.102.
Escape character is '^]'
NuttShell (NSH) NuttX-7.24
nsh>
对于Redbear,则可以依葫芦画瓢地增加板级配置文件,也能达到这个效果。