花了半年時間在QNX系統上,這是一個RTOS,這個系統是高安全級別的系統,在核物理站/天文空間站/電站/地鐵/交通運輸(飛機/汽車/地鐵)等工業系統領域占有70%以上的市場份額。

背景:本文將我個人在QNX上移植內核和開發驅動以及應用程序的部分經驗記錄在此,因公司商業機密,部分源碼不便公開。我會框架性的講解開發思路。為了簡化文章復雜性,我只討論相同板子的平臺驅動轉移,我手中是at91sam9260-ek的板子。外部設備是公司硬件部單獨添加的。
目的:利用已有的Linux驅動,簡化QNX的驅動編寫。
大致思路:Linux的驅動是基于模塊的,每個驅動作為內核的擴展放進內存中,QNX的驅動就是一個進程,需要將模塊改為一個應用程序。調整內核與驅動的通信機制。

以RTC驅動為例子:
Linux的驅動開發框架為
1.填充read/write/ioctl/probe等驅動回調函數,設置對應的接口。
static struct file_operations rtc8025_fops = {
    .owner   = THIS_MODULE,
    .open    = rtc_open,
    .release = rtc_release,
    .read    = rtc_read,
    .write   = rtc_write,
    .ioctl   = rtc_ioctl,
};
2.編寫每個部分的硬件相關代碼。以write為例:
static ssize_t rtc_write(struct file *filp, __user const char  *buf,
                         size_t len,loff_t *ppos){
    char buff[16];
    VR_TIME *tm;

    tm = kmalloc(sizeof(VR_TIME),GFP_KERNEL);
    if ( NULL == tm ){
        printk("Memory error!\n");
        return -1;
    }
    
    if ( copy_from_user(buff, buf, len) )
        return -EFAULT;
    buff[len] = '\0';
    
    set_time_value(tm, buff);
    set_sys_time(tm);
    kfree(tm);

    return len;
}
3.注冊設備的initial接口函數與exit接口函數。
static __init int rtc8025_init(void){
    int ret;

    printk("%s Driver Version: %s\n", DRIVER_NAME, VERSION);
    ret = register_chrdev(major,DRIVER_NAME,&rtc8025_fops);
    if ( ret < 0 ){
        printk("unable to register %s\n",DRIVER_NAME);
        return ret;
    }

    init_gpio();
    msleep(10);
    init_rtc();

    return 0;
}

static __exit void rtc8025_exit(void){
    i2c_stime(0xe0, 0x20);
    i2c_stime(0xf0, 0);
    i2c_stime(0x70, 0x00);
    unregister_chrdev(major, DRIVER_NAME);
    printk("%s unregister!\n", DRIVER_NAME);
}

module_init(rtc8025_init);
module_exit(rtc8025_exit);

其他具體實現就不在這里描述了。

為了將原有的Linux驅動移植到QNX上,需要改動read/write以及ioctl的函數接口參數。并將與Linux內核相關的函數去掉(通常不會在QNX里面用到)以read函數代碼為例:

size_t rtc_read(char *buf, size_t len, int *ppos){
    VR_TIME *tm = (VR_TIME *)buf;
    static unsigned char tmp = 0;
    tmp = i2c_rtime(0x00) & 0x7f;     tm->second = BCD2DEC(tmp);
    tmp = i2c_rtime(0x10) & 0x7f;      tm->minute = BCD2DEC(tmp);
    tmp = i2c_rtime(0x20) & 0x3f;      tm->hour   = BCD2DEC(tmp);
    tmp = i2c_rtime(0x40) & 0x3f;      tm->day    = BCD2DEC(tmp);
    tmp = i2c_rtime(0x50) & 0x1f;      tm->month  = BCD2DEC(tmp);
    tmp = i2c_rtime(0x60);             tm->year   = BCD2DEC(tmp)+ 1920;
    return 0;
}

