From 24fcbe421e55871c7bead087c05e45d7df166d81 Mon Sep 17 00:00:00 2001 From: Liu_Weichao Date: Thu, 29 Apr 2021 17:15:10 +0800 Subject: [PATCH] modify driver framework description --- docs/doc/component/drvmodel.md | 879 +++++++++++++----------- docs/doc/component/imagesrc/spi_bus.png | Bin 0 -> 37524 bytes 2 files changed, 483 insertions(+), 396 deletions(-) create mode 100644 docs/doc/component/imagesrc/spi_bus.png diff --git a/docs/doc/component/drvmodel.md b/docs/doc/component/drvmodel.md index 8609b2d..5912b3d 100644 --- a/docs/doc/component/drvmodel.md +++ b/docs/doc/component/drvmodel.md @@ -1,458 +1,545 @@ # 驱动模型 ## 概述 -多数嵌入式操作系统对驱动的抽象采用以物理外设实体为中心的方式,采用驱动(driver)-设备(device)的模式开发驱动。这种抽象带来的缺陷在面对一些兼容多平台的操作系统开发驱动时尤为突出。 +多数嵌入式操作系统对驱动的抽象采用以物理外设实体为中心的方式,采用驱动(DRIVER)-设备(DEVICE)的模式开发驱动。这种抽象带来的缺陷在面对一些兼容多平台的操作系统开发驱动时尤为突出。 对于实时复杂的操作系统,代码的重用性非常重要,否则的话就会在操作系统内核中存在大量无意义的重复代码。尤其是驱动程序,因为驱动程序占用了操作系统代码量的绝大部分,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么随着操作系统的发展,其文件数量就庞大到无法接受的地步,进而增加了驱动开发难度和周期。 -以I2C接口的ATH10的温湿度传感器驱动为例,每种平台下都有一个主机驱动和设备驱动,主机驱动是必要的,它随着每个平台的IIC控制器不同而不同。但是对于不同平台下的同一个设备ATH10(温湿度传感器),没必要每个平台都写一个设备驱动,因为不管对于哪个架构的 SOC 来说, ATH10温湿度传感器都是一样,通过I2C接口读写数据就行了,只需要公用一个ATH10的驱动程序即可。基于上述思想,XiuOS将驱动进行分隔设计以求简化驱动的开发,采用驱动(driver)-总线(bus)-设备(device)的架构对驱动进行管理和组织。对于同一类设备,每个平台的同种外设控制器都提供一个统一的接口,即主机驱动。每个设备的话也只提供一个驱动程序(设备驱动),每个设备通过统一的 I2C接口驱动来访问,这样就可以大大简化驱动文件。 +针对这种情况,我们了解到对于嵌入式系统外设来说,不外乎几类常用的外设,如I2C外设、SPI外设、字符设备外设、块设备外设等,同一类外设使用的外设接口相同,这就意味着这类外设可以复用同一套DRIVER驱动代码和DEVICE操作代码。而对于上层应用来说,最理想的情况下是这类外设的接口控制器在不同SOC平台上可以复用,即应用程序通过统一的API接口操作外设,可以大大减少系统开发和维护工作量。 -对于ATH10的温湿度传感器,我们只需要提供设备信息即可,比如设备连接到了哪个I2C 接口上,I2C的速度是多少等等。相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息,然后根据获取到的设备信息来初始化设备。这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可,总线负责将驱动和设备信息链接起来。 +因此,XiUOS集成了一套BUS驱动模型,即利用一套BUS总线管理DEVICE设备和DRIVER驱动的框架,以BUS为基类,分发多种BUS子类对应一类外设,覆盖目前常使用的外设接口,如SPI_BUS、I2C_BUS、SERIAL_BUS等。以SPI_BUS总线为例,SPI_BUS总线创建后,首先会注册SPI_DRIVER驱动相关实现,之后系统接入的所有SPI_DEVICE都会注册挂载到SPI_BUS总线上,DRIVER驱动和DEVICE设备通过bus_name绑定上,实现设备和驱动相互对应的配置,这样即可实现用一套BUS数据结构管理其挂载的DEVICE设备和DRIVER驱动的目的。对于上层应用来说,通过获取SPI_BUS数据结构即可知道当前系统中挂载的SPI_DEVICE个数,利用BUS回调函数便可满足配置、数据交互需求,而不用关心具体的硬件细节,且底层新增或删减外设的操作由框架层统一管理,上层应用模块依旧可以复用,这套框架层次化管理界限清晰,驱动和应用解耦、复用功能凸显,应用层开发友好。 +## 目录结构和框图 +* 驱动框架resources/xxx/文件目录结构 -其中 xs_Bus 结构体被设计为可以采用类似面向对象的方法,它集合了所有总线的共有特征,为总线的基类。具体的总线以它为基类进行派生,如 xs_IIC_bus 、xs_USB_bus 、xs_SPI_bus 、xs_CAN_bus等。从而为众多不同性质的总线架构实现统一的管理架构。在应用开发的过程中只需要使用对应外设的总线实例,无需关心传感器的硬件细节,从而实现数据采样功能与底层硬件的解耦。 +| 文件名 | 描述 | +| --- | --- | +| bus.c | bus驱动框架实现,提供给上层应用调用 | +| bus_xxx.c | xxx_bus子类实现,如spi_bus、i2c_bus、serial_bus等 | +| dev_xxx.c | xxx_dev子类实现,如spi_dev、i2c_dev、serial_dev等 | +| drv_xxx.c | xxx_drv子类实现,如spi_drv、i2c_drv、serial_drv等 | -xs_Device 结构体同样采用类似面向对象的方法,它集合了所有设备的共有特征,为设备的基类。平台上接入的具体设备以它为基类进行派生,如 IICHardwareDevice 、USBHardwareDevice 、SPIHardwareDevice 、CANHardwareDevice等。从而为众多不同性质的设备实现统一的管理架构。 +* 驱动框架框图(以SPI_Bus为例) + + ## 关键数据结构 -* struct xs_Bus结构 +* struct Bus结构 ```c -struct xs_Bus { - char Bus_name[XS_NAME_MAX]; /* name of bus */ - enum xs_BusType type; /* type of bus */ - enum xs_BUSSTATE flag; - list_head device; - list_head driver; - int (*match)(const struct xs_DeviceDriver * drv,const struct xs_Device dev); - int (*probe)(void * bus); - int (*remove)(void * bus); - int (*shutdown)(void * bus); - int (*resume)(void * bus); +struct Bus +{ + int8 bus_name[NAME_NUM_MAX]; + enum BusType bus_type; + enum BusState bus_state; - int (bus_register)(void * bus); - int (bus_unregister)(void * bus); - void * private_data; -}; -``` -Bus_name成员是一个可读的名字,用于唯一标识一个xs_Bus结构, -type成员表示该xs_Bus为什么类型的总线,用一个枚举变量表示。 -```c -enum xs_BusType { - XS_IIC_BUS = 0, /* IIC */ - XS_SPI_BUS, /* SPI */ - XS_USB_BUS, /* USB */ - XS_CAN_BUS, /* CAN */ - /* ...... */ - XS_BUS_END, -}; -``` -xs_BusType成员表示不同的总线类型,其具体定义在上文给出。 + int32 (*match)(struct Driver *driver, struct HardwareDev *device); + int bus_lock; -* enum Bus_priority枚举结构 -```c -enum Bus_priority{ - PriorityLevelOne = 1, - PriorityLevelTwo, - PriorityLevelThree, - PriorityLevelforth, - PriorityLevelFive, - PriorityLevelSix, - PriorityLevelSeven -}; -``` -Bus_priority变量表示总线优先级,数值越低优先级越高 - - -* struct xs_IIC_bus结构 -```c -struct xs_I2CBus { - struct xs_Bus bus; /* Basic properties of the bus */ - unsigned int speed; /* IIC communication rate*/ -}; -``` -speed成员记录I2C总线与传感器设备通信的速率 - -* struct xs_SPIBus结构 -```c -struct xs_SPIBus { - struct xs_Bus bus; /* Basic properties of the bus */ - enum Bus_priority priority /* Bus priority */ - unsigned char CPHA; /* SPI clock phase*/ - unsigned char CPOL; /* SPI clock polarity*/ -}; -``` -CPHA成员记录SPI总线的时钟相位,CPOL成员记录SPI总线的时钟极性 - -* struct xs_CANBus结构 -```c -struct xs_CAN_bus { - struct xs_Bus bus; /* Basic properties of the bus */ - enum Bus_priority priority /* Bus priority */ - unsigned int speed; /* CAN communication rate*/ -}; -``` - -* struct xs_USBBus结构 -```c -struct xs_USB_bus { - struct xs_Bus bus; /* Basic properties of the bus */ - enum Bus_priority priority /* Bus priority */ - unsigned int speed; /* USB communication rate*/ -}; -``` - - -* struct xs_Device结构 -```c -typedef struct xs_Device { - char dev_name[XS_NAME_MAX]; /* name of device */ - enum xs_DeviceType type; - struct mutex mut; /* Mutually Exclusive Semaphore*/ - xs_uint32 ref_count; /* the number of open */ - void * driver; /* Mounted driver*/ - struct device_ops ops; /* Device operation function set */ - struct xs_Device * prev; - struct xs_Device * next; - void * data; - }; -}; -typedef struct xs_Device * xs_DevicePointer ; -``` - - - -* struct device_ops结构 -```c - struct device_ops{ - int (*init)(struct xs_Device * dev); - int (*open)(struct xs_Device * dev); - int (*close)(struct xs_Device * dev); - int (*read)(struct xs_Device * dev, void * buf, int unm); - int (*write)(struct xs_Device * dev, void *buf, int num); - int (*ioctl)(struct xs_Device * dev, int cmd); - } -``` -device_ops包含统一的、类似文件系统的API,用于对具体外设进行实际的数据读写。如在使用一个传感器前后需要打开(open)/关闭(close)该传感器,read、write分别用与从传感器接收数据与向传感器发送数据,ioctl用于配置传感器属性(如波特率) - -* struct xs_HardwareDev结构 -```c -struct xs_HardwareDev{ - struct xs_Device dev; - struct device_ops ops; - - int (*probe)(struct xs_Device * dev); - int (*remove)(struct xs_Device * dev); - void (*shutdown)(struct xs_Device * dev); - int (*suspend)(struct xs_Device * dev, int state); - int (*resume)(struct xs_Device * dev); -}; -``` - -* struct IICHardwareDevice结构 -```c - struct IICHardwareDevice{ - struct xs_HardwareDev hardware; - unsigned short IIC_addr; - void * PrivData; - } -``` - -* struct SPIHardwareDevice结构 -```c - struct SPIHardwareDevice{ - struct xs_HardwareDev hardware; - unsigned short SPI_addr; - void * PrivData; - } -``` - -* struct CANHardwareDevice结构 -```c - struct CANHardwareDevice{ - struct xs_HardwareDev hardware; - unsigned short CAN_addr; - void * PrivData; - } -``` - -* struct USBHardwareDevice结构 -```c - struct USBHardwareDevice{ - struct xs_HardwareDev hardware; - unsigned short USB_addr; - void * PrivData; - } -``` - - -* struct xs_DeviceDriver结构 - ```c -struct xs_DeviceDriver{ - char driver_name[XS_NAME_MAX]; - struct bus_type * bus; - enum driver_state falg; + struct HardwareDev *owner_haldev; + struct Driver *owner_driver; - int (*add)(struct xs_DeviceDriver *dev); - int (*remove)(struct xs_DeviceDriver *dev); - int (*probe)(struct xs_HardwareDev *dev); - void (*shutdown)(struct xs_HardwareDev *dev); - struct list_drv{ - struct xs_DeviceDriver * prev; - struct xs_DeviceDriver * next; - void * data; - }; -} Device_driver_head; - ``` + void *private_data; + /*manage the drv of the bus*/ + uint8 driver_cnt; + uint8 bus_drvlink_flag; + DoubleLinklistType bus_drvlink; + + /*manage the dev of the bus*/ + uint8 haldev_cnt; + uint8 bus_devlink_flag; + DoubleLinklistType bus_devlink; + + uint8 bus_cnt; + uint8 bus_link_flag; + DoubleLinklistType bus_link; +}; +``` +* enum BusType枚举结构 +```c +enum BusType +{ + TYPE_I2C_BUS = 0, + TYPE_SPI_BUS, + TYPE_HWTIMER_BUS, + TYPE_USB_BUS, + TYPE_CAN_BUS, + TYPE_WDT_BUS, + TYPE_SDIO_BUS, + TYPE_TOUCH_BUS, + TYPE_LCD_BUS, + TYPE_PIN_BUS, + TYPE_RTC_BUS, + TYPE_SERIAL_BUS, + TYPE_BUS_END, +}; +``` + +* enum BusState枚举结构 +```c +enum BusState +{ + BUS_INIT = 0, + BUS_INSTALL, + BUS_UNINSTALL, +}; +``` +* struct HardwareDev结构 +```c +struct HardwareDev +{ + int8 dev_name[NAME_NUM_MAX]; + enum DevType dev_type; + enum DevState dev_state; + + const struct HalDevDone *dev_done; + + int (*dev_recv_callback) (void *dev, x_size_t length); + int (*dev_block_control) (struct HardwareDev *dev, struct HalDevBlockParam *block_param); + + struct Bus *owner_bus; + void *private_data; + + int32 dev_sem; + + DoubleLinklistType dev_link; +}; +``` +* struct HalDevDone结构 +```c +struct HalDevDone +{ + uint32 (*open) (void *dev); + uint32 (*close) (void *dev); + uint32 (*write) (void *dev, struct BusBlockWriteParam *write_param); + uint32 (*read) (void *dev, struct BusBlockReadParam *read_param); +}; +``` +HalDevDone包含统一的、类似文件系统的API,用于对具体外设进行实际的开关和数据读写。如在使用一个外设前后需要打开(open)/关闭(close)该外设,read、write分别用与从外设接收数据与向外设发送数据。 +* struct Driver结构 + ```c +struct Driver +{ + int8 drv_name[NAME_NUM_MAX]; + enum DriverType driver_type; + enum DriverState driver_state; + + uint32 (*configure)(void *drv, struct BusConfigureInfo *configure_info); + + struct Bus *owner_bus; + void *private_data; + + DoubleLinklistType driver_link; +}; +``` +回调函数configure用于对具体外设进行实际的配置。 ## 使用场景 -在获取i2c数据前,要匹配设备驱动,在设备驱动打开后对设备进行读写。使用完毕后,关闭设备驱动。 +在获取外设数据前,要先获取外设bus,匹配driver和device数据结构,若有需要应配置外设driver,在外设device打开后对外设进行读写,使用完毕后,关闭设备。 -完整的使用过程示例如下: +以SERIAL为例,完整的使用过程示例如下: ```c int main(int argc, char *argv[]) { - int ret; - struct xs_I2cDevice *dev; + int ret = EOK; + char test_str[] = "Hello AIIT!\r\n"; - /* find the i2c device instance */ - dev = (struct xs_I2cDevice*)xs_DeviceFind("i2c_temp",XS_IIC_BUS); //获取设备句柄 - if(dev == NULL) - { - xs_kprintf("find iic device error\n"); + struct Bus *bus; + struct HardwareDev *dev; + struct Driver *drv; + + /* find the serial bus pointer */ + bus = BusFind(SERIAL_BUS_NAME); + if (NONE == bus) { + KPrintf("BusFind %s failed\n", SERIAL_BUS_NAME); + return ERROR; + } + + /* find the serial driver pointer */ + drv = BusFindDriver(bus, SERIAL_DRV_NAME); + if (NONE == drv) { + KPrintf("BusFindDriver %s failed\n", SERIAL_DRV_NAME); + return ERROR; + } + + /* find the serial device pointer */ + dev = BusFindDevice(bus, SERIAL_DEVICE_NAME); + if (NONE == dev) { + KPrintf("BusFindDevice %s failed\n", SERIAL_DEVICE_NAME); + return ERROR; } - /* open the device instance */ - ret = xs_DeviceOpen(dev); //打开设备 - XS_ASSERT(ret == XS_EOK); + /*step 1: init bus_driver, change struct SerialCfgParam if necessary*/ + struct SerialCfgParam serial_cfg; + memset(&serial_cfg, 0, sizeof(struct SerialCfgParam)); + configure_info.configure_cmd = OPE_INT; + configure_info.private_data = &serial_cfg; + ret = BusDrvConfigure(drv, &configure_info); + if (EOK != ret) { + KPrintf("BusDrvConfigure OPE_INT failed error code %d\n", ret); + return ret; + } - /* read temperature sensor data for 5 times */ - for (int i = 0; i < 5; i++) - xs_kprintf("Current CO2 concentration is %u ppm\n", xs_DeviceRead(dev)); //读取设备数据 - xs_DeviceClose(dev); //关闭数据 + /*step 2: match serial bus_driver with bus_device*/ + bus->match(drv, dev); - return 0; + /*step 3: open bus_device, configure struct SerialDevParam if necessary*/ + serial_dev_param->serial_set_mode = SIGN_OPER_INT_RX; + serial_dev_param->serial_stream_mode = SIGN_OPER_STREAM; + ret = BusDevOpen(dev); + if (EOK != ret) { + KPrintf("BusDevOpen failed error code %d\n", ret); + return ret; + } + + /*step 4: write serial data, configure struct BusBlockWriteParam*/ + struct BusBlockWriteParam write_param; + write_param.pos = 0; + write_param.buffer = (void *)test_str; + write_param.size = sizeof(test_str) - 1; + BusDevWriteData(bus_device, &write_param); + + /*step 5: close bus_device*/ + BusDevClose(bus_device); + + return EOK; } ``` - - - ## 函数接口 -### 设备初始化函数 +* BUS注册函数 ```c /** - * This function will initialize the specified device - * - * @param dev the pointer of device driver structure - * - * @return the result - */ -xs_err_t xs_DeviceInit(xs_DevicePointer dev) -{ - xs_err_t result = XS_EOK; - - XS_ASSERT(dev != XS_NULL); - - /*driver init*/ - if(dev->driver && dev->driver->flag & DRIVER_FLAG_MOUNTED){ - device_register(dev) - if (!(dev->flag & DEVICE_FLAG_ACTIVATED)) - { - result = device_init(dev);/* Macro */ - if (result != XS_EOK) - { - xs_kprintf("Initialize device:%s failed. The error code is %d\n", - dev->name, result); - } - else - { - dev->flag |= XS_DEVICE_FLAG_ACTIVATED; - } - } - - }else{ - return DRIVER_ERROR; - } - - return result; -} -``` - -### 设备查找函数 - -```c -/** - * This function finds a device driver by specified name. - */ -xs_DevicePointer xs_DeviceFind(const char *name) -{ - enum xs_BusType e; - xs_DevicePointer dev; - for(e=0;e !=XS_BUS_END; ++e){ - if(dev = __xs_DeviceFind_by_type(name,e) && dev !=BUS_ERROR) - return dev; - } - return NULL; -} - -xs_DevicePointer xs_DeviceFind(const char *name,enum xs_BusType bus_type){ - return __xs_DeviceFind_by_type(name,bus_type); -}; - -xs_DevicePointer __xs_DeviceFind_by_type(const char *name,enum xs_BusType bus_type) -{ - struct xs_Device *device; - struct xs_ListNode *node; - - xs_Bus bus = get_bus_from_type(bus_type); - if(!(bus && bus->falg = BUS_FLAG_ACTIVED)) - return BUS_ERROR;/*a point to speical area */ - - /* enter critical */ - if (xs_get_kthread_descriptor() != XS_NULL) - xs_critical_enter(); - - /* try to find device */ - for (node = bus->device.next; - node != &(bus->device); - node = node->next) - { - device = xs_list_entry(node, struct xs_Device, list); - if (xs_strncmp(device->name, name, XS_NAME_MAX) == 0) - { - /* leave critical */ - if (xs_get_kthread_descriptor() != XS_NULL) - xs_critical_exit(); - - return (xs_DevicePointer)device; - } - } - - /* leave critical */ - if (xs_get_kthread_descriptor() != XS_NULL) - xs_critical_exit(); - - /* not found */ - return XS_NULL; -} - -``` -### 设备打开函数 -```c - -/* -* This function open a device +* @Description: support to register bus pointer with linklist +* @param bus - bus pointer +* @return successful:EOK,failed:NONE */ -xs_err_t xs_DeviceOpen(xs_DevicePointer dev) +int BusRegister(struct Bus *bus) { - xs_err_t result = XS_EOK; + x_err_t ret = EOK; + NULL_PARAM_CHECK(bus); - XS_ASSERT(dev != XS_NULL); - if(!(dev->bus)) - return BUS_ERROR; - if(!(dev->driver && dev->bus->match(dev->driver,dev))) - return DRIVER_ERROR; + bus->match = BusMatchDrvDev; - /* if device is not initialized, initialize it. */ - if (!(dev->flag & DEVICE_FLAG_ACTIVATED)) - { - xs_DeviceInit(dev); - } + BusLinkInit(bus); - /* call device_open interface */ - if (device_open != XS_NULL) - { - result = device_open(dev, oflag); - dev->ref_count++; - XS_ASSERT(dev->ref_count != 0); - } - return result; -} -``` -### 设备关闭函数 + bus->bus_lock = KMutexCreate(); -```c -xs_err_t xs_DeviceClose(xs_DevicePointer dev) -{ - xs_err_t result = XS_EOK; + DoubleLinkListInsertNodeAfter(&bus_linklist, &(bus->bus_link)); - XS_ASSERT(dev != XS_NULL); - - if (dev->ref_count == 0) - return XS_ERROR; - - dev->ref_count--; - - if (dev->ref_count != 0) - return XS_EOK; - - /* call device_close interface */ - if (device_close != XS_NULL) - { - result = device_close(dev); - - } - - - return result; + return ret; } ``` - - -## i2c设备注册与卸载 +* BUS删除函数 ```c -int int xs_I2cDeviceRegister(struct xs_I2cDevice *dev,const char *name, xs_uint16 flags); -int xs_I2cDeviceunRegister(struct xs_I2cDevice *dev); +/** +* @Description: support to unregister bus pointer and delete its linklist node +* @param bus - bus pointer +* @return successful:EOK,failed:NONE +*/ +int BusUnregister(struct Bus *bus) +{ + NULL_PARAM_CHECK(bus); + bus->bus_cnt--; + + DoubleLinkListRmNode(&(bus->bus_link)); + + return EOK; +} ``` -### xs_I2cDeviceRegister函数具体实现 +* DRIVER注册到BUS函数 ```c -int xs_I2cDeviceRegister(struct xs_I2cDevice *dev,const char *name, xs_uint16 flags) +/** +* @Description: support to register driver pointer to bus pointer +* @param bus - bus pointer +* @param driver - driver pointer +* @return successful:EOK,failed:NONE +*/ +int DriverRegisterToBus(struct Bus *bus, struct Driver *driver) { - if(dev != NULL) - { - return xs_DeviceRegister(dev,name,0); - } - return -XS_ERROR; + NULL_PARAM_CHECK(bus); + NULL_PARAM_CHECK(driver); + + driver->owner_bus = bus; + bus->driver_cnt++; + + DoubleLinkListInsertNodeAfter(&bus->bus_drvlink, &(driver->driver_link)); + + return EOK; } +``` -xs_err_t xs_DeviceRegister(void * dev,const char *name, xs_uint16 flags) +* DRIVER从BUS中删除函数 +```c +/** +* @Description: support to delete driver pointer from bus pointer +* @param bus - bus pointer +* @param driver - driver pointer +* @return successful:EOK,failed:NONE +*/ +int DriverDeleteFromBus(struct Bus *bus, struct Driver *driver) { + NULL_PARAM_CHECK(bus); + NULL_PARAM_CHECK(driver); - if (dev == XS_NULL) - return -XS_ERROR; + bus->driver_cnt--; - if (xs_DeviceFind(name) != XS_NULL) - return -XS_ERROR; + DoubleLinkListRmNode(&(driver->driver_link)); - xs_CriticalEnter(); - for (node = Device_driver_head.NodeNext; - node != &(xiaoshan_device_head); - node = node->NodeNext) + free(driver); + + return EOK; +} +``` + +* DEVICE注册到BUS函数 +```c +/** +* @Description: support to register dev pointer to bus pointer +* @param bus - bus pointer +* @param device - device pointer +* @return successful:EOK,failed:NONE +*/ +int DeviceRegisterToBus(struct Bus *bus, struct HardwareDev *device) +{ + NULL_PARAM_CHECK(bus); + NULL_PARAM_CHECK(device); + + device->owner_bus = bus; + bus->haldev_cnt++; + + DoubleLinkListInsertNodeAfter(&bus->bus_devlink, &(device->dev_link)); + + return EOK; +} +``` + +* DEVICE从BUS中删除函数 +```c +/** +* @Description: support to delete dev pointer from bus pointer +* @param bus - bus pointer +* @param device - device pointer +* @return successful:EOK,failed:NONE +*/ +int DeviceDeleteFromBus(struct Bus *bus, struct HardwareDev *device) +{ + NULL_PARAM_CHECK(bus); + NULL_PARAM_CHECK(device); + + bus->haldev_cnt--; + + DoubleLinkListRmNode(&(device->dev_link)); + + free(device); + + return EOK; +} +``` + +* 查找BUS函数 +```c +/** +* @Description: support to find bus pointer by bus name +* @param bus_name - bus name +* @return successful:bus pointer,failed:NONE +*/ +BusType BusFind(const char *bus_name) +{ + struct Bus *bus = NONE; + + DoubleLinklistType *node = NONE; + DoubleLinklistType *head = &bus_linklist; + + for (node = head->node_next; node != head; node = node->node_next) { - struct xs_Device *device; - - device = XS_DOUBLE_LINKLIST_ENTRY(node, struct xs_Device, link); - if (device) - { - XS_ASSERT(device != dev); + bus = SYS_DOUBLE_LINKLIST_ENTRY(node, struct Bus, bus_link); + if(!strcmp(bus->bus_name, bus_name)) { + return bus; } } - xs_CriticalExit(); - xs_strncpy(dev->dev_string, name, XS_NAME_MAX); - register xs_base temp = xs_DisableHwInterrupt(); - { - xs_DoubleLinkListInsertNodeAfter(&xiaoshan_device_head, &(dev->link)); - } - xs_EnableHwInterrupt(temp); - dev->sign = flags; - dev->call_cnt = 0; - dev->status = 0; - xs_InitWqueue(&(dev->wait_queue)); - return XS_EOK; + + KPrintf("BusFind cannot find the %s bus.return NULL\n", bus_name); + return NONE; } ``` + +* 查找DRIVER函数 +```c +/** +* @Description: support to find driver pointer of certain bus by driver name +* @param bus - bus pointer +* @param driver_name - driver name +* @return successful:EOK,failed:NONE +*/ +DriverType BusFindDriver(struct Bus *bus, const char *driver_name) +{ + NULL_PARAM_CHECK(bus); + struct Driver *driver = NONE; + + DoubleLinklistType *node = NONE; + DoubleLinklistType *head = &bus->bus_drvlink; + + for (node = head->node_next; node != head; node = node->node_next) + { + driver = SYS_DOUBLE_LINKLIST_ENTRY(node, struct Driver, driver_link); + if(!strcmp(driver->drv_name, driver_name)) { + return driver; + } + } + + KPrintf("BusFindDriver cannot find the %s driver.return NULL\n", driver_name); + return NONE; +} +``` + +* 查找DEVICE函数 +```c +/** +* @Description: support to find device pointer of certain bus by device name +* @param bus - bus pointer +* @param device_name - device name +* @return successful:EOK,failed:NONE +*/ +HardwareDevType BusFindDevice(struct Bus *bus, const char *device_name) +{ + NULL_PARAM_CHECK(bus); + struct HardwareDev *device = NONE; + + DoubleLinklistType *node = NONE; + DoubleLinklistType *head = &bus->bus_devlink; + + for (node = head->node_next; node != head; node = node->node_next) + { + device = SYS_DOUBLE_LINKLIST_ENTRY(node, struct HardwareDev, dev_link); + + if(!strcmp(device->dev_name, device_name)) { + return device; + } + } + + KPrintf("BusFindDevice cannot find the %s device.return NULL\n", device_name); + return NONE; +} +``` + +* 打开DEVICE函数 +```c +/** +* @Description: support to open dev +* @param dev - dev pointer +* @return successful:EOK,failed:ERROR +*/ +uint32 BusDevOpen(struct HardwareDev *dev) +{ + NULL_PARAM_CHECK(dev); + + x_err_t ret = EOK; + + if (dev->dev_done->open) { + ret = dev->dev_done->open(dev); + if(ret) { + KPrintf("BusDevOpen error ret %u\n", ret); + return ERROR; + } + } + + return ret; +} +``` + +* 关闭DEVICE函数 +```c +/** +* @Description: support to close dev +* @param dev - dev pointer +* @return successful:EOK,failed:ERROR +*/ +uint32 BusDevClose(struct HardwareDev *dev) +{ + NULL_PARAM_CHECK(dev); + + x_err_t ret = EOK; + + if (dev->dev_done->close) { + ret = dev->dev_done->close(dev); + if(ret) { + KPrintf("BusDevClose error ret %u\n", ret); + return ERROR; + } + } + + return ret; +} +``` + +* DEVICE写函数 +```c +/** +* @Description: support to write data to dev +* @param dev - dev pointer +* @param write_param - BusBlockWriteParam +* @return successful:EOK,failed:NONE +*/ +uint32 BusDevWriteData(struct HardwareDev *dev, struct BusBlockWriteParam *write_param) +{ + NULL_PARAM_CHECK(dev); + + if (dev->dev_done->write) { + return dev->dev_done->write(dev, write_param); + } + + return EOK; +} +``` + +* DEVICE读函数 +```c +/** +* @Description: support to read data from dev +* @param dev - dev pointer +* @param read_param - BusBlockReadParam +* @return successful:EOK,failed:NONE +*/ +uint32 BusDevReadData(struct HardwareDev *dev, struct BusBlockReadParam *read_param) +{ + NULL_PARAM_CHECK(dev); + + if (dev->dev_done->read) { + return dev->dev_done->read(dev, read_param); + } + + return EOK; +} +``` + +* DRIVER配置函数 +```c +/** +* @Description: support to configure drv, include OPE_CFG and OPE_INT +* @param drv - drv pointer +* @param configure_info - BusConfigureInfo +* @return successful:EOK,failed:NONE +*/ +uint32 BusDrvConfigure(struct Driver *drv, struct BusConfigureInfo *configure_info) +{ + NULL_PARAM_CHECK(drv); + NULL_PARAM_CHECK(configure_info); + + x_err_t ret = EOK; + + if (drv->configure) { + ret = drv->configure(drv, configure_info); + if(ret) { + KPrintf("BusDrvCfg error, ret %u\n", ret); + return ERROR; + } + } + + return ret; +} +``` diff --git a/docs/doc/component/imagesrc/spi_bus.png b/docs/doc/component/imagesrc/spi_bus.png new file mode 100644 index 0000000000000000000000000000000000000000..81b6d9f4cf43fce3681cc86a7f426b7cffabd82f GIT binary patch literal 37524 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