QNX® Neutrino SPI驅動框架與代碼分析

本文主要描述QNX SPI Drvier的相關內容,並以Texas Instruments DRA71x Jacinto6 Cortex A15處理器爲例講解

QNX® Neutrino I2C驅動框架與代碼分析,我們具體分析了I2C驅動框架與源碼的實現,同樣在QNX系統裏,也提供了SPI驅動框架。

目錄結構與組成部分

下面是Texas.Instruments.DRA71x.Jacinto6.Entry.BSP.for.QNX.SDP.6.6包目錄,我們只展開跟SPI相關目錄內容:

.
├── Makefile
├── images
│   ├── Makefile
│   └── mkflashimage.sh
├── install
├── manifest
├── prebuilt
│   ├── armle-v7
│   │   ├── lib
│   │   │   └── dll
│   │   └── usr
│   │       └── lib
│   │           ├── libspi-master.a
│   │           └── libutil.a
│   └── usr
│       └── include
└── src
    ├── Makefile
    ├── hardware
    │   ├── spi
    │   │   ├── Makefile
    │   │   ├── master
    │   │   │   ├── Makefile
    │   │   │   ├── _spi_close_ocb.c
    │   │   │   ├── _spi_create_instance.c
    │   │   │   ├── _spi_devctl.c
    │   │   │   ├── _spi_devlock.c
    │   │   │   ├── _spi_dlload.c
    │   │   │   ├── _spi_init_iofunc.c
    │   │   │   ├── _spi_iomsg.c
    │   │   │   ├── _spi_iomsg_cmdread.c
    │   │   │   ├── _spi_iomsg_dmaxchange.c
    │   │   │   ├── _spi_iomsg_read.c
    │   │   │   ├── _spi_iomsg_write.c
    │   │   │   ├── _spi_iomsg_xchange.c
    │   │   │   ├── _spi_main.c
    │   │   │   ├── _spi_ocb.c
    │   │   │   ├── _spi_read.c
    │   │   │   ├── _spi_slogf.c
    │   │   │   ├── _spi_write.c
    │   │   │   ├── arm
    │   │   │   ├── common.mk
    │   │   │   ├── module.tmpl
    │   │   │   ├── project.xml
    │   │   │   └── proto.h
    │   │   └── omap4430
    │   │       ├── Makefile
    │   │       ├── arm
    │   │       ├── clock_toggle.c
    │   │       ├── clock_toggle.h
    │   │       ├── common.mk
    │   │       ├── config.c
    │   │       ├── context_restore.c
    │   │       ├── context_restore.h
    │   │       ├── intr.c
    │   │       ├── module.tmpl
    │   │       ├── omap4430spi.c
    │   │       ├── omap4430spi.h
    │   │       ├── pinfo.mk
    │   │       ├── project.xml
    │   │       ├── reg_map_init.c
    │   │       ├── sdma.c
    │   │       ├── sdma.h
    │   │       ├── spi-omap4430.use
    │   │       └── wait.c
    │   ├── startup
    │   └── support
    └── utils
        ├── Makefile
        └── r

I2C框架由以下部分組成:

hardware/spi/omap4430/* 硬件接口

├── hardware
│   ├── spi
│   │   ├── Makefile
│   │   ├── master
│   │   └── omap4430
│   │       ├── Makefile
│   │       ├── arm
│   │       ├── clock_toggle.c
│   │       ├── clock_toggle.h
│   │       ├── common.mk
│   │       ├── config.c
│   │       ├── context_restore.c
│   │       ├── context_restore.h
│   │       ├── intr.c
│   │       ├── module.tmpl
│   │       ├── omap4430spi.c
│   │       ├── omap4430spi.h
│   │       ├── pinfo.mk
│   │       ├── project.xml
│   │       ├── reg_map_init.c
│   │       ├── sdma.c
│   │       ├── sdma.h
│   │       ├── spi-omap4430.use
│   │       └── wait.c

hardware/spi/master 資源管理器層

├── hardware
│   ├── spi
│   │   ├── Makefile
│   │   ├── master
│   │   │   ├── Makefile
│   │   │   ├── _spi_close_ocb.c
│   │   │   ├── _spi_create_instance.c
│   │   │   ├── _spi_devctl.c
│   │   │   ├── _spi_devlock.c
│   │   │   ├── _spi_dlload.c
│   │   │   ├── _spi_init_iofunc.c
│   │   │   ├── _spi_iomsg.c
│   │   │   ├── _spi_iomsg_cmdread.c
│   │   │   ├── _spi_iomsg_dmaxchange.c
│   │   │   ├── _spi_iomsg_read.c
│   │   │   ├── _spi_iomsg_write.c
│   │   │   ├── _spi_iomsg_xchange.c
│   │   │   ├── _spi_main.c
│   │   │   ├── _spi_ocb.c
│   │   │   ├── _spi_read.c
│   │   │   ├── _spi_slogf.c
│   │   │   ├── _spi_write.c
│   │   │   ├── arm
│   │   │   ├── common.mk
│   │   │   ├── module.tmpl
│   │   │   ├── project.xml
│   │   │   └── proto.h
│   │   └── omap4430

硬件管理接口

這是實現 SPI 主設備特定於硬件的底層功能的代碼的接口。spi_funcs_t 結構是一個指向函數的指針表,您可以爲特定於硬件的底層模塊提供這些函數。 高級應用層代碼調用這些函數。

typedef struct {
    size_t  size;   /* size of this structure */
    void*   (*init)( void *hdl, char *options );
    void    (*fini)( void *hdl );
    int     (*drvinfo)( void *hdl, spi_drvinfo_t *info );
    int     (*devinfo)( void *hdl, uint32_t device, spi_devinfo_t *info );
    int     (*setcfg)( void *hdl, uint16_t device, spi_cfg_t *cfg );
    void*   (*xfer)( void *hdl, uint32_t device, uint8_t *buf, int *len );
    int     (*dma_xfer)( void *hdl, uint32_t device, spi_dma_paddr_t *paddr, int len );
} spi_funcs_t;


