OpenHarmony的USB框架

编程入门 行业动态 更新时间:2024-10-27 02:21:01

OpenHarmony的USB<a href=https://www.elefans.com/category/jswz/34/1770644.html style=框架"/>

OpenHarmony的USB框架

作者:鸿湖万联 许文龙

1、 前言

写这篇文档主要目的是想弄清楚OpenHarmony的usb设备、驱动以及设备结点的加载过程,弄清楚usb分别在内核驱动、HDF、ueventd都做了什么,是什么关系。顺便,学习一下HDF的设计思路,它是如何与内核态的驱动交互的。同时也能窥探一下内核的驱动框架。

最后,就是要基于自己的理解,解决usb设备结点为什么有的没有被创建。比如插入打印机,看不到结点。是系统bug还是有意为之。

?? ?? kernel usb HDF Ueventd

2、通讯机制

2.1 字符设备驱动

内核提供了三种设备文件,字符设备、块设备、网络设备。

属性开头c标志的都是字符设备。

字符设备驱动机制

在用户态,往字符设备文件写数据,就相当于向设备写数据。

  • usb 设备驱动初始化时,会注册设备驱动。然后生成主设备号,绑定操作函数。并没有生成设备结点。

    const struct file_operations usbdev_file_operations = {.owner =	  THIS_MODULE,.llseek =	  no_seek_end_llseek, ---->  对应Linux 标准接口seek.read =		  usbdev_read,---->  对应Linux 标准接口read.poll =		  usbdev_poll,---->  对应Linux 标准接口poll.unlocked_ioctl = usbdev_ioctl,  ---->  对应Linux 标准接口ioctlpat_ioctl =   compat_ptr_ioctl,.mmap =           usbdev_mmap,  ---->  对应Linux 标准接口mmap.open =		  usbdev_open,   ---->  对应Linux 标准接口open.release =	  usbdev_release,
    };
    
  • 设备插入时,生成次设备号,并探测到对应的驱动,关联具体的操作函数。状态上报给用户空间,用户守护进程(Openharmony 里是Ueventd)创建设备结点。

  • 在用户空间操作设备,就相当于调用了驱动的操作函数。比如,打开设备文件后,用户调用ioctl, 就相当于调用驱动的ioctl函数。

设备号

字符设备的路径是可以改的,主要的是主设备号、次设备号。主设备号对应bus上的设备驱动,主设备号是固定的,比如usb的主设备号是180,任何设备上都是固定的。而是次设备号是动态申请的。

下面是主设备号:

# cat /proc/devices
Character devices:1 mem4 tty4 ttyS5 /dev/tty5 /dev/console10 misc13 input29 fb81 video4linux89 i2c
153 spi
180 usb
188 ttyUSB
189 usb_device

usb 可能比较特别,他跟其他驱动不同,他有两个主设备号。180和189, 这主要是因为usb 还包括鼠标、键盘这样的设备以及一些复杂的数据传输。

  • usb_device 189 是usb设备文件系统(usbfs)

    # ls -al /dev/bus/usb/003
    crw-rw---- 1 root root 189, 256 2017-08-05 09:00 001
    crw-rw-r-- 1 root root 189, 259 2017-08-08 07:01 004
    
  • usb 180 是给用户开发使用的。可以直接读写对应的设备结点即可读写设备。

    # ls /dev/usb/ -al
    crw-------  1 root root 180,   0 2017-08-09 05:43 lp0
    

    ​ 用180和189应该都是可以通讯的,只是使用189会复杂些,需要处理许多细节,比如发送数据要自己构造urb的结构体,而180则简单很多,直接按文件读写就可以了。

    ​ HDF DDK,libusb等都是使用189, 因为他们需要实现许多复杂的需求,而普通应用程序只需要收发数据,直接使用180结点就行了。这也解释了为什么用libusb只需要知道bus 和dev 编号就能通讯。

实现原理

字符设备也是有个驱动的,这里没有详细了解。只是弄清楚机制和用法。因为,在Openharmony里很多代码用到这个东西,比如token_id也是这样的。