結構體VR_TIME是自定義的。
經過多次驅動的編寫和調整,我發現還是Linux的那種回調機制在移植的時候代碼改動最方便。因此,我自己也定義了一個結構體,用于模仿Linux的驅動框架,整合兩個系統的差異。
struct driver_interface ;
struct driver_interface get_driver_interface(){
    struct driver_interface drv;
    drv.drv_init  = rtc_init;
    drv.drv_read  = rtc_read;
    drv.drv_write = rtc_write;
    drv.drv_ioctl = rtc_ioctl;
    return drv;
}
這個框架差異整合的結構體為:
struct driver_interface{
    void (*drv_init)(void);
    size_t (*drv_read)(char *buf, size_t len, int *ppos);
    size_t (*drv_write)( const char *buf, size_t len, int *ppos);
    int (*drv_ioctl)(unsigned int cmd, unsigned long arg);
};

struct driver_interface get_driver_interface();
然后我們來看看QNX的驅動框架,根據這個框架,我們將自己的drv整合進去,于是便有了下面的代碼:
文件名為:driver_main.c

/*
 *  driver_main.c
 *
 *  This module contains the source code for the /dev/rtc device.
 *
 *  This module contains all of the functions necessary.
 *
 
*/

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <sys/neutrino.h>
#include <sys/resmgr.h>


#include <arm/atmel-at91sam9260.h>
#include "linux2qnx_drv.h"

//#include "MMA8452Q.h"  
#include "rtc.h"


#define EXAMPLE_NAME "/dev/rtc"


struct driver_interface drv;

/* change to something else if sharing a target */
// #define EXAMPLE_NAME "/dev/dagexample"

void options (int argc, char *argv[]);

/*
 *  these prototypes are needed since we are using their names in main ()
 
*/

int io_open (resmgr_context_t *ctp, io_open_t  *msg, RESMGR_HANDLE_T *handle, void *extra);
int io_read (resmgr_context_t *ctp, io_read_t  *msg, RESMGR_OCB_T *ocb);
int io_write(resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb);
int    io_devctl(resmgr_context_t *ctp, io_devctl_t *msg, RESMGR_OCB_T *ocb);

/*
 *  our connect and I/O functions
 
*/

resmgr_connect_funcs_t  connect_funcs;
resmgr_io_funcs_t       io_funcs;

/*
 *  our dispatch, resource manager and iofunc variables
 
*/

dispatch_t              *dpp;
resmgr_attr_t           rattr;
dispatch_context_t      *ctp;
iofunc_attr_t           ioattr;

char    *progname = "rtc_driver";
int     optv;                               // -v for verbose operation

int main (int argc, char *argv[]){
    printf ("%s:  starting\n", progname);
    int pathID;
    options (argc, argv);

    drv = get_driver_interface();
    drv.drv_init();

    /*
     * allocate and initialize a dispatch structure for use by our
     * main loop
     
*/
    dpp = dispatch_create ();
    if (dpp == NULL) {
        fprintf (stderr, "%s:  couldn't dispatch_create: %s\n",
                 progname, strerror (errno));
        exit (1);
    }
    /*
     * set up the resource manager attributes structure, we'll
     * use this as a way of passing information to resmgr_attach().
     * For now, we just use defaults.
     
*/
    memset (&rattr, 0, sizeof (rattr)); /* using the defaults for rattr */
    /*
     * initialize the connect functions and I/O functions tables to
     * their defaults by calling iofunc_func_init().
     *
     * connect_funcs, and io_funcs variables are already declared.
     *
     
*/
    iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &connect_funcs,
                      _RESMGR_IO_NFUNCS, &io_funcs);
    /* over-ride the connect_funcs handler for open with our io_open,
     * and over-ride the io_funcs handlers for read and write with our
     * io_read and io_write handlers
     
*/
    connect_funcs.open = io_open;
    io_funcs.read = io_read;
    io_funcs.write = io_write;
    io_funcs.devctl = io_devctl;
    /* initialize our device description structure
     
*/
    iofunc_attr_init (&ioattr, S_IFCHR | 0666, NULL, NULL);
    /*
     *  call resmgr_attach to register our prefix with the
     *  process manager, and also to let it know about our connect
     *  and I/O functions.
     *
     *  On error, returns -1 and errno is set.
     
*/
    pathID = resmgr_attach (dpp, &rattr, EXAMPLE_NAME, _FTYPE_ANY, 0,
                            &connect_funcs, &io_funcs, &ioattr);
    if (pathID == -1) {
        fprintf (stderr, "%s:  couldn't attach pathname: %s\n",
                 progname, strerror (errno));
        exit (1);
    }

    ctp = dispatch_context_alloc (dpp);
    while (1) {
        if ((ctp = dispatch_block (ctp)) == NULL) {
            fprintf (stderr, "%s:  dispatch_block failed: %s\n",
                     progname, strerror (errno));
            exit (1);
        }
        dispatch_handler (ctp);
    }
}

