一 概述

鸿蒙为了支持多个内核,提出了HDF(HarmonyDiverFoundation),鸿蒙驱动框架。

使用“服务”的概念编写驱动程序:

(1)驱动程序中实现服务;(2)APP要先获得服务,然后调用服务跟驱动函数交互。

liteOS-a中驱动程序也跟linux类似:

linux使用设备树描述硬件信息,驱动程序从设备树中获得这些信息;

liteos-a使用HCS文件描述硬件信息,驱动程序从HCS文件中获得这些信息。

二 驱动程序怎么写?

(1)跟Linux类似:构造注册一个file_operations_vfs结构体

OpenharmonyFor6ull\kernel\liteos_a\kernel\common\virtual_serial.c

STATIC const struct file_operations_vfs g_serialDevOps = {
    SerialOpen,  /* open */
    SerialClose, /* close */
    SerialRead,  /* read */
    SerialWrite,
    NULL,
    SerialIoctl,
    NULL,
#ifndef CONFIG_DISABLE_POLL
    SerialPoll,
#endif
    NULL,
};

INT32 virtual_serial_init(const CHAR *deviceName)
{
    ...
    (VOID)memset_s(&g_serialFilep, sizeof(struct file), 0, sizeof(struct file));
    g_serialFilep.f_oflags = O_RDWR;
    g_serialFilep.f_inode = inode;
    (VOID)register_driver(SERIAL, &g_serialDevOps, DEFFILEMODE, &g_serialFilep);
    ...
}

(2)HCS-相当于设备树,设备树与内核代码的映射关系:

OpenharmonyFor6ull\vendor\nxp\imx6ull\config\device_info

 root {
    device_info {
        //1.
        match_attr = "hdf_manager";
        //2.
        template host {
            hostName = "";
            priority = 100;
            template device{
                template deviceNode {
                    policy = 0;
                    priority = 100;
                    preload = 0;
                    permission = 0664;
                    moduleName = "";
                    serviceName = "";
                    deviceMatchAttr = "";
                }
            }
        }
        //3.
        platform :: host {
            hostName = "platform_host";
            priority = 50;
            //3.1 iic接口设备树描述
            device_i2c :: device {
                device0 :: deviceNode {
                    policy = 1;
                    priority = 50;
                    permission = 0644;
                    moduleName = "HDF_PLATFORM_I2C";    //对应的驱动程序名称
                    serviceName = "HDF_PLATFORM_I2C_0"; //对应本驱动程序对外提供服务的名称,供app程序调用
                    deviceMatchAttr = "nxp_imx6ull_i2c_0";//对应硬件初始化信息,根据这个名字在设备树中查找相应的节点来描述这些信息
                }
            }
            //3.2 触摸屏接口设备树描述
           device_touchscreen :: device {
                device0 :: deviceNode {
                    policy = 1;
                    priority = 100;
                    preload = 0;
                    permission = 0666;
                    moduleName = "HDF_TOUCHSCREEN";
                    serviceName = "HDF_TOUCHSCREEN";
                }
            }

        }
        //4.
        storage :: host {
        }
        media :: host {
        }



    }
}

举个例子:找一下上面设备树中用于调用iic设备的HDF_PLATFORM_I2C驱动程序在内核代码中的位置:

OpenharmonyFor6ull\vendor\nxp\imx6ull\driver\imx6ull-i2c\i2c_imx6ull.c

struct HdfDriverEntry g_i2cDriverEntry = {
    .moduleVersion = 1,
    .Bind = Imx6ullI2cBind,
    .Init = Imx6ullI2cInit,
    .Release = Imx6ullI2cRelease,
    .moduleName = "HDF_PLATFORM_I2C",//这里的名称和设备树中的名称一致,那么就对应起来了
};
HDF_INIT(g_i2cDriverEntry);

继续举例子:找一下上面设备树中用于调用iic设备的硬件描述信息deviceMatchAttr = "nxp_imx6ull_i2c_0"在内核代码中的位置:

OpenharmonyFor6ull\vendor\nxp\imx6ull\config\i2c\i2c_config.hcs

root {
    platfrom {
        i2c_config {
            //描述IIC接口的模板i2c_controller 
            template i2c_controller {
                bus = 0;
                match_attr = "";
                reg_pbase = 0x021A4000;//IIC控制器基地址
                reg_size = 0xd1;//IIC控制器寄存器占用的空间
                irq = 0;
                freq = 400000;//IIC通信频率400KHz
                clk = 50000000;//IIC时钟 50MHz
            }
            //定义一个设备controller_0x021A4000 
            //使用上面定义的i2c_controller模板
            controller_0x021A4000 :: i2c_controller {
                bus = 0;
                match_attr = "nxp_imx6ull_i2c_0";//对应top层HCS
            }

        }
    }
}