底層模塊中必須有一個函數表目錄,它必須命名爲spi_drv_entry。 應用代碼或者資源管理器代碼查找此符號名稱以查找底層模塊的函數表。

底層模塊句柄結構

SPIDEV 結構是底層模塊必須返回給高層代碼的句柄。 您可以擴展結構,但 SPIDEV 必須位於頂部。 當高層代碼調用底層函數時,此句柄也會傳遞給底層驅動程序。

typedef struct _spidev_entry {
    iofunc_attr_t   attr;
    void        *hdl;       /* Pointer to high-level handle */
    void        *lock;      /* Pointer to lock list */
} SPIDEV;

init函數

init函數初始化主接口。該函數的原型是:

void *init( void *hdl, char *options );
/*
* Parameters:
* (in)     hdl         Handle returned from init()  init函數返回的句柄;
* (in)     options     A pointer to the command-line arguments for the low-level module 底層模塊命令行參數

該函數必須返回一個句柄,它是指向底層模塊的 SPIDEV 的指針,如果發生錯誤,則返回 NULL。

fini函數

fini函數清理驅動程序並釋放與給定句柄關聯的所有內存。該函數的原型是:

void (*fini)(void *hdl);

drvinfo函數

drvinfo函數返回有關驅動程序的信息。該函數的原型是:

int drvinfo( void *hdl, spi_drvinfo_t *info );
/* 
hdl            init函數返回的句柄
info
一個指向spi_drvinfo_t結構的指針,函數應該在該結構中存儲信息:
typedef struct {
        uint32_t    version;
        char        name[16];   // Driver name
        uint32_t    feature;
#define SPI_FEATURE_DMA         (1 << 31)
#define SPI_FEATURE_DMA_ALIGN   0xFF
      } spi_drvinfo_t; 
*/

devinfo函數

devinfo函數返回有關驅動程序的信息。該函數的原型是:

int devinfo( void *hdl, uint32_t device, spi_devinfo_t *info );
/*  
hdl            init函數返回的句柄
device         設備 ID。 您可以使用 SPI_DEV_DEFAULT 或它來選擇當前設備; 否則選擇下一個設備。
info
一個指向spi_drvinfo_t結構的指針,函數應該在該結構中存儲信息:
typedef struct {
    uint32_t    device;     // Device ID 
    char        name[16];   // Device description 
    spi_cfg_t   cfg;        // Device configuration
} spi_devinfo_t;
*/

setcfg函數

setcfg 函數更改 SPI 總線上特定設備的配置。該函數的原型是:

int setcfg( void *hdl, uint16_t device, spi_cfg_t *cfg );
/* 
* Parameters:
* (in)     hdl         Handle returned from init()  init函數返回的句柄;
* (in)     device      The device ID.   設備 ID。 
* (in)     cfg         A pointer to the configuration structure. This structure is defined as:       
指向配置結構的指針。 該結構定義爲
typedef struct {
    uint32_t    mode;
    uint32_t    clock_rate;
} spi_cfg_t;
*/

xfer函數

xfer 函數啓動傳輸、接收或交換事務。該函數的原型是:

void *xfer( void *hdl, uint32_t device, uint8_t *buf, int *len );
/* 
* xfer.
* Parameters:
* (in)     hdl         Handle returned from init()   init函數返回的句柄
* (in)     device      The device ID.   設備 ID。
* (in)     buf         Buffer for received data      指向要發送的數據緩衝區的指針;
* (in)     len         Length in bytes of buf        發送的數據的長度(以字節爲單位);
*/

該函數必須返回一個指向接收/交換緩衝區的指針,並在 len 指向的位置存儲底層模塊已發送、接收或交換的數據的字節長度。 高層代碼檢查長度以確定事務是否成功。