/*
 *  io_open
 *
 *  we are called here when the client does an open.
 *  It is up to us to establish a context (in this
 *  case NULL will do just fine), and return a status
 *  code.
 
*/
int io_open (resmgr_context_t *ctp, io_open_t *msg,
             RESMGR_HANDLE_T *handle, void *extra){
    if (optv) {
        printf ("%s:  in io_open\n", progname);
    }

    return (iofunc_open_default (ctp, msg, handle, extra));
}

/*
 *  io_read
 *
 *  At this point, the client has called their library "read"
 *  function, and expects zero or more bytes.  Currently our
 *  /dev/example resource manager returns zero bytes to
 *  indicate EOF -- no more bytes expected.
 *
 *  After our exercises, it will return some data.
 
*/
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb){
    int status;
    int nb = sizeof(VR_TIME);
    static VR_TIME data;

    if (optv) {
        printf ("%s:  in io_read\n", progname);
    }

    if ((status = iofunc_read_verify(ctp, msg, ocb, NULL)) != EOK) {
        if (optv) printf("read failed because of error %d\n", status );
        return status;
    }

    // No special xtypes
    if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE) {
        return ENOSYS;   // causes MsgError( ctp->rcvid, ENOSYS );
    }

    drv.drv_read((char*)&data, sizeof(data), 0);

    nb = min( nb, msg->i.nbytes );

    _IO_SET_READ_NBYTES (ctp, nb); // ctp->status = nb
    SETIOV( ctp->iov, &data, nb );

    if (nb > 0)
        ocb->attr->flags |= IOFUNC_ATTR_ATIME;

    return _RESMGR_NPARTS (1); // causes MsgReplyv( ctp->rcvid, ctp->status, ctp->iov, 1 );
}

/*
 *  io_write
 *
 *  At this point, the client has called their library "write"
 *  function, and expects that our resource manager will write
 *  the number of bytes that they have specified to some device.
 *
 *  Currently, for /dev/example, all of the clients writes always
 *  work -- they just go into Deep Outer Space.
 *
 *  After our updates, they will be displayed on standard out.
 
*/
int io_write (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb){
    int status;
    int nb;
    VR_TIME *buf;

    if (optv) {
        printf ("\n\n\n%s:  in io_write, of %d bytes\n\n\n", progname, msg->i.nbytes);
    }

    if ((status = iofunc_write_verify(ctp, msg, ocb, NULL)) != EOK)
        return status;

    // No special xtypes
    if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE) {
        return ENOSYS;
    }

    /* first process any data already in the receive buff */

    // skip the io_write header to get to the data
    buf = (VR_TIME *)(msg+1);
    // calculate number of bytes of client data in receive buffer
    nb = ctp->info.msglen - (ctp->offset + sizeof(*msg) );
    status = drv.drv_write( (char*)buf, sizeof(*buf), 0 );

    _IO_SET_WRITE_NBYTES (ctp, nb);

    // if we actually handled any data, mark that a write was done for
    
// time updates (POSIX stuff)
    if (nb > 0)
        ocb->attr->flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_CTIME;

    return _RESMGR_NPARTS (0);
}