# ls -al /dev/access_token_id
crw-rw-rw- 1 access_token access_token 10,  56 2017-08-05 09:00 /dev/access_token_id

这就是设备结点的一个基本的机制。

以usb_device 为例:

int usb_devio_init()
{// 1、 注册设备号, 这里是189, 即usb_device. 三个参数分别是主设备号(189), 设备可分配最大个数,名称retval = register_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX,"usb_device"); // 在/proc/devices中添加: 189  usb_deviceif (retval) {printk(KERN_ERR "Unable to register minors for usb_device\n");goto out;}// 2、 注册设备号, 关联对应的操作函数cdev_init(&usb_device_cdev, &usbdev_file_operations);// 3、 添加到usb 的bus上。retval = cdev_add(&usb_device_cdev, USB_DEVICE_DEV, USB_DEVICE_MAX);if (retval) {printk(KERN_ERR "Unable to get usb_device major %d\n",USB_DEVICE_MAJOR);goto error_cdev;}usb_register_notify(&usbdev_nb);
}const struct file_operations usbdev_file_operations = {.owner =	  THIS_MODULE,.llseek =	  no_seek_end_llseek, ---->  对应Linux 标准接口seek.read =		  usbdev_read,---->  对应Linux 标准接口read.poll =		  usbdev_poll,---->  对应Linux 标准接口poll.unlocked_ioctl = usbdev_ioctl,  ---->  对应Linux 标准接口ioctlpat_ioctl =   compat_ptr_ioctl,.mmap =           usbdev_mmap,  ---->  对应Linux 标准接口mmap.open =		  usbdev_open,   ---->  对应Linux 标准接口open.release =	  usbdev_release,
};static long usbdev_do_ioctl(struct file *file, unsigned int cmd,void __user *p)
{struct usb_dev_state *ps = file->private_data;struct inode *inode = file_inode(file);struct usb_device *dev = ps->dev;int ret = -ENOTTY;switch (cmd) {case USBDEVFS_BULK:snoop(&dev->dev, "%s: BULK\n", __func__);ret = proc_bulk(ps, p);if (ret >= 0)inode->i_mtime = current_time(inode);break;case USBDEVFS_GET_CAPABILITIES:ret = proc_get_capabilities(ps, p);break;。。。 省略}

次设备号是在设备插拔时自动生成的,是动态的。主设备号是静态申请的,固定的。

在HDF 的用户态是这样调用的:

	// 对应的设备结点/dev/bus/usb/ 下的设备结点,主设备号是189char path[64];sprintf_s(path, sizeof(path), USB_DEV_FS_PATH "/%03u/%03u", dev->busNum, dev->devAddr);fd = open(path,0666);//  获取设备接口描述符ret = ioctl(fd, USBDEVFS_GET_CAPABILITIES, &devHandle->caps);// 传输数据ret = ioctl(fd, USBDEVFS_SUBMITURB, urb);

这就是字符设备的一个机制。

2.2 HdfSBuf

HdfSBuf 类似组件使用的Binder机制,使用比较相似,只是底层机制不同,Binder使用的是共享内存。

HdfSBuf 底层也是使用字符设备实现的。底层细节没有深入。

crw-rw----  1 root root 234,   0 2017-08-05 09:00 dev_mgr
crw-rw----  1 root root 234,   1 2017-08-05 09:00 devsvc_mgr
crw-rw----  1 root root 234,   7 2017-08-05 09:00 hdf_audio_capture
crw-rw----  1 root root 234,   8 2017-08-05 09:00 hdf_audio_render
crw-rw----  1 root root 234,  11 2017-08-05 09:00 hdf_usb_pnp_notify_service

HdfSBuf 不仅可以在内核空间和用户空间通讯,也可以转变成Binder的对象,使用c++开发。

int32_t SbufToParcel(struct HdfSBuf *sbuf, OHOS::MessageParcel **parcel);

下面看一个例子:

服务端:

static int32_t UsbPnpNotifyDispatch(struct HdfDeviceIoClient *client, int32_t cmd, struct HdfSBuf *data, struct HdfSBuf *reply)
{int32_t ret = HDF_SUCCESS;struct UsbPnpAddRemoveInfo *usbPnpInfo = NULL;uint32_t infoSize;HdfSbufReadBuffer(data, (const void **)(&usbPnpInfo), &infoSize);// 根据cmd做相应处理,代码省略OsalMutexUnlock(&g_usbSendEventLock);if (!HdfSbufWriteInt32(reply, INT32_MAX)) {HDF_LOGE("%s: reply int32 fail", __func__);}return ret;
}static int32_t UsbPnpNotifyBind(struct HdfDeviceObject *device)
{static struct IDeviceIoService pnpNotifyService = {.Dispatch = UsbPnpNotifyDispatch,};device->service = &pnpNotifyService;return HDF_SUCCESS;
}struct HdfDriverEntry g_usbPnpNotifyEntry = {.moduleVersion = 1,.Bind = UsbPnpNotifyBind,.Init = UsbPnpNotifyInit,.Release = UsbPnpNotifyRelease,.moduleName = "HDF_USB_PNP_NOTIFY",
};HDF_INIT(g_usbPnpNotifyEntry);

客户端:

struct HdfSBuf *data = HdfObtainDefaultSize();
struct HdfSBuf *reply = HdfObainDefaultSize();
struct UsbPnpAddRemoveInfo usbPnpInfo;
uint32_t ret = 0;HdfSbufWriteBuffer(data, &usbPnpInfo, sizeof(usbPnpInfo));
// 阻塞的
server->dispatcher->Dispatch(&server->object, cmd, data, reply);HdfSbufReadInt32(reply, &ret);HdfSBufRecycle(data);
HdfSBufRecycle(reply);
return ret;

2.3 Uevent

uevent 主要是用于usb设备插拔监控的。

socket的NETLINK协议族有多种协议,包括网络通讯、进程间通信、路由查询,ueventd等。ueventd是内核空间向用户空间主动上报事件的一种协议。内核驱动只是作为socket客户端,广播消息。谁接收由用户进程决定。

在OpenHarmony中,用户进程的热插拔处理是有一个Ueventd的进程。它是基于init的fd代持技术实现条件触发、按需启动。

  • 条件就是收到socket的NETLINK_KOBJECT_UEVENT的消息,ueventd进程就会被唤醒

  • 按需启动就是出现热插拔事件。

通过调试也能看到这一点:

插拔设备之前,一直是停止运行的,
# param dump | grep ueventdparam: startup.service.ctl.ueventd=stopped插入设备之后,很快就被唤醒了。
# param dump | grep ueventdparam: startup.service.ctl.ueventd=running
#

Ueventd使用的是NETLINK_KOBJECT_UEVENT协议,获取驱动发来的uevent消息。

	struct sockaddr_nl addr;addr.nl_family = AF_NETLINK;addr.nl_pid = getpid();addr.nl_groups = 0xffffffff;int sockfd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT);setsockopt(sockfd, SOL_SOCKET, SO_RCVBUFFORCE, &buffSize, sizeof(buffSize));setsockopt(sockfd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {}

3、 USB 通讯过程

3.1 usb 内核驱动

内核usb主要就是加载驱动。并将状态通过uevent报文上报给用户空间。

大致了解一下。

1、注册debugfs, 注册电源等高级配置。 /sys/kernel/debug/usb/ 可以看各种调试信息

2、注册usb总线,并加载到系统驱动总线. 会创建 /sys/bus/usb目录,关联相关的函数操作,包括uevent,设备match

3、分配主设备号,180。 /sys/bus/usb/drviers/usb, 关联ioctl,read,write等函数

4、分配usbfs设备号,189, /sys/bus/usb/drivers/usbfs, 关联ioctl,open, read等函数

5、注册hub

6、注册一般设备驱动(usb分为设备驱动和接口驱动,接口驱动可以理解为逻辑设备,一个设备可以有多个逻辑设备)

7、接口驱动是单独注册的,在/sys/bus/usb/drivers 这里可以看到所有已经加载的驱动。

# ls /sys/bus/usb/drivers/usblp/ -al
total 0
drwxr-xr-x  2 root root    0 2017-08-05 09:26 .
drwxr-xr-x 33 root root    0 2017-08-05 09:00 ..
lrwxrwxrwx  1 root root    0 2017-08-06 01:11 3-1:1.0 -> ../../../../devices/platform/fd840000.usb/usb3/3-1/3-1:1.0
--w-------  1 root root 4096 2017-08-06 01:11 bind
lrwxrwxrwx  1 root root    0 2017-08-06 01:11 module -> ../../../../module/usblp
-rw-r--r--  1 root root 4096 2017-08-06 01:11 new_id
-rw-r--r--  1 root root 4096 2017-08-06 01:11 remove_id
--w-------  1 root root 4096 2017-08-06 01:11 uevent
--w-------  1 root root 4096 2017-08-06 01:11 unbind

驱动框架:

这里的设备是逻辑设备,对于usb来说就是接口驱动。

状态通知:

插入 通知 HdfSBuf 接口 netlink uevent 设备 usb bus HDF内核驱动服务 HDF用户态驱动服务 HDI APP Ueventd 创建设备结点

数据通讯:

read/write/ioctl 主设备号 次设备号 APP 设备结点 bus usb 设备

3.2 Ueventd进程

在Openharmony里,用户空间使用的是Ueventd作为守护进程。前面已经说过,它是条件触发、按需启动。这里做一个简单梳理:

1、 解析设备结点的权限配置文件

/etc/ueventd.config

/vendor/etc/ueventd.config

// 摘取部分
/dev/block/sdd19 0660 6666 6666
/dev/watchdog 0660  watchdog watchdog
/dev/hdf_input_event* 0660 3029 3029
/dev/HDF* 0666 0 0
/dev/ttyS* 0666 0 0
/dev/ttyACM* 0666 0 0
/dev/ttyUSB* 0666 0 0

2、基于init的fd代持技术,获取socket句柄。

如果init进程socket不存在,就自己按默认方式创建socket服务端。

使用的是NETLINK_KOBJECT_UEVENT

3、解析ueventd报文

就是一般的文本格式。

# cat /sys/class/usbmisc/lp0/uevent
MAJOR=180
MINOR=0
DEVNAME=usb/lp0

4、创建设备结点

两种方式:

  • 静态创建设备结点

    扫描这些目录下的ueventd文件,并创建设备结点。

    Trigger("/sys/block", sockFd, devices, num);
    Trigger("/sys/class", sockFd, devices, num);
    Trigger("/sys/devices", sockFd, devices, num);
    

    就是系统启动时,在ueventd里已经配置好的。一般可能是上次系统关闭之前设备并未拔出,系统重启后就生成的。

    /sys/class/usbmisc/lp0/uevent
    # cat /sys/class/usbmisc/lp0/uevent
    MAJOR=180
    MINOR=0
    DEVNAME=usb/lp0
    
  • 动态创建设备结点

    是收到netlink uevent消息时触发创建的。

下面是usb的设备结点路径处理的代码。

void HandleOtherDeviceEvent(const struct Uevent *uevent)
{char deviceNode[DEVICE_FILE_SIZE] = {};char sysPath[SYSPATH_SIZE] = {};const char *devName = GetDeviceName(sysPath, uevent->deviceName);const char *devPath = GetDeviceBasePath(uevent->subsystem);if (STRINGEQUAL(uevent->subsystem, "usb")) {// 189 的设备结点, /dev/bus/usb/003/001if (uevent->deviceName != NULL) {if (snprintf_s(deviceNode, DEVICE_FILE_SIZE, DEVICE_FILE_SIZE - 1, "/dev/%s", uevent->deviceName) == -1) {INIT_LOGE("Make device file for device [%d : %d]", uevent->major, uevent->minor);return;}} } else if (STARTSWITH(uevent->subsystem, "usb")) {//180的设备结点,走这条线路// Other usb devies, do not handle it.return;} else {}HandleDeviceNode(uevent, deviceNode, false);// 最终调用mknode(path, mode, 主设备号<<8|次设备号);  //mode是权限,默认0666
}

5、设备结点参数化

在OpenHarmony release 3.2版本将设备节点参数化。

startup.uevent.xxx = "added"/"remove"

这样,就可以通过参数服务提供的接口,将ueventd的热插拔事件通知到任何用户进程。

不过这部分代码,好像有问题。

3.3 HDF usb

hdf 服务分为内核态(khdf)和用户态(uhdf)服务。 这个可以根据hcs 的配置文件所在的目录可以知道。

vendor/isoftstone/tecsun/hdf_config
├── khdf
│   ├── audio
│   ├── device_info
│   ├── hdf.hcs
│   ├── input
│   └── wifi
└── uhdf├── camera├── device_info.hcs├── hdf.hcs├── media_codec├── usb_ecm_acm.hcs└── usb_pnp_device.hcs

在编译时,hdf目录的framework,khdf 部分的代码是链接到内核一起编译的。

wlxuz@swanlink02:~/code/SwanlinkOS$ ls -al out/kernel/src_tmp/linux-5.10/drivers/hdf/
total 24
drwxr-xr-x   4 wlxuz deve 4096 May  6 17:43 .
drwxr-xr-x 146 wlxuz deve 4096 May  6 17:43 ..
drwxr-xr-x   2 wlxuz deve 4096 May  6 17:43 evdev
lrwxrwxrwx   1 wlxuz deve   58 May  6 17:43 framework -> //SwanlinkOS/drivers/hdf_core/framework
lrwxrwxrwx   1 wlxuz deve   67 May  6 17:43 khdf -> //SwanlinkOS/drivers/hdf_core/adapter/khdf/linux
-rw-r--r--   1 wlxuz deve   74 May  6 17:43 Makefile
drwxr-xr-x   3 wlxuz deve 4096 May  6 17:43 wifi

hcs 配置文件里, 每个host是一个独立的进程。

UsbPnpNotify服务是创建在内核态的服务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6cXuXmhm-1683769479080)(figures/4b535758-af46-11ec-aa7f-dac502259ad0.png)]