dma_xfer函數

dma_xfer 函數啓動 DMA 發送、接收或交換事務。該函數的原型是:

int *dma_xfer( void *hdl, uint32_t device, spi_dma_paddr_t *paddr, int len );
/* 
* dma_xfer.
* Parameters:
* (in)     hdl         Handle returned from init()   init函數返回的句柄
* (in)     device      The device ID.   設備 ID。
* (in)     paddr       指向 DMA 緩衝區地址的指針,其定義爲:
                        typedef struct {
                            uint64_t    rpaddr;
                            uint64_t    wpaddr;
                        } spi_dma_paddr_t;
                        rpaddr 和 wpaddr 是物理地址。
* (in)     len         Length in bytes of buf        發送的數據的長度(以字節爲單位);
*/

此函數必須返回 DMA 已成功傳輸的字節數,如果發生錯誤,則返回 -1。 管理 DMA 緩衝區是應用程序的責任。

API library 庫的接口

libspi-master 庫提供了一個接口來調解對 SPI 主設備的訪問。 資源管理器層註冊一個設備名稱(通常是/dev/spi0)。 應用程序通過使用 <hw/spi-master.h> 中聲明的函數訪問 SPI 主設備。庫函數包含如下:

spi_open函數

spi_open() 函數讓應用程序連接到 SPI 資源管理器。 這個函數的原型是:

int spi_open( const char *path );
/* 
* dma_xfer.
* Parameters:
* (in)     path         The path name of the SPI device, usually /dev/spi0.   SPI 設備的路徑名,通常爲 /dev/spi0。
*/

此函數返回一個文件描述符,如果打開失敗,則返回 -1。

spi_close函數

spi_close() 函數讓應用程序連接到 SPI 資源管理器。 這個函數的原型是:

int spi_close( int fd );
/* 
* spi_close.
* Parameters:
* (in)     fd   The file descriptor that the spi_open() function returned. spi_open() 函數返回的文件描述符。。
*/

spi_setcfg函數

spi_setcfg() 函數設置 SPI 總線上特定設備的配置。這個函數的原型是:

int spi_setcfg( int fd, uint32_t device, spi_cfg_t *cfg );
/* 
* spi_close.
* Parameters:
* (in)     fd          The file descriptor that the spi_open() function returned. spi_open() 函數返回的文件描述符。
* (in)     device      The device ID.   設備 ID。 
* (in)     cfg         A pointer to the configuration structure. This structure is defined as:       
指向配置結構的指針。 該結構定義爲
typedef struct {
    uint32_t    mode;
    uint32_t    clock_rate;
} spi_cfg_t;
*/

spi_getdevinfo函數

spi_getdevinfo() 函數獲取 SPI 總線上特定設備的信息。這個函數的原型是:

int spi_getdevinfo( int fd, uint32_t device, spi_devinfo_t *devinfo );
/*  
fd             spi_open() 函數返回的文件描述符。
device         設備 ID。 設備 ID 或 SPI_DEV_ID_NONE 以選擇第一個設備。 
                        如果指定了設備ID,則可以與SPI_DEV_DEFAULT 進行OR 來選擇指定的設備; 否則,選擇下一個設備。
devinfo
一個指向spi_devinfo_t結構的指針,函數應該在該結構中存儲信息:
typedef struct {
    uint32_t    device;     // Device ID 
    char        name[16];   // Device description 
    spi_cfg_t   cfg;        // Device configuration
} spi_devinfo_t;
*/

spi_getdrvinfo函數

spi_getdrvinfo() 函數獲取低級模塊的驅動程序信息。這個函數的原型是:

int spi_getdrvinfo( int fd, spi_drvinfo_t *drvinfo );
/*  
fd             spi_open() 函數返回的文件描述符。
drvinfo
一個指向spi_drvinfo_t結構的指針,函數應該在該結構中存儲信息:
typedef struct {
        uint32_t    version;
        char        name[16];   // Driver name
        uint32_t    feature;
      } spi_drvinfo_t; 
*/

spi_read函數

spi_read() 函數從 SPI 總線上的特定設備讀取數據。這個函數的原型是:

int spi_read( int fd, uint32_t device, void *buf, int len );
/* 
* spi_read.
* Parameters:
* (in)     fd          The file descriptor that the spi_open() function returned. spi_open() 函數返回的文件描述符。
* (in)     device      The device ID.   設備 ID。
* (out)    buf         A pointer to the read buffer. 指向要讀取的數據緩衝區的指針;
* (in)     len         Length in bytes of buf        讀取的數據的長度(以字節爲單位);
該函數返回它成功從設備讀取的數據字節數。 如果發生錯誤,函數返回 -1 並設置 errno:
EIO
The read from the device failed, or a hardware error occurred.
EINVAL
The device ID is invalid, or you're trying to unlock a device that isn't locked.
ENOMEM
Insufficient memory.
EPERM
The device is locked by another connection.
*/