int    io_devctl(resmgr_context_t *ctp, io_devctl_t *msg, RESMGR_OCB_T *ocb){
    int     nbytes, status;

    union {  /* See note 1 */
        XYZunion8  arg_info_data;
        int     data32;
        /*  other devctl types you can receive */
    } *rx_data;

    /*
     Let common code handle DCMD_ALL_* cases.
     You can do this before or after you intercept devctls, depending
     on your intentions.  Here we aren't using any predefined values,
     so let the system ones be handled first. See note 2.
    
*/
    if ((status = iofunc_devctl_default(ctp, msg, ocb)) !=
         _RESMGR_DEFAULT) {
        return(status);
    }
    status = nbytes = 0;

    /*
     Note this assumes that you can fit the entire data portion of
     the devctl into one message.  In reality you should probably
     perform a MsgReadv() once you know the type of message you
     have received to get all of the data, rather than assume
     it all fits in the message.  We have set in our main routine
     that we'll accept a total message size of up to 2 KB, so we
     don't worry about it in this example where we deal with ints.
    
*/

    /* Get the data from the message. See Note 3. */
    rx_data = _DEVCTL_DATA(msg->i);
    /*
     Three examples of devctl operations:
     SET: Set a value (int) in the server
     GET: Get a value (int) from the server
     SETGET: Set a new value and return the previous value
    
*/
    _Int32t linux_cmd = (msg->i.dcmd);

    if((linux_cmd & SETVAL) && ((GETVAL & linux_cmd))){  // SETGET is SETVAL|GETVAL == 0x800000000|0x40000000
        linux_cmd &= ~SETGET;
        linux_cmd -= (sizeof(XYZunion8)<<16);
        //printf("SG = 0x%0x", SETGET);

        drv.drv_ioctl(linux_cmd, (unsigned long)&(rx_data->arg_info_data) );
        nbytes = sizeof( rx_data->arg_info_data );

    }else if(linux_cmd & GETVAL){
        linux_cmd &= ~GETVAL;
        linux_cmd -= (sizeof(int)<<16);
        printf("G = 0x%x", GETVAL);
        printf(", linux_cmd=%d, data address =%d\n", linux_cmd, (int)&(rx_data->data32));
        drv.drv_ioctl(linux_cmd, (unsigned long )&(rx_data->data32));
        nbytes = sizeof(&rx_data->data32);

    }else if(linux_cmd & SETVAL ){
        linux_cmd &= ~SETVAL;
        linux_cmd -= (sizeof(int)<<16);
        printf("S = 0x%x", SETVAL);
        printf(", linux_cmd=%d, data=%d\n", linux_cmd, rx_data->data32);

        drv.drv_ioctl(linux_cmd, (int)(rx_data->data32) );
    }else{
        printf("undefined functionality\n");
        //TODO
         return -1;
    }

    /* Clear the return message. Note that we saved our data past
       this location in the message. 
*/
    memset(&msg->o, 0, sizeof(msg->o));
    /*
     If you wanted to pass something different to the return
     field of the devctl() you could do it through this member.
     See note 5.
    
*/
    msg->o.ret_val = status;

    /* Indicate the number of bytes and return the message */
    msg->o.nbytes = nbytes;
    return(_RESMGR_PTR(ctp, &msg->o, sizeof(msg->o) + nbytes));
}

/*
 *  options
 *
 *  This routine handles the command line options.
 *  For our simple /dev/example, we support:
 *      -v      verbose operation
 
*/
void options (int argc, char *argv[]){
    optv = 0;
    int     opt;

    while ((opt = getopt (argc, argv, "v")) != -1) {
        if( opt == 'v' ){
            optv++;
        }
    }
}
這樣,我們的Linux驅動就能基本原封不動的移植到QNX里面。
這是rtc.h:

#include <devctl.h>
#include "MMA8452Q.h"
#ifndef RTC_COMMON_H_
#define RTC_COMMON_H_

#define GETVAL _POSIX_DEVDIR_FROM
#define SETVAL _POSIX_DEVDIR_TO
#define SETGET _POSIX_DEVDIR_TOFROM

