在加载WIFI驱动之前,首先需要把WCNSS(Wireless connectivity subsystem)运行起来,这就需要wcn驱动的帮助,通过wcn驱动来启动wncss,让wcn chip和和ccpu工作起来。wcn驱动程序在kernel目录/kernel/driver/net/wireless/wcnss/wcnss_wlan.c。
驱动入口:
static int __init wcnss_wlan_init(void)
{
platform_driver_register(&wcnss_wlan_driver);
platform_driver_register(&wcnss_wlan_ctrl_driver);
platform_driver_register(&wcnss_ctrl_driver);
register_pm_notifier(&wcnss_pm_notifier);
return 0;
}
这里注册了三个platform驱动。分别是wcnss_wlan_driver,wcnss_wlan_ctrl_driver,wcnss_ctrl_driver。并注册接收待机事件,控制待机下的行为。
现在分别看三个驱动的注册。
1.wcnss_wlan_driver驱动
第一个wcnss_wlan_driver:
#ifdef CONFIG_WCNSS_CORE_PRONTO
static struct of_device_id msm_wcnss_pronto_match[] = {
{.compatible = "qcom,wcnss_wlan"},
{}
};
#endif
static struct platform_driver wcnss_wlan_driver = {
.driver = {
.name = DEVICE,
.owner = THIS_MODULE,
.pm = &wcnss_wlan_pm_ops,
#ifdef CONFIG_WCNSS_CORE_PRONTO
.of_match_table = msm_wcnss_pronto_match,
#endif
},
.probe = wcnss_wlan_probe,
.remove = wcnss_wlan_remove,
};
对应的dts在/kernel/arch/arm/boot/dts/qcom/msm8909.dtsi
wcnss: qcom,wcnss-wlan@a000000 {
compatible = "qcom,wcnss_wlan";
reg = <0x0a000000 0x280000>,
...
}
dts中定义了wcn芯片用到的电源,gpio,寄存器地址,收发缓冲buf大小等信息。我们需要看probe函数内容。
static int
wcnss_wlan_probe(struct platform_device *pdev)
{
int ret = 0;
// 分配penv, penv结构是wcn所有资源的集合
penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
// 将pdev保存到penv->pdev,后面misc会用到
penv->pdev = pdev;
penv->user_cal_data =
devm_kzalloc(&pdev->dev, MAX_CALIBRATED_DATA_SIZE, GFP_KERNEL);
/* register sysfs entries */
/* 创建serial_number, wcnss_version,wcnss_mac_addr文件 */
ret = wcnss_create_sysfs(&pdev->dev);
/* register wcnss event notification */
// 将wcn注册到subsys中
penv->wcnss_notif_hdle = subsys_notif_register_notifier("wcnss", &wnb);
// 初始化penv中的成员
mutex_init(&penv->dev_lock);
mutex_init(&penv->ctrl_lock);
mutex_init(&penv->vbat_monitor_mutex);
mutex_init(&penv->pm_qos_mutex);
init_waitqueue_head(&penv->read_wait);
penv->user_cal_rcvd = 0;
penv->user_cal_read = 0;
penv->user_cal_exp_size = 0;
penv->user_cal_available = false;
// 注册两个misc设备
misc_register(&wcnss_usr_ctrl);
return misc_register(&wcnss_misc);
}
因此在wcnss_wlan_driver这个platform中,主要测试后了penv机构然后注册了连个misc设备,分别是wcnss_usr_ctrl,wcnss_misc。
我们看两个misc设备的注册过程。首先看wcnss_usr_ctrl的注册:
static const struct file_operations wcnss_ctrl_fops = {
.owner = THIS_MODULE,
.open = wcnss_ctrl_open,
.write = wcnss_ctrl_write,
};
static struct miscdevice wcnss_usr_ctrl = {
.minor = MISC_DYNAMIC_MINOR,
.name = CTRL_DEVICE,
.fops = &wcnss_ctrl_fops,
};
我们在设备的/dev/wcnss_ctrl中可以找到该设备。给设备fops只给出了两个操作方法,一是open,一是write。open只是标记设备已经打开,设置
penv->ctrl_device_opened=1
write函数对应wcnss_ctrl_write,用于修改三个值:
penv->serial_number
has_calibrated_data
penv->wlan_nv_macAddr
其中serial_number和wlan_nv_macAddr在sys文件中也可以查看和修改。但这些修改都是零时的修改,保存到penv变量成员内存中。
再看第二个misc设备。改设备名为wcnss_wlan,在设备/dev/wcnss_wlan中可以找到。
static const struct file_operations wcnss_node_fops = {
.owner = THIS_MODULE,
.open = wcnss_node_open,
.read = wcnss_wlan_read,
.write = wcnss_wlan_write,
.release = wcnss_node_release,
};
static struct miscdevice wcnss_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE,
.fops = &wcnss_node_fops,
};
该设备/dev/wcnss_ctrl用来启动wcn芯片。先看下open函数:
wcnss_node_open
pdev = penv->pdev;
wcnss_trigger_config(pdev);
wcnss_trigger_config函数将从wcnss_wlan_driver这个platform设备树节点中获取设备信息,并保存到penv全局结构中。
static int
wcnss_trigger_config(struct platform_device *pdev)
{
int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
"qcom,has-pronto-hw");
is_pronto_vadc = of_property_read_bool(pdev->dev.of_node,
"qcom,is-pronto-vadc");
...
// 解析设置的iris电压到vlevel数组中,用于上电
index = 0;
ret = wcnss_dt_parse_vreg_level(&pdev->dev, index,
"qcom,iris-vddxo-current",
"qcom,iris-vddxo-voltage-level",
penv->wlan_config.iris_vlevel);
index++;
ret = wcnss_dt_parse_vreg_level(&pdev->dev, index,
"qcom,iris-vddpa-current",
"qcom,iris-vddpa-voltage-level",
penv->wlan_config.iris_vlevel);
index++;
ret = wcnss_dt_parse_vreg_level(&pdev->dev, index,
"qcom,iris-vdddig-current",
"qcom,iris-vdddig-voltage-level",
penv->wlan_config.iris_vlevel);
// 已经启动
penv->triggered = 1;
/* allocate resources */
/* 获取内存和irq资源 */
penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"wcnss_mmio");
penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,"wcnss_wlantx_irq");
penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,"wcnss_wlanrx_irq");
// 初始化工作
INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
INIT_WORK(&penv->wcnss_pm_config_work, wcnss_send_pm_config);
INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main);
INIT_DELAYED_WORK(&penv->wcnss_pm_qos_del_req, wcnss_pm_qos_enable_pc);
// 驱动WCN子系统
do {
/* trigger initialization of the WCNSS */
penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
if (IS_ERR(penv->pil)) {
dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
ret = PTR_ERR(penv->pil);
wcnss_disable_pc_add_req();
wcnss_pronto_log_debug_regs();
}
} while (pil_retry++ < WCNSS_MAX_PIL_RETRY && IS_ERR(penv->pil));
...
}
在/dev/wcnss_ctrl的open函数中解析wcn设备树到penv全局结构中,并初始rx work, nvbin download work等。
/dev/wcnss_ctrl设备的read, write函数用于操作penv->user_cal_data。
在注册完wcnss_wlan_driver驱动后,在注册wcnss_wlan_ctrl_driver驱动。
2.wcnss_wlan_ctrl_driver
改驱动设备名为WLAN_CTRL,platform driver定位为:
static struct platform_driver wcnss_wlan_ctrl_driver = {
.driver = {
.name = "WLAN_CTRL",
.owner = THIS_MODULE,
},
.probe = wcnss_wlan_ctrl_probe,
.remove = wcnss_wlan_ctrl_remove,
};
这里注册了驱动程序,设备在其他地方注册。具体位置后面在看。该设备在probe函数设置了penv->smd_channel_ready为1,然后打印一句log, 通知smd已经可以使用。
3.wcnss_ctrl_driver
wcnss ctrl驱动,我们关注probe,在probe中建立smd和wcnss的通信连接。
static int
wcnss_ctrl_probe(struct platform_device *pdev)
{
int ret = 0;
if (!penv || !penv->triggered)
return -ENODEV;
ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
&penv->smd_ch, penv, wcnss_smd_notify_event);
if (ret < 0) {
pr_err("wcnss: cannot open the smd command channel %s: %d\n",
WCNSS_CTRL_CHANNEL, ret);
return -ENODEV;
}
smd_disable_read_intr(penv->smd_ch);
return 0;
}
涉及到SMD进程通信内容,后续看这些内容。
驱动的实际运行主要有demon启动。接下来看demon如何和驱动进行交互。
4.wcnss_service
demon进程的实现在/hardware/qcom/wlan/waln/msm8909/wcnss-service/wcnss_service.c中。在init.rc中启动。我们看其main函数。
int main(int argc, char *argv[])
{
int fd_dev, ret_cal;
int nv_mac_addr = FAILED;
int nom = 0;
/* 拷贝/system/etc/wifi/WCNSS_qcom_cfg.ini到/data/misc/wifi/WCNSS_qcom_cfg.ini,并修改后者的创建时间,组id等信息 */
// 如果后者没有或者时间更老,将发起拷贝
setup_wlan_config_file();
#ifdef WCNSS_QMI
/* 从/sys/bus/msm_subsys/devices下的subsys查下是否有modem已经modem数量 */
nom = get_system_info(&mdm_detect_info);
// 通过qmi从NV中获取mac地址
if (SUCCESS == wcnss_init_qmi()) {
rc = wcnss_qmi_get_wlan_address(wlan_nv_mac_addr);
if (rc == SUCCESS) {
nv_mac_addr = SUCCESS;
ALOGE("WLAN MAC Addr:" MAC_ADDRESS_STR,
MAC_ADDR_ARRAY(wlan_nv_mac_addr));
} else
ALOGE("Failed to Get MAC addr from modem");
wcnss_qmi_deinit();
}
#endif
/* 根据soc信息(MTP或者QRD)和subtype_id信息等,拷贝nv.bin, 8909平台此处为拷贝 */
dynamic_nv_replace();
// 将打开/dev/wcnss_ctrl设备,写入序列号和mac地址到内核
setup_wcnss_parameters(&ret_cal, nv_mac_addr);
// 打开/dev/wcnss_wlan
fd_dev = open(WCNSS_DEVICE, O_RDWR);
close(fd_dev);
}
这里主要看和驱动交互的的两个设备。wcnss_ctrl设备是misc设备,wcnss_wlan是misc设备。
操作wcnss_ctrl设备设备是setup_wcnss_parameters函数。往内核写序列号和mac地址。可以把wcnss_service和wcnss_ctrl设备结合看。
wcnss_service.c
rc = property_get("ro.serialno", serial, "");
if (rc) {
serial_num = convert_string_to_hex(serial);
ALOGE("Serial Number is %x", serial_num);
msg[pos++] = WCNSS_USR_SERIAL_NUM >> BYTE_1;
msg[pos++] = WCNSS_USR_SERIAL_NUM >> BYTE_0;
msg[pos++] = serial_num >> BYTE_3;
msg[pos++] = serial_num >> BYTE_2;
msg[pos++] = serial_num >> BYTE_1;
msg[pos++] = serial_num >> BYTE_0;
if (write(fd, msg, pos) < 0) {
ALOGE("Failed to write to %s : %s", WCNSS_CTRL,
strerror(errno));
goto fail;
wcnss_wlan.c
void process_usr_ctrl_cmd(u8 *buf, size_t len)
{
u16 cmd = buf[0] << 8 | buf[1];
switch (cmd) {
case WCNSS_USR_SERIAL_NUM:
if (WCNSS_MIN_SERIAL_LEN > len) {
pr_err("%s: Invalid serial number\n", __func__);
return;
}
penv->serial_number = buf[2] << 24 | buf[3] << 16
| buf[4] << 8 | buf[5];
break;
case WCNSS_USR_HAS_CAL_DATA:
if (1 < buf[2])
pr_err("%s: Invalid data for cal %d\n", __func__,
buf[2]);
has_calibrated_data = buf[2];
break;
case WCNSS_USR_WLAN_MAC_ADDR:
memcpy(&penv->wlan_nv_macAddr, &buf[2],
sizeof(penv->wlan_nv_macAddr));
pr_debug("%s: MAC Addr:" MAC_ADDRESS_STR "\n", __func__,
penv->wlan_nv_macAddr[0], penv->wlan_nv_macAddr[1],
penv->wlan_nv_macAddr[2], penv->wlan_nv_macAddr[3],
penv->wlan_nv_macAddr[4], penv->wlan_nv_macAddr[5]);
break;
default:
pr_err("%s: Invalid command %d\n", __func__, cmd);
break;
}
}
然后就是open /dev/wcnss_wlan, wcnss_wlan是wcnss_wlan_driver注册时创建的。其open函数在前面有分析,主要初始化了penv全局结构,并启动wcn子系统。具体启动过程,我们现在看。
penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
subsystem_get
__subsystem_get(name, NULL);
这里要看子系统的实现了。系统实现请看子系统章节。__subsystem_get我们详细看:
@subsystem_restart.c
void *__subsystem_get(const char *name, const char *fw_name)
{
struct subsys_device *subsys;
struct subsys_device *subsys_d;
subsys = retval = find_subsys(name);
track = subsys_get_track(subsys);
mutex_lock(&track->lock);
if (!subsys->count) {
if (fw_name) {
pr_info("Changing subsys fw_name to %s\n", fw_name);
strlcpy(subsys->desc->fw_name, fw_name,
sizeof(subsys->desc->fw_name));
}
ret = subsys_start(subsys);
if (ret) {
retval = ERR_PTR(ret);
goto err_start;
}
}
subsys->count++;
mutex_unlock(&track->lock);
}
__subsystem_get()依据传入的name(wcnss)找到subsystem的subsys_device,然后调用subsys_start()完成接下里的工作。
static int subsys_start(struct subsys_device *subsys)
{
int ret;
// 通知SUBSYS_BEFORE_POWERUP
notify_each_subsys_device(&subsys, 1, SUBSYS_BEFORE_POWERUP,
NULL);
ret = subsys->desc->powerup(subsys->desc);
enable_all_irqs(subsys);
ret = wait_for_err_ready(subsys);
if (ret) {
/* pil-boot succeeded but we need to shutdown
* the device because error ready timed out.
*/
notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE,
NULL);
subsys->desc->shutdown(subsys->desc, false);
disable_all_irqs(subsys);
return ret;
} else {
subsys_set_state(subsys, SUBSYS_ONLINE);
}
notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_POWERUP,
NULL);
return ret;
}
如果Powerup成功,这里会发出SUBSYS_BEFORE_POWERUP,SUBSYS_AFTER_POWERUP通知。powerup在subsystem中定义的回调方法。
在wcnss_wlan驱动的probe中,注册了通知接收:
/* register wcnss event notification */
penv->wcnss_notif_hdle = subsys_notif_register_notifier("wcnss", &wnb);
因此发送的SUBSYS_BEFORE_POWERUP,SUBSYS_AFTER_POWERUP通知都会在这里被处理。
@wcnss_wlan.c
static struct notifier_block wnb = {
.notifier_call = wcnss_notif_cb,
};
static int wcnss_notif_cb(struct notifier_block *this, unsigned long code,
void *ss_handle)
{
struct platform_device *pdev = wcnss_get_platform_device();
struct wcnss_wlan_config *pwlanconfig = wcnss_get_wlan_config();
struct notif_data *data = (struct notif_data *)ss_handle;
if (code == SUBSYS_PROXY_VOTE) {
if (pdev && pwlanconfig) {
ret = wcnss_wlan_power(&pdev->dev, pwlanconfig,
WCNSS_WLAN_SWITCH_ON, &xo_mode);
wcnss_set_iris_xo_mode(xo_mode);
if (ret)
pr_err("Failed to execute wcnss_wlan_power\n");
}
}
...
}
在pil-te boot的时候,将发出SUBSYS_PROXY_VOTE,wcn收到通知,将对wcnss上电。调用的方法为:
wcnss_wlan_power(&pdev->dev, pwlanconfig,
WCNSS_WLAN_SWITCH_ON, &xo_mode);
OK,WCN相关的驱动内容就到这里。关于pil部分在另外文章中介绍。