如果此函數返回的字節數與其要求函數讀取的字節數不同,則 SPI 驅動程序通常會將其視爲錯誤。

spi_write函數

spi_write() 函數從 SPI 總線上的特定設備讀取數據。這個函數的原型是:

int spi_write( int fd, uint32_t device, void *buf, int len );
/* 
* spi_write.
* Parameters:
* (in)     fd          The file descriptor that the spi_open() function returned. spi_open() 函數返回的文件描述符。
* (in)     device      The device ID.   設備 ID。
                       最多具有以下標誌之一的設備 ID(可選):
                       SPI_DEV_LOCK
                       SPI_DEV_UNLOCK
* (in)     buf         A pointer to the write buffer. 指向要發送的數據緩衝區的指針;
* (in)     len         Length in bytes of buf        發送的數據的長度(以字節爲單位);
該函數返回它成功從設備讀取的數據字節數。 如果發生錯誤,函數返回 -1 並設置 errno:
EIO
The read from the device failed, or a hardware error occurred.
EINVAL
The device ID is invalid, or you're trying to unlock a device that isn't locked.
ENOMEM
Insufficient memory.
EPERM
The device is locked by another connection.
*/

如果此函數返回的字節數與其要求函數寫入的字節數不同,則 SPI 驅動程序通常會將其視爲錯誤。

spi_xchange函數

spi_exchange() 函數在特定設備和 SPI 主設備之間交換數據。該函數的原型是:

int spi_xchange( int fd, uint32_t device, void *wbuf,  void *rbuf, int len );
/* 
* spi_xchange.
* Parameters:
* (in)     fd          The file descriptor that the spi_open() function returned. spi_open() 函數返回的文件描述符。
* (in)     device      The device ID.   設備 ID。
                       最多具有以下標誌之一的設備 ID(可選):
                       SPI_DEV_LOCK
                       SPI_DEV_UNLOCK
* (in)     wbuf        A pointer to the send buffer.      指向要發送的數據緩衝區的指針;
* (out)    rbuf        A pointer to the receive buffer.   指向要接收的數據緩衝區的指針;
* (in)     len         Length in bytes of buf        發送的數據的長度(以字節爲單位)
該函數返回它成功從設備讀取的數據字節數。 如果發生錯誤,函數返回 -1 並設置 errno:
EIO
The read from the device failed, or a hardware error occurred.
EINVAL
The device ID is invalid, or you're trying to unlock a device that isn't locked.
ENOMEM
Insufficient memory.
EPERM
The device is locked by another connection.
*/

該函數必須返回一個指向接收/交換緩衝區的指針,並在 len 指向的位置存儲底層模塊已發送、接收或交換的數據的字節長度。 高層代碼檢查長度以確定事務是否成功。

spi_cmdread函數

spi_cmdread() 函數向 SPI 總線上的特定設備發送命令,然後從中讀取數據。該函數的原型是:

int spi_cmdread( int fd, uint32_t device, void *cbuf, int16_t clen, void *rbuf, int rlen );
/* 
* spi_cmdread.
* Parameters:
* (in)     fd          The file descriptor that the spi_open() function returned. spi_open() 函數返回的文件描述符。
* (in)     device      The device ID.   設備 ID。
                       最多具有以下標誌之一的設備 ID(可選):
                       SPI_DEV_LOCK
                       SPI_DEV_UNLOCK
* (in)     cbuf        A pointer to the command buffer.                           
* (in)     wbuf        A pointer to the send buffer.      指向要發送的數據緩衝區的指針;
* (out)    rbuf        A pointer to the receive buffer.   指向要接收的數據緩衝區的指針;
* (in)     len         Length in bytes of buf        發送的數據的長度(以字節爲單位)
該函數返回它成功從設備讀取的數據字節數。 如果發生錯誤,函數返回 -1 並設置 errno:
EIO
The read from the device failed, or a hardware error occurred.
EINVAL
The device ID is invalid, or you're trying to unlock a device that isn't locked.
ENOMEM
Insufficient memory.
EPERM
The device is locked by another connection.
*/

如果此函數返回的字節數與其要求函數讀取的字節數不同,則 SPI 驅動程序通常會將其視爲錯誤。

spi_xchange函數

spi_dma_xchange() 函數使用 DMA 在 SPI 主設備和 SPI 設備之間交換數據。該函數的原型是:

int spi_dma_xchange( int fd, uint32_t device, void *wbuf,  void *rbuf, int len );
/* 
* spi_dma_xchange.
* Parameters:
* (in)     fd          The file descriptor that the spi_open() function returned. spi_open() 函數返回的文件描述符。
* (in)     device      The device ID.   設備 ID。
                       最多具有以下標誌之一的設備 ID(可選):
                       SPI_DEV_LOCK
                       SPI_DEV_UNLOCK
* (in)     wbuf        A pointer to the send buffer, or NULL if there's no data to send.
* (out)    rbuf        A pointer to the receive buffer, or NULL if there's no data to receive.
* (in)     len         Length in bytes of buf        發送的數據的長度(以字節爲單位)
該函數返回它成功從設備讀取的數據字節數。 如果發生錯誤,函數返回 -1 並設置 errno:
EIO
The write to the device failed, or a hardware error occurred.
EINVAL
The device ID is invalid, or you're trying to unlock a device that isn't locked, or the buffer address is invalid.
ENOMEM
Insufficient memory.
EPERM
The device is locked by another connection.
ENOTSUP
DMA isn't supported.
*/

如果此函數返回的字節數與它要求該函數交換的字節數不同,則 SPI 驅動程序通常會將其視爲錯誤。

資源管理器設計

在QNX下開發驅動程序,最主要的工作除了瞭解底層硬件具體工作流程外,就是建立一個能與操作系統兼容且支持POSIX的Resource manger框架了。在任何一段程序的執行過程中一段都是從main函數開始的,然而在操作系統中的main函數還傳遞了兩個參數:int argc, char argv,這兩個參數是用來傳遞從shell命令行或者buildfile中傳來對Resource manger具體參數的,使用options (int argc, char argv);函數實現,所以這個函數在main函數中最開始的位置,可以開發的driver具有不同可選的特性,提供使用的便利性。

第一步:書寫options (int argc, char **argv);的具體實現;

第二步:建立一個上下文切換句柄dpp = dispatch_create();這個東東主要用在mainloop中產生一個block特性,可以讓我們等待接受消息;

第三步:iofunc初始化。這一步是將自己實現的函數與POSIX層函數進行接口,解析從read、write、devctl等函數傳來的消息進行解析,以實現底層與應用層函數之間的交互,通過io_funcs.read = io_read,io_funcs.write = io_write,進行函數重載;

第四步:註冊設備名,使設備在命名空間中產生相應的名稱,這一點是整個過程的關鍵了,形如 pathID = resmgr_attach (dpp, &rattr, "/dev/Null",_FTYPE_ANY, 0, &connect_funcs, &io_funcs, &ioattr),這樣不僅註冊了一個設備名,還讓系統知道了我們實習的IO函數對應關係;

第五步:爲之前創建的上下文句柄分配空間,例如ctp = dispatch_context_alloc (dpp);爲了第六步使用;

第六步:通過不斷循環等待dispatch_block()來調用MsgReceive()使Resource manger處於receive block狀態,以接收上層發送來的消息,通過dispatch_handler (ctp)去調用我們自己定義的IO函數。

下面我拿SPI啓動腳本和源碼來分析一下:

QNX系統啓動後,執行一系列腳本命令,然後加載SPI驅動。

spi-master -u0 -d omap4430 base=0x4809A000,bitrate=125000,clock=48000000,irq=66,force=1,channel=2,sdma=1

當QNX執行該腳本時,會自動到指定目錄搜索是否存在spi-master驅動,然後後面一串是參數設置。這一串參數就像Linux設備樹一樣,指定相關的硬件參數。具體參數意義在驅動裏在詳細解釋。

Syntax:
    spi-master -d omap4430 [option[,option ...]] ... &

Options (to override autodetected defaults):

    "base"          /* Base address for this CSPI controller */
    "bitrate"       /* defines teh bitrate to to assigned to the spi */
    "clock"         /* defined the value of the clock source of the SPI */
    "channel"       /* defines the connected channel number (1,2,3, or 4) */
    "irq"           /* IRQ for this CSPI intereface */
    "force"         /* Force the default CSx level */
    "num_cs"        /* number of support devices on this channel (default=1)*/
    "sdma"          /* Disable/enable SDMA for SPI, 1:enable SDMA, 0:disable SDMA (default=0)*/
    "cs_delay"      /* Select clock cycles 0:0.5, 1:1.5, 2:2.5, 3:3.5 (default=1) */
    "somi"          /* determine which pin to use for somi, 0:use D0, 1:use D1 (default=0) */
    "clk_activity"  /* Clocks activity during wake-up mode period, can be 0-3 (default=0) */
    "pwr"           /* Power management setting, can be 0-2 (default=1) */
    "spidatdir"     /* Enable setting of SPIDATDIRx fields */

Notes:
    The OMAP4430 SPI controller can manage up to four devices; each can be
    configured independently using the spi_setcfg() function found in the
    spi-master library.

第一步:書寫options

QNX執行spi-master後,應該執行函數入口在哪裏?其實QNX早就爲我們分配好了,它把所有的驅動當做應用程序,一個應用程序一樣,都有一個main入口。因此分析QNX SPI驅動,可以從_spi_main.c裏的這個函數開始。