它通过调用下面的内核接口获取内核空间的usb驱动的状态:

usb_register_notify(&g_usbPnpNotifyNb);  // 将监听接口注册到usb的通知链。
usb_for_each_dev((void *)client, funcs);  // 遍历设备列表

通知链是usb总线的一个通知链表,当事件触发时,所有注册到这个链表的监听函数,会被依次通知。HDF 利用这种机制将内核的状态变化通知到hdf。

usb_register_notify HdfSBuf 接口调用 kernel usb(内核空间) UsbPnpNotify(内核空间) Usb DDK(用户空间) usb驱动开发

这样就实现了本来只能在内核态开发的usb驱动迁移到了用户态。

4、 问题解决

4.1 设备结点未创建的问题

主要问题是插入usb设备,设备结点没有被创建。主要分析ueventd的log基本就知道问题所在。

查看log 可以用下面命令都可以看log

# cat /proc/kmsg
# hilog -t kmsg  
# dmesg | grep usb

先用 dmesg|grep usb 查看插拔时产生的log,

[252064.106002] [I/USB_PNP_NOTIFY] UsbPnpNotifyHdfSendEvent:387 report one device information, 4 usbDevAddr=18446743524337473536, devNum=3, busNum=3, infoTable=1-0x483-0x7540
!\x0d
[252066.381377] usb 3-1: new full-speed USB device number 4 using ohci-platform
[252066.614071] usb 3-1: New USB device found, idVendor=0483, idProduct=7540, bcdDevice= 2.00
[252066.614185] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[252066.614227] usb 3-1: Product: ICOD_Thermal_Printer
[252066.614259] usb 3-1: Manufacturer: ICOD
[252066.614291] usb 3-1: SerialNumber: 000000000002af5360
[252066.630719] usblp 3-1:1.0: usblp0: USB Bidirectional printer dev 4 if 0 alt 0 proto 2 vid 0x0483 pid 0x7540
[252066.634401] [I/USB_PNP_NOTIFY] UsbPnpNotifyHdfSendEvent:387 report one device information, 3 usbDevAddr=18446743524337473536, devNum=4, busNum=3, infoTable=1-0x483-0x7540
!\x0d