(3)分析驱动程序源码-新框架

OpenharmonyFor6ull\vendor\nxp\imx6ull\driver\imx6ull-i2c\i2c_imx6ull.c

设备树和驱动程序的对应的源码一致的时候,会导致init函数被调用,即Imx6ullI2cInit函数被调用

struct HdfDriverEntry g_i2cDriverEntry = {
    .moduleVersion = 1,
    .Bind = Imx6ullI2cBind,
    .Init = Imx6ullI2cInit, //init函数被调用
    .Release = Imx6ullI2cRelease,
    .moduleName = "HDF_PLATFORM_I2C",//HCS和这里名称一致
};
HDF_INIT(g_i2cDriverEntry);

 

init函数源码:

static int32_t Imx6ullI2cInit(struct HdfDeviceObject *device)
{
    (void)device;
    return HDF_SUCCESS;
}

bind函数源码:

LINE94~97:服务结构体,放入dispatch函数

LINE80:驱动程序中把g_hello_val这个值放入reply里面。应用程序就可以把reply取出来。

继续分析该驱动程序对应的app程序:

LINE22 定义服务名称,

LINE26 相当于调用了对应驱动程序的bind函数,传入服务名称就获得了这个服务,该名称在HCS文件中有定义 serviceName = "hello_service"; 该函数里面肯定调用了open函数

LINE45 调用这个服务最终导致驱动程序dispatch函数调用,提供给服务(即驱动程序)的数据为data,服务(即驱动程序)回复的数据为reply;该函数里面肯定调用了read write函数

使用这套新框架隐藏了老框架中需要首先open然后write然后read函数,直接使用BInd函数后dispatch即可,更为简便。

 

(4)分析驱动程序源码-老框架

ps:没有下载韦东山老师的源代码,视频截个图,凑合看看吧

hello_init函数:

g_helloDevOps结构体:就是上文说的file_operations_vfs结构体;

/dev/hello:设备节点,供app程序调用使用

看一下g_helloDevOps结构体的定义吧~跟linux的一毛一样。

继续分析该驱动程序对应的app程序:

注释:若使用本小节介绍的老框架,调用的时候调用设备节点-/dev/hello

三 分析源代码

(1)驱动程序:会发布服务。发布服务就是把该服务传入设备管理器(设备管理器的节点路径为“/dev/dev_mgr”);

(2)APP:从设备管理器(设备管理器的节点路径为“/dev/dev_mgr”)查找并获得服务即调用这个驱动。

如果app提供给设备管理器的服务存在,那么设备管理器会调用下面这个函数,在这个函数中注册驱动程序

OpenharmonyFor6ull\drivers\hdf\lite\adapter\vnode\src\hdf_vnode_adapter.c->HdfIoServiceAdapterObtain”

 

1.获得服务

应用程序的动作:应用程序调用HdfIoServiceBind函数

struct HdfIoService *service = HdfIoServiceBind("hello_service",0);

分析HdfIoServiceBind函数

OpenharmonyFor6ull\drivers\hdf\frameworks\core\shared\src\hdf_io_service.c

struct HdfIoService *HdfIoServiceBind(const char *serviceName, mode_t permission)
{
    return HdfIoServiceAdapterObtain(serviceName, permission);
}

-->

(1)分析一个“HdfIoServiceAdapterObtain”函数:

OpenharmonyFor6ull\drivers\hdf\lite\adapter\syscall\src\hdf_syscall_adapter.c(给应用程序使用的)

struct HdfIoService *HdfIoServiceAdapterObtain(const char *serviceName, mode_t mode)
{
    struct HdfSyscallAdapter *adapter = NULL;
    struct HdfIoService *ioService = NULL;
    char devNodePath[PATH_MAX] = {0};
    char realPath[PATH_MAX] = {0};
    ...

    if (realpath(devNodePath, realPath) == NULL) {
    //通过服务名称查找并加载驱动,根据上文HCS中的ServiceName定义
    //在这之前 /dev/dev_mgr会自动给hello_service注册一个驱动程序
        HdfLoadDriverByServiceName(serviceName);
    //获得设备节点路径
        realpath(devNodePath, realPath);
    }
    ...
    //realPath = “/dev/hello_service”
    adapter->fd = open(realPath, O_RDWR);
    ioService = &adapter->super;
    static struct HdfIoDispatcher dispatch = {
        .Dispatch = HdfSyscallAdapterDispatch,
    };
    ioService->dispatcher = &dispatch;
    return ioService;
}

--->

OpenharmonyFor6ull\drivers\hdf\lite\adapter\syscall\src\hdf_syscall_adapter.c