int main(int argc, char *argv[])
{
        spi_dev_t       *head = NULL, *tail = NULL, *dev;
        void            *drventry, *dlhdl;
        siginfo_t       info;
        sigset_t        set;
        int                     i, c, devnum = 0;

        UserParm = NULL;

        /* default permission for /dev/spi* entry */
        devperm = 0666;
        /*使能超級鎖定進程的內存和請求I/O特權,讓線程在具有適當特權的架構上執行in、in、out、out、cli和sti I/O操作碼,
          並讓它附加IRQ處理程序。*/
        if (ThreadCtl(_NTO_TCTL_IO, 0) == -1) {
                perror("ThreadCtl");
                return (!EOK);
        }
        // 通過 iofunc_func_init()函數初始化,通過連接和POSIX默認IO結構層功能。
        _spi_init_iofunc();

        while ((c = getopt(argc, argv, "u:U:P:d:")) != -1) {
                switch (c) {
                        case 'u':
                                devnum = strtol(optarg, NULL, 0);
                                break;
                        case 'U':
                                UserParm = strdup(optarg);
                                break;
                        case 'P':
                                if ( optarg ) {
                                        devperm = strtoul(optarg, NULL, 8);     // octal
                                }
                                break;
                        case 'd':
                                if ((drventry = _spi_dlload(&dlhdl, optarg)) == NULL) {
                                        perror("spi_load_driver() failed");
                                        return (-1);
                                }

                                do {
                                        if ((dev = calloc(1, sizeof(spi_dev_t))) == NULL)
                                                goto cleanup;

                                        if (argv[optind] == NULL || *argv[optind] == '-')
                                                dev->opts = NULL;
                                        else
                                                dev->opts = strdup(argv[optind]);
                                        ++optind;
                                        dev->funcs  = (spi_funcs_t *)drventry;
                                        dev->devnum = devnum++;
                                        dev->dlhdl  = dlhdl;

                                        i = _spi_create_instance(dev);

                                        if (i != EOK) {
                                                perror("spi_create_instance() failed");

                                                if (dev->opts)
                                                        free(dev->opts);
                                                free(dev);
                                                goto cleanup;
                                        }
                                        if (head) {
                                                tail->next = dev;
                                                tail = dev;
                                        }
                                        else
                                                head = tail = dev;
                                } while (optind < argc && *(optarg = argv[optind]) != '-');
                                /*
                                 * Now we only support one dll
                                 */
                                goto start_spi;
                                break;
                }
        }
start_spi:
        if (head) {
                /* background the process */
                procmgr_daemon(0, PROCMGR_DAEMON_NOCLOSE | PROCMGR_DAEMON_NODEVNULL);
                sigemptyset(&set);
                sigaddset(&set, SIGTERM);
                for (;;) {
                        if (SignalWaitinfo(&set, &info) == -1)
                                continue;
                        if (info.si_signo == SIGTERM)
                                break;
                }
        }
cleanup:
        dev=head;
        while (dev) {
                if (dev->ctp) {
                        dispatch_unblock(dev->ctp);
                }
                if (dev->drvhdl) {
                        resmgr_detach(dev->dpp, dev->id, _RESMGR_DETACH_ALL);
                        dev->funcs->fini(dev->drvhdl);
                }
                if (dev->dpp) {
                        dispatch_destroy(dev->dpp);
                }
                head = dev->next;
                if (dev->opts)
                        free(dev->opts);
                free(dev);
                dev=head;
        }
        dlclose(dlhdl);
        return (EOK);
}

進入main後,執行ThreadCtl(_NTO_TCTL_IO, 0)函數,該函數使能超級鎖定進程的內存和請求I/O特權;讓線程在具有適當特權的架構上執行in、in、out、out、cli和sti I/O操作碼,並讓它附加IRQ處理程序。很多操作都需要進行寄存器操作,需要採用out32 in32接口等。 調用 _spi_init_iofunc(); 初始化連接函數,通過 iofunc_func_init()函數初始化,通過連接和POSIX默認IO結構層功能。有關默認函數的信息。

int _spi_init_iofunc(void)
{
    iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &_spi_connect_funcs, _RESMGR_IO_NFUNCS, &_spi_io_funcs);
    _spi_io_funcs.read      = _spi_read;
    _spi_io_funcs.write     = _spi_write;
    _spi_io_funcs.devctl    = _spi_devctl;
    _spi_io_funcs.close_ocb = _spi_close_ocb;
    _spi_io_funcs.msg       = _spi_iomsg;

    return EOK;
}

完成後,開始解析參數命令,也就是前面提到的spi-master後的參數-u0 -d omap4430 base=0x4809A000,bitrate=125000,clock=48000000,irq=66,force=1,channel=2,sdma=1,其中u表示設備號,定義爲spi0,-d表示加載的驅動名稱鏈接庫,調用 _spi_dlload(&dlhdl, optarg)函數,加載動態庫,而寄存器基地址和中斷號,不在這個參數裏設置,後面在敘述。