可以看到,UsbPnpNotify 服务首先获取到了新插入的设备,并将信息上报.

下面是ueventd打印的log,usb打印机的subsystem 为usbmisc, 路径为lp0.

[2017-8-8 7:1:8][pid=5353][<6>][INFO] [ueventd_device_handler.c:464)] subsystem = usb, syspath = /devices/platform/fd840000.usb/usb3/3-1
[2017-8-8 7:1:8][pid=5353][<6>][INFO] [ueventd_device_handler.c:483)] HandleOtherDeviceEvent, devPath = /dev, devName = 004
[2017-8-8 7:1:8][pid=5353][<6>][INFO] [ueventd_device_handler.c:464)] subsystem = usb, syspath = /devices/platform/fd840000.usb/usb3/3-1/3-1:1.0
[2017-8-8 7:1:8][pid=5353][<6>][INFO] [ueventd_device_handler.c:464)] subsystem = class, syspath = /class/usbmisc
[2017-8-8 7:1:8][pid=5353][<6>][INFO] [ueventd_device_handler.c:464)] subsystem = usbmisc, syspath = /devices/platform/fd840000.usb/usb3/3-1/3-1:1.0/usbmisc/lp0
[2017-8-8 7:1:8][pid=5353][<6>][INFO] [ueventd_device_handler.c:483)] HandleOtherDeviceEvent, devPath = /dev, devName = lp0