typedef struct tagVRTIME{
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
}VR_TIME;

#endif /* RTC_COMMON_H_ */
由于QNX使用了微內核架構,而驅動是一個進程,因此使用的進程間通信,對于ioctl要做特別處理,read和write可以直接使用,但是ioctl目前沒有用到,在我的驅動中,后面加入了加速計傳感器的驅動,需要ioctl進行擴展操作,但我最終使用QNX自己的devctl函數,看一下這個函數的使用代碼。

#define GETVAL _POSIX_DEVDIR_FROM
#define SETVAL _POSIX_DEVDIR_TO
#define SETGET _POSIX_DEVDIR_TOFROM

static int  inline DEVCTL_CMD( int code){
    if((code & _POSIX_DEVDIR_TO) && (code & _POSIX_DEVDIR_FROM))
        return (sizeof(XYZunion8)<<16)+code;
    if(code & _POSIX_DEVDIR_TO)
        return (sizeof(int)<<16) + code;
    if(code & _POSIX_DEVDIR_FROM)
        return (sizeof(int)<<16) + code;
    return    code;
}

static void get_xyz( int *x, int *y, int *z ){
    XYZunion8  _XYZdata8;
    memset( &_XYZdata8, 0, sizeof( _XYZdata8 ) );
    devctl( fd, DEVCTL_CMD(SETGET|CMD_READ_XYZ8), &_XYZdata8, sizeof(_XYZdata8), NULL);
    
    *x = ( int )_XYZdata8.Byte.Xdata8;
    *y = ( int )_XYZdata8.Byte.Ydata8;
    *z = ( int )_XYZdata8.Byte.Zdata8;

    //printf( "x=%4d, y=%4d, z=%4d\n", *x, *y, *z);
}

這樣,就可以在QNX的應用程序中和驅動進行通信了。

最后附加上額外的兩個源文件,叫做linux2qnx.c/linux2qnx.h用以整合兩邊對純硬件的操作(地址映射與寄存器操作)的差異。
Linux2qnx.c:
/*
 * linux2qnx_drv.c
 *
 *  Created on: Feb 18, 2013
 *      Author: mark
 
*/
#include <arm/inout.h>

#include "linux2qnx_drv.h"

uintptr_t ioremap(uint64_t io, size_t len){
    return mmap_device_io(len, io);
}

void iowrite16(  uint16_t val, uintptr_t port ){
    out16(port, val);
}

uint_t ioread16(uintptr_t port){
    return in16(port);
}

void ssleep(unsigned int  seconds){
    sleep(seconds);
}

void iowrite32(uint32_t val, uintptr_t port){
    out32(port, val);
}

uint_t ioread32(uintptr_t port){
    return in32(port);
}
linux2qnx.h:
/*
 * linux2qnx_drv.h
 *
 *  Created on: Feb 18, 2013
 *      Author: mark
 
*/
#include <stddef.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ioctl.h>
#include <fcntl.h>
#include <devctl.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <sys/neutrino.h>
#include <sys/resmgr.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>


#include <arm/atmel-at91sam9260.h>

#ifndef LINUX2QNX_DRV_H_
#define LINUX2QNX_DRV_H_

#define GETVAL _POSIX_DEVDIR_FROM
#define SETVAL _POSIX_DEVDIR_TO


uintptr_t ioremap(uint64_t io, size_t len);
void iowrite16(  uint16_t val, uintptr_t port );
uint_t ioread16(uintptr_t port);
uint_t ioread32(uintptr_t port);
void iowrite32(uint32_t val, uintptr_t port);
void ssleep(unsigned int  seconds);

struct driver_interface{
    void (*drv_init)(void);
    size_t (*drv_read)(char *buf, size_t len, int *ppos);
    size_t (*drv_write)( const char *buf, size_t len, int *ppos);
    int (*drv_ioctl)(unsigned int cmd, unsigned long arg);
};

struct driver_interface get_driver_interface();

#endif /* LINUX2QNX_DRV_H_ */