void *_spi_dlload(void **hdl, const char *optarg)
{
        char            dllpath[_POSIX_PATH_MAX + 1];
        void            *dlhdl, *entry;

        if (strchr(optarg, '/') != NULL)
                strcpy(dllpath, optarg);
        else
                sprintf(dllpath, "spi-%s.so", optarg);

        dlhdl = dlopen(dllpath, 0);
        // 獲取動態鏈接庫spi-omap4430 裏的spi_drv_entry函數符號表
        if (dlhdl != NULL) {
                entry = dlsym(dlhdl, "spi_drv_entry");

                if (entry != NULL) {
                        *hdl = dlhdl;
                        return entry;
                }

                dlclose(dlhdl);
        }

        return NULL;
}

_spi_dlload調用dlsym函數,找到動態鏈接庫內的匹配函數符號表名稱spi_drv_entry。這個函數作爲SPI底層驅動入口加載。在主函數參數命令裏,調用 dev->funcs = (spi_funcs_t *)drventry;和 i = _spi_create_instance(dev);實例化驅動。最終完成系列的初始化過程,進入循環。

第二步:建立一個上下文切換句柄

主要從_spi_create_instance(dev)函數開始,_spi_create_instance初始化線程參數,創建一個線程任務,這個線程函數爲_spi_driver_thread。

int _spi_create_instance(spi_dev_t *dev)
{
        pthread_attr_t          pattr;
        struct sched_param      param;

        if (NULL == (dev->dpp = dispatch_create())) {
                perror("dispatch_create() failed");
                goto failed0;
        }

        pthread_attr_init(&pattr);
        pthread_attr_setschedpolicy(&pattr, SCHED_RR);
        param.sched_priority = 21;
        pthread_attr_setschedparam(&pattr, &param);
        pthread_attr_setinheritsched(&pattr, PTHREAD_EXPLICIT_SCHED);

        // Create thread for this interface
        if (pthread_create(NULL, &pattr, (void *)_spi_driver_thread, dev) != EOK) {
                perror("pthread_create() failed");
                goto failed1;
        }

        return (EOK);

failed1:
        dispatch_destroy(dev->dpp);
failed0:

        return (-1);
}

_spi_driver_thread調用_spi_register_interface後進入主線程任務。

static void* _spi_driver_thread(void *data)
{
	spi_dev_t	*dev = data;

	if (_spi_register_interface(data) != EOK)
		return NULL;

	while (1) {
		if ((dev->ctp = dispatch_block(dev->ctp)) != NULL)
			dispatch_handler(dev->ctp);
		else
			break;
	}

	return NULL;
}

第三,四,五步: iofunc初始化, 註冊設備名與創建的上下文句柄分配空間

其中_spi_register_interface在dev目錄下創建一個設備節點和初始化SPI驅動。

spi_funcs_t spi_drv_entry = {
	sizeof(spi_funcs_t),
	omap4430_init,		/* init() */
	omap4430_dinit,		/* fini() */
	omap4430_drvinfo,	/* drvinfo() */
	omap4430_devinfo,	/* devinfo() */
	omap4430_setcfg,	/* setcfg() */
	omap4430_xfer,		/* xfer() */
	omap4430_dmaxfer	/* dma_xfer() */
};
static int _spi_register_interface(void *data)
{
        spi_dev_t               *dev = data;
        SPIDEV                  *drvhdl;
        resmgr_attr_t   rattr;
        char                    devname[PATH_MAX + 1];

        if ((drvhdl = dev->funcs->init(dev, dev->opts)) == NULL) {
                free(dev->opts);
                dev->opts = NULL;
                return (!EOK);
        }

        dev->drvhdl = drvhdl;

        /* set up i/o handler functions */
        /* 資源管理器本身的一些參數,下面這個就是指定了資源管理器最多一次可以處理SPI_RESMGR_NPARTS_MIN個 iov_t */
        memset(&rattr, 0, sizeof(rattr));
        rattr.nparts_max   = SPI_RESMGR_NPARTS_MIN;
        rattr.msg_max_size = SPI_RESMGR_MSGSIZE_MIN;
        /* io_attr 其實可以想像成一個文件相關的參數,比如讀寫權限等等 */
        iofunc_attr_init(&drvhdl->attr, S_IFCHR | devperm, NULL, NULL);
        drvhdl->attr.mount = &_spi_mount;

        /* register device name */
        /* 建立起資源管理層,同時註冊路徑 */
        snprintf(devname, PATH_MAX, "/dev/spi%d", dev->devnum);
        if (-1 == (dev->id = resmgr_attach(dev->dpp, &rattr, devname, _FTYPE_ANY, 0,
                                        &_spi_connect_funcs, &_spi_io_funcs, (void *)drvhdl))) {
                perror("resmgr_attach() failed");
                goto failed1;
        }

        resmgr_devino(dev->id, &drvhdl->attr.mount->dev, &drvhdl->attr.inode);
        // /* 準備一個資源管理層的 context 以備使用 */
        if ((dev->ctp = dispatch_context_alloc(dev->dpp)) != NULL)
                return (EOK);

        perror("dispatch_context_alloc() failed");

        resmgr_detach(dev->dpp, dev->id, _RESMGR_DETACH_ALL);
failed1:
        dev->funcs->fini(drvhdl);

        return (!EOK);
}