所以,只需要在HandleOtherDeviceEvent函数拼接路径即可。

void HandleOtherDeviceEvent(const struct Uevent *uevent)
{// 其他代码省略 。。。if (STRINGEQUAL(uevent->subsystem, "usb")) {。。。 省略, 这里是/dev/bus/usb/ 的设备结点} else if (STARTSWITH(uevent->subsystem, "usb")) {// Other usb devies, do not handle it.// 添加的代码if (!STRINGEQUAL(uevent->subsystem, "usbmisc")) {return;}if (devPath == NULL) {return;}//  printer usb devices ,need create device node: /dev/usb/lp%dif (snprintf_s(deviceNode, DEVICE_FILE_SIZE, DEVICE_FILE_SIZE - 1, "%s/usb/%s", devPath, devName) == -1) {INIT_LOGE("Make device file for device [%d : %d]", uevent->major, uevent->minor);return;}} 
}

通过运行, 设备结点确实创建了,而且可用。

# ls -al /dev/usb/
total 0
drwxr-xr-x  2 root root       60 2017-08-10 06:30 .
drwxr-xr-x 21 root root     4340 2017-08-05 09:00 ..
crw-------  1 root root 180,   0 2017-08-10 06:30 lp0

发现权限太高了,配置权限:

在/etc/ueventd.config 添加一行:

/dev/usb/*  0666 1005 1005

1005是samgr的uid,gid

重启系统,权限就变了。

# ls -al /dev/usb/
total 0
drwxr-xr-x  2 root  root        60 2017-08-09 14:09 .
drwxr-xr-x 21 root  root      4340 2017-08-09 14:09 ..
crwxrwxrwx  1 samgr samgr 180,   0 2017-08-09 14:09 lp0

执行打印命令,验证确实可用。

echo "test print" > /dev/usb/lp0

4. 总结

这里的设备是逻辑设备,对于usb来说就是接口驱动。

状态通知:

插入 通知 HdfSBuf 接口 netlink uevent 读写设备结点 设备 usb bus HDF内核驱动服务 HDF用户态驱动服务 HDI APP Ueventd 创建设备结点

数据通讯:

read/write/ioctl 主设备号 次设备号 APP 设备结点 bus usb 设备

更多推荐

OpenHarmony的USB框架

本文发布于:2024-03-08 12:48:19,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1720916.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:框架   OpenHarmony   USB

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!