高通WCNSS驱动简析

在加载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部分在另外文章中介绍。

  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值