首先说一下这个问题为什么会出现以及自己为什么要解决这个问题
本人在开发中发现,单片机和主控MCU通信使用USB,且单片机虚拟了一个ttyACM0-ttyACM4共4个串口设备出来,用于上层应用和单片机串口通讯使用,但由于上层应用在复位单片机时没有做好处理导致打开句柄未被释放,进而导致单片机在重启后虚拟出来的串口号发生变动,这种变动是我们不希望,设备名变动就意味着程序里也要判断,无法正确区分,故需要将USB虚拟设备与ttyACMx逐一对应,且永久唯一,设备名不变化,也就是底层变动尽量对上层无感。
搜索网络上的信息,奈何没有该方面的说明,故记录下来,以备其他人使用。
1.通过kernel中dmesg的内核打印信息,定位到源码在kernel\drivers\usb\class\cdc-acm.c 中。
信息如下:
[ 274.131902] usb 1-1.1: new full-speed USB device number 7 using xhci-hcd
[ 274.253434] cdc_acm 1-1.1:1.0: ttyACM0: USB ACM device
[ 274.256867] cdc_acm 1-1.1:1.2: ttyACM1: USB ACM device
[ 274.259718] cdc_acm 1-1.1:1.4: ttyACM2: USB ACM device
[ 274.263245] cdc_acm 1-1.1:1.6: ttyACM3: USB ACM device
[ 274.266623] cdc_acm 1-1.1:1.8: ttyACM4: USB ACM device
通过查看该代码的处理逻辑定位到设备在acm_probe内接入设备,在函数acm_alloc_minor内对设备的id号进行分配,经测试可以在该函数内实现以上需求功能。
acm_alloc_minor原函数如下:
/*
* Try to find an available minor number and if found, associate it with 'acm'.
*/
static int acm_alloc_minor(struct acm *acm)
{
int minor;
mutex_lock(&acm_minors_lock);
minor = idr_alloc(&acm_minors, acm, 0, ACM_TTY_MINORS, GFP_KERNEL);
mutex_unlock(&acm_minors_lock);
return minor;
}
经查询 idr_alloc的使用方法:通过修改第三位参数来确定分配的起始ID,该起始ID可通过传入的usb名来确定,故需要修改acm_alloc_minor传入USB设备名来进行区分,好在acm_probe函数中的acm_alloc_minor函数下有设备名赋值代码“acm->dev = usb_dev”,提至acm_alloc_minor前即可,本人使用pritk_acm_info来将USB设备名转为ID,为防止该ID被占用,需要提前通过idr_find查询一下是否被占用,如果占用,则使用idr_remove来释放,之后再使用idr_alloc进行分配即可,代码如下:
//从设备名:1-1.3:1.2中获取hub编号3和端口号2,返回转换后的ACM编号
/*设备说明:
1. 1-1.1:1.0,1-1.1:1.2至1-1.1:1.8代表hub1的单片机虚拟的五个ACM设备,ACM编号为0-4
2. 1-1.2:1.0,1-1.2:1.2至1-1.2:1.6代表hub2(主板最右侧的拓展版)的四个ACM设备,ACM编号为13-16
3. 1-1.3:1.0,1-1.3:1.2至1-1.3:1.6代表hub3(主板中间的拓展版)的四个ACM设备,ACM编号为9-12
4. 1-1.4:1.0,1-1.4:1.2至1-1.4:1.6代表hub4(主板最左侧的拓展版)的四个ACM设备,ACM编号为5-8
*/
static int pritk_acm_info(const char *pdevname)
{
int nHubNum=0; //hub编号
int nInterfaceNum=0; //端口编号
int nFindPoint=0; //设备名里已经找到的点号的数量
int nFindColon=0; //设备名里已经找到的冒号的数量
int nACMNum=0; //转换后的ACM编号
int i;
if(pdevname)
{
for (i=0;i<strlen(pdevname);i++)
{
if('.' == pdevname[i])
{
nFindPoint++;
continue;
}
if(':' == pdevname[i])
{
nFindColon++;
continue;
}
if((1 == nFindPoint)&&(0 == nFindColon))
{
//第一个点号和冒号之间的是hub编号,设备名中从1开始,这里从0开始
nHubNum = pdevname[i]-'1';
//目前只支持4个hub拓展出来的USB串口设备
if(nHubNum >= 4)
{
nHubNum=4;
}
}
if((2 == nFindPoint)&&(1 == nFindColon))
{
//第一个冒号且第二个点号之后的是端口编号,设备名中从0开始,这里
//从0开始,注意此编号每个设备占两个编号,故后期需要除以2
nInterfaceNum = pdevname[i]-'0';
//目前每个USB串口设最大虚拟出5个设备,nInterfaceNum最大为8
if(nInterfaceNum >= 9)
{
nInterfaceNum=0;
}
}
}
if(nHubNum > 0)
{
//USB转串口ACM,第一个HUB上有5个ACM,所以编号0以外的需要再加1
nACMNum=1+(4-nHubNum)*4+(nInterfaceNum)/2;
}
else
{
//单片机虚拟串口
nACMNum=(nInterfaceNum)/2;
}
}
printk("usb dev:%s,Hub Num:%d,Interface Num:%d,ACM num:%d\n",pdevname,nHubNum,nInterfaceNum,nACMNum);
if(nACMNum > 18) //目前单片机拓展5个,USB拓展版最多3个,也就是共有5+3*4=17路ACM,
{
nACMNum=0; //超过18个,使用自动分配
printk("usb dev:%s,ACM num more than 5+3*4+1=17+1=18,auto num\n",pdevname);
}
return nACMNum;
}
/*
* Try to find an available minor number and if found, associate it with 'acm'.
*/
static int acm_alloc_minor(struct acm *acm,const char *pname)
{
int minor;
int nstart=0;
struct acm *facm;
nstart = pritk_acm_info(pname);
mutex_lock(&acm_minors_lock);
facm = idr_find(&acm_minors, nstart);
if (facm) {
printk("find %s,%d is exit,now remove it!\n",pname,nstart);
idr_remove(&acm_minors, nstart);
}
minor = idr_alloc(&acm_minors, acm, nstart, ACM_TTY_MINORS, GFP_KERNEL);
if(minor != nstart)
{
printk("warning: alloc idr minor:%d != nstart:%d !\n",minor,nstart);
}
mutex_unlock(&acm_minors_lock);
return minor;
}
.....................
.....................
.....................
static int acm_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_cdc_union_desc *union_header = NULL;
struct usb_cdc_call_mgmt_descriptor *cmgmd = NULL;
.....................
.....................
acm->combined_interfaces = combined_interfaces;
acm->writesize = usb_endpoint_maxp(epwrite) * 20;
acm->control = control_interface;
acm->data = data_interface;
usb_get_intf(acm->control); /* undone in destruct() */
acm->dev = usb_dev;
minor = acm_alloc_minor(acm,dev_name(&intf->dev));
if (minor < 0) {
acm->minor = ACM_MINOR_INVALID;
goto alloc_fail1;
}
usb_get_intf(acm->control); /* undone in destruct() */
acm->dev = usb_dev;
minor = acm_alloc_minor(acm,dev_name(&intf->dev));
if (minor < 0) {
acm->minor = ACM_MINOR_INVALID;
goto alloc_fail1;
}
.....................
.....................
}
即可做到需求的目的,但这样会引来一个新问题,就是“正在使用且占用的ttyACM0”的打开句柄 fd 在被复位后会变得无法对设备进行读写,解决方法也很简单,当无法收发时重新open 更新fd就行了。