花了半年时间在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_ */