iofunc初始化

調用 dev->funcs->init(dev, dev->opts),這個函數指針在spi_drv_entry結構體中,在主函數裏通過鏈接庫完成指針賦值,spi_drv_entry裏實現真正的SPI操作。包括初始化、配置、設備信息獲取以及數據傳輸等等操作。

if ((drvhdl = dev->funcs->init(dev, dev->opts)) == NULL) {
    free(dev->opts);
    dev->opts = NULL;
    return (!EOK);
}

dev->drvhdl = drvhdl;

/* set up i/o handler functions */
/* 資源管理器本身的一些參數,下面這個就是指定了資源管理器最多一次可以處理SPI_RESMGR_NPARTS_MIN個 iov_t */
memset(&rattr, 0, sizeof(rattr));
rattr.nparts_max   = SPI_RESMGR_NPARTS_MIN;
rattr.msg_max_size = SPI_RESMGR_MSGSIZE_MIN;
/* io_attr 其實可以想像成一個文件相關的參數,比如讀寫權限等等 */
iofunc_attr_init(&drvhdl->attr, S_IFCHR | devperm, NULL, NULL);

註冊設備名

通過resmgr_attach,註冊一個資源設備名爲“/dev/spi0”。

/* register device name */
/* 建立起資源管理層,同時註冊路徑 */
snprintf(devname, PATH_MAX, "/dev/spi%d", dev->devnum);
if (-1 == (dev->id = resmgr_attach(dev->dpp, &rattr, devname, _FTYPE_ANY, 0,
                                   &_spi_connect_funcs, &_spi_io_funcs, (void *)drvhdl))) {
    perror("resmgr_attach() failed");
    goto failed1;
}

爲之前創建的上下文句柄分配空間

// /* 準備一個資源管理層的 context 以備使用 */
if ((dev->ctp = dispatch_context_alloc(dev->dpp)) != NULL)
    return (EOK);

第六步:通過不斷循環等待dispatch_block()與dispatch_handler (ctp)執行IO函數處理。

我們基本可以認爲SPI驅動是用iofunc層+resmgr層已經可以構建一個完整的資源管理器。該資源管理器通過dispatch處理其他形態的信息。

在使用 dispatch時,進行特殊的 *_attach() 掛接以後,只要把resmgr層的幾個函數替換成dispath層的幾個函數 就可以了,比如這樣:

ctp = dispatch_context_alloc(dispatch);
while (1)
{
    ctp = dispatch_block(ctp);
    dispatch_handler(ctp);
}

dispath_block() 相當於阻塞並等待,而 dispatch_handle() 則根據不同的掛接,調用不同的回調函數進行處理。其實在_spi_register_interface裏進行了dispatch_context_alloc的操作。通過不斷循環等待dispatch_block()來調用MsgReceive()使Resource manger處於receive block狀態,以接收上層發送來的消息,通過dispatch_handler (ctp)去調用我們自己定義的IO函數。

main主進程後臺處理

到main函數最後將進程spi-master進程放在後臺處理。

start_spi:
        if (head) {
                /* background the process */
                /* 調用procmgr_daemon函數,置PROCMGR_DAEMON_NOCLOSE | PROCMGR_DAEMON_NODEVNULL標誌,把該進程運行於後臺。 
                */
                procmgr_daemon(0, PROCMGR_DAEMON_NOCLOSE | PROCMGR_DAEMON_NODEVNULL);
                /* 初始化一個不包含任何信號的集合 */
                sigemptyset(&set);
                /* 設置SIGTERM信號。是一個程序結束(terminate)信號, 與SIGKILL不同的是該信號可以被阻塞和 
                   處理. 通常用來要求程序自己正常退出. shell命令kill缺省產生這個信號。 */
                sigaddset(&set, SIGTERM);
                for (;;) {
                        /* 內核調用從set指定的集合中選擇掛起信號。如果在調用時集合中沒有掛起信號,線程將阻塞,
                           直到集合中的一個或多個信號成爲掛起信號,或者直到被未阻塞的捕獲信號中斷。在這裏主要
                           是捕獲SIGTERM信號,當收到該信號,退出該驅動。 */
                        if (SignalWaitinfo(&set, &info) == -1)
                                continue;
                        if (info.si_signo == SIGTERM)
                                break;
                }
        }

參考文獻:

QNX驅動開發——Resource manger framework
QNX資源管理器——知乎
QNX---SPI驅動分析
Technical Notes SPI (Serial Peripheral Interface) Framework (qnx.com)
Writing a Resource Manager

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章