#define DEV_NODE_PATH "/dev/"
#define DEV_MGR_NODE "dev_mgr"
static int32_t HdfLoadDriverByServiceName(const char *serviceName)
{
    int32_t ret = HDF_FAILURE;
    struct HdfSBuf *data = NULL;
//通过访问驱动程序,让驱动程序加载服务发布服务
    struct HdfIoService *ioService = HdfIoServiceBind(DEV_MGR_NODE, 0);
  ...
}

---->回到之前继续执行函数

OpenharmonyFor6ull\drivers\hdf\lite\adapter\vnode\src\hdf_vnode_adapter.c

struct HdfIoService *HdfIoServiceAdapterObtain(const char *serviceName, mode_t mode)
{
    // /dev/dev_mgr会给hello_service注册一个驱动程序/dev/hello_service
    HdfLoadDriverByServiceName(serviceName);
    //获得realPath的值为realPath="/dev/hello_service"
    realpath(devNodePath, realPath);
    //realPath="/dev/hello_service"
    adapter->fd = open(realPath, O_RDWR);
}

(2)分析另一个“HdfIoServiceAdapterObtain”函数:

OpenharmonyFor6ull\drivers\hdf\lite\adapter\vnode\src\hdf_vnode_adapter.c//内核调用

struct HdfIoService *HdfIoServiceAdapterObtain(const char *serviceName, mode_t mode)
{
    static const struct file_operations_vfs fileOps = {
        .open = HdfVNodeAdapterOpen,
        .ioctl = HdfVNodeAdapterIoctl,
        .poll = HdfVNodeAdapterPoll,
        .close = HdfVNodeAdapterClose,
    };
    //注册file_operations结构体
    //vNodePath就是传进来的“/dev/hello_service”
    int ret = register_driver(vnodeAdapter->vNodePath, &fileOps, mode, vnodeAdapter);

}

2.获得服务

应用程序的动作:应用程序调用service->dispatcher->Dispatch(&service->object,0,data,reply)函数

//OpenharmonyFor6ull\drivers\hdf\lite\adapter\syscall\src\hdf_syscall_adapter.c
struct HdfIoService *HdfIoServiceAdapterObtain(const char *serviceName, mode_t mode)
{
    ...
    ioService = &adapter->super;
    static struct HdfIoDispatcher dispatch = {
        .Dispatch = HdfSyscallAdapterDispatch,
    };
    ioService->dispatcher = &dispatch;
    ...
}

 

->

从上面的代码可知,最终调用函数HdfSyscallAdapterDispatch

OpenharmonyFor6ull\drivers\hdf\lite\adapter\syscall\src\hdf_syscall_adapter.c

tatic int HdfSyscallAdapterDispatch(struct HdfObject *object, int code, struct HdfSBuf *data, struct HdfSBuf *reply)
{
...
    int ret = ioctl(ioService->fd,  HDF_WRITE_READ, &wrBuf);
...
    return ret;
}

->

从上面的代码可知,最终调用了ioctl,该函数最终进入到下面这个函数

OpenharmonyFor6ull\drivers\hdf\lite\adapter\vnode\src\hdf_vnode_adapter.c

static int HdfVNodeAdapterIoctl(struct file *filep, int cmd, unsigned long arg)
{
    struct HdfVNodeAdapterClient *client = (struct HdfVNodeAdapterClient *)filep->f_priv;
    if (client == NULL) {
        return HDF_DEV_ERR_NO_DEVICE;
    }
    switch (cmd) {
        case HDF_WRITE_READ:
            if (client->serv == NULL) {
                return HDF_DEV_ERR_NO_DEVICE;
            }
            return HdfVNodeAdapterServCall(client, arg);
        ...
        default:
            return HDF_FAILURE;
    }

    return HDF_SUCCESS;
}

->

继续进入函数HdfVNodeAdapterServCall

OpenharmonyFor6ull\drivers\hdf\lite\adapter\vnode\src\hdf_vnode_adapter.c

static int HdfVNodeAdapterServCall(const struct HdfVNodeAdapterClient *client, unsigned long arg)
{
...
    if (LOS_ArchCopyFromUser(&bwr, (void*)bwrUser, sizeof(bwr)) != 0) {
        HDF_LOGE("Copy from user failed");
        return HDF_FAILURE;
    }
...
    int ret = client->adapter->ioService.dispatcher->Dispatch(client->adapter->ioService.target,
        bwr.cmdCode, data, reply);
 ...
    if (LOS_ArchCopyToUser(bwrUser, &bwr, sizeof(struct HdfWriteReadBuf)) != 0) {
        HDF_LOGE("%s: copy bwr fail", __func__);
        ret = HDF_FAILURE;
    }
}

->

其中的Dispatcher函数进一步调用就是驱动程序的dispatch函数

 

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