ibm aix_IBM AIX设备驱动程序开发

在传统的UNIX®中,术语“ 设备”指的是硬件组件,例如磁盘驱动器,磁带驱动器,打印机,键盘,伪设备(例如控制台,错误特殊文件和null特殊文件)等等。 在AIX中,这些设备称为内核设备,它们具有设备驱动程序,并且由系统的主要编号和次要编号知道。

AIX设备驱动程序是AIX内核扩展的一种。 内核扩展在内核的受保护域内运行。 它们可以在系统启动或运行时加载到内核中,并在运行时删除。 用户级代码只能通过系统调用来访问注册的设备驱动程序代码。 设备驱动程序为AIX添加了可扩展性,可配置性和易于系统管理的功能。

要了解基本的内核扩展开发,请参阅IBM developerWorks文章编写AIX内核扩展

设备驱动程序通过参考/usr/lib/kernex.exp使用AIX内核“ / unix”导出的核心内核服务。 “ kernex.exp”包含内核导出的符号列表。 这些导出的符号本质上是内核功能和存储位置(内核全局数据结构)。 链接器程序( ld )在链接已编译的设备驱动程序代码时使用此信息。

设备类型

  • 字符
  • 网络
  • 多重字符设备

设备表示为/ dev目录下列出的特殊文件类型。

这些文件中没有任何可打印的内容。 相反,它们为用户提供了与相关设备进行交互的界面。 这些文件的inode包括主要和次要编号。 这些文件是使用mknod()系统调用创建的。

对象数据管理器(ODM)

ODM维护以对象类形式表示的数据。 它用于存储设备特定的配置信息。 它具有预定义的数据库,用于存储所有设备的配置数据。 它还具有针对系统中当前正在运行的设备实例的自定义数据库。 ODM需要在使用之前进行初始化。 打算配置设备驱动程序的应用程序必须首先初始化ODM。

应用程序使用/usr/include/odmi.h中定义的odm_initialize() API初始化ODM。

内核扩展配置基础

struct cfg_load的组件和使用

加载内核扩展的结构:

struct cfg_load
{
   caddr_t path; /* ptr to object module pathname */
   caddr_t libpath; /* ptr to a substitute libpath */
   mid_t   kmid;    /* kernel module id (returned) */
};

struct cfg_dd的组件和使用

调用设备驱动程序的config(模块)入口点的结构:

struct cfg_dd
{
  mid_t   kmid;/* Module ID of device driver */
  dev_t   devno; /* Device major/minor number */
  int     cmd; /* Config command code for device driver*/
  caddr_t ddsptr; /* Pointer to device dependent structure*/
  int     ddslen; /* Length of device dependent structure */
};

Sysconfig()系统调用

Sysconfig()需要三个参数。

int sysconfig(             
   int cmd, /* Command to be executed */
   void *parmp,/* Address of structure containing info for cmd */
   int parmlen /* Length of parmp information */
 );

Sysconfig()用于控制内核扩展的生命周期。 如/usr/include/sys/sysconfig.h中所述,以下命令作为cmd参数传递到sysconfig()

  • SYS_KLOAD:将内核扩展对象文件加载到内核内存中。
  • SYS_SINGLELOAD:仅在尚未加载内核扩展对象文件时加载。
  • SYS_QUERYLOAD:确定是否加载了指定的内核对象文件。
  • SYS_KULOAD:卸载以前加载的内核对象文件。
  • SYS_CFGKMOD:出于配置目的,在其模块入口点调用指定的模块。

设备驱动程序基础

主要和次要数字

设备在/ dev目录下被视为特殊文件。 因此,每个设备在其根文件系统下都有一个名称以及一个关联的索引节点或索引节点。 每个设备的文件系统入口点都包含主要和次要编号。

主号码用于在系统中唯一找到该设备。 它是设备切换表的索引。 设备切换表包含指向特定于设备的方法的指针,这些方法实质上是通用文件系统功能的实现。 以这种方式,由用户进程在给定设备上发出的文件系统调用被解析为对适当设备驱动程序功能的调用。 驱动程序在内部使用一个较小的数字来区分多路复用设备的逻辑通道。

可以通过库调用genmajor()获得一个主号码。

genmajor()语法是:

int genmajor(char* name_of_device_driver)

通过库调用genminor()可以获得次要数字。

它会生成可用于设备的最小未使用的次要编号,如果可用,则会生成首选的次要编号,或者为设备生成一组未使用的未成年人编号。

genminor()语法为:

int *genminor (
 char * device_instance,
 /*
 Points to a character string containing the device instance name.*/
 int major_no,
 /*
  The major number of the device instance.*/
 int preferred_minor,
 /*
  Contains a single preferred minor number or a starting  
minor number for generating a set of numbers. */
 int minors_in_grp,
 /*
 Indicates how many minor numbers are to be allocated.	*/
 int inc_within_grp,
 /*
 Indicates the interval between minor numbers.*/
 int inc_btwn_grp
 /*
 Indicates the interval between groups of minor numbers.*/
);

makedev()API

给定设备的主要和次要编号必须打包为32位或64位整数,具体取决于体系结构。 该整数的数据类型为dev_tmakedev()是一个宏,它根据给定的主数字和副数字创建dev_t 。 它在<sys/sysmacros.h>下定义。 makedev64()用于为64位环境创建dev_t

makedev()makedev64()语法为:

dev_t makedev64(int major, int minor);

mknod()系统调用

它在<sys/stat.h>定义。 它用于创建普通文件,先进先出(FIFO)或特殊文件。 它要求具有root特权才能使用mknod()创建设备专用文件。

mknod()的语法为:

int mknod (
  char *Path,
  /*
   Names the new device special file.
  */
  int Mode,
  /*
   Specifies the file type, attributes, and access
   permissions.
  */
  dev_t Device
  /*
   Device number generated by makedev() subroutine.
  */
 );

设备切换表

设备切换表是struct devsw结构的数组。 它由主索引编制索引并固定在内存中(也就是说,它将永远不会被交换出RAM)。 块和字符设备使用此内核结构在根文件系统中注册自己。

设备切换表的条目结构:

struct devsw
{
 int (*d_open)(); /* entry point for open routine */
 int (*d_close)(); /* entry point for close routine */
 int (*d_read)();/* entry point for read routine */
 int (*d_write)(); /* entry point for write routine */
 int (*d_ioctl)();/* entry point for ioctl routine */
 int (*d_strategy)();/* entry point for strategy routine */
 struct tty  *d_ttys;/* pointer to tty device structure */
 int (*d_select)();  /* entry point for select routine */
 int (*d_config)();  /* entry point for config routine */
 int (*d_print)(); /* entry point for print routine */
 int (*d_dump)(); /* entry point for dump routine */
 int (*d_mpx)(); /* entry point for mpx routine */
 int (*d_revoke)(); /* entry point for revoke routine */
 caddr_t d_dsdptr; /* pointer to device specific data */
 /*
  * The following entries are control fields managed
  * by the kernel and should not be modified by device
  * drivers or kernel extensions.  They may be set to
  * 0 in the devsw structure for devswadd(), since they
  * are not copied into the device switch table.
  */
 caddr_t d_selptr;/* ptr to outstanding select cntl blks*/
 ulong   d_opts;/* internal device switch control field */
};

驾驶员入境点

驱动程序入口点不过是struct devsw结构的成员。 在将设备添加到设备切换表之前,必须初始化所有这些成员。 设备驱动程序不必强制实现所有方法。 可以将未实现的成员初始化为nodev 。 这些入口点可以接受dev_no (该操作指向的设备或子设备的设备编号), chan (多路复用设备的通道ID), ext (整数,对调用扩展子例程,例如openxreadxwritexioctlx ,它们将额外的特定于设备的参数传递给少数设备入口点。)

清单1. 1)ddconfig或d_config:
int d_config(dev_t dev_no, int cmd, struct uio *uiop)

它由sysconfig()系统调用调用。 它为第一个open()调用准备设备。 它可以初始化,终止,请求设备的配置数据或执行设备特定的配置功能。 uio结构包含用于配置信息的数据区域。

清单2. 2)ddopen或d_open:
int d_open(dev_t dev_no, ulong flag, chan_t chan, ext_t ext)

它支持设备操作并为数据传输做准备。 它根据当前设备状态分配内部缓冲区并强制执行有关如何打开设备的策略。 这由open()create ()系统调用以及fp_open()fp_opendev()内核服务调用。 输入参数标志指定打开文件控制标志,例如DREADDWRITE等等。

清单3. 3)ddclose或d_close:
int d_close(dev_t dev_no, chan_t chan)

它关闭以前打开的设备实例。 它是由close ()系统调用或fp_close()内核服务调用的。 即使d_close()返回给调用方,即使返回了非零返回码,设备实例也被视为已关闭。

清单4. 4)ddread或d_read:
int d_read(dev_t devno, struct uio *uiop, chan_t chan, int ext)

它从字符设备读取数据。 它由系统调用(例如read ()或readx ()和fp_rwuio()内核服务fp_rwuio() 。 这里, uio结构描述了要写入的一个或多个数据区域。

清单5. 5)ddwrite或d_write:
int d_write (dev_t devno, struct uio *uiop, chan_t chan, int ext)

它将数据写到字符设备。 它由系统调用(例如write ()或writex ())以及fp_rwuio()内核服务fp_rwuio() 。 在这里, uio结构描述了要写入的一个或多个数据区域。

清单6. 6)ddioctl或d_ioctl:
int d_ioctl(dev_t devno, int cmd, void *arg, ulong devflag, chan_t chan, int ext)

它执行ioctl ()或ioctlx ()系统调用或fp_ioctl()内核服务中请求的特殊I / O控制操作。 它必须响应IOCINFO命令,该命令返回描述设备的devinfo结构。

清单7. 7)ddstrategy或d_strategy:
int d_strategy(struct buf* buffer)

它通过调度对块设备的读取或写入来执行面向块的I / O。 它将I / O请求映射到设备请求,以便使用最少的设备请求实现最大的数据传输。 缓冲区是指向与b_forw指针链接的缓冲区结构的b_forw指针。 ddstrategy例程可以接收具有多个buf结构的单个请求。 但是,不需要以任何特定顺序处理请求。 此例程从不返回返回码,也从不等待I / O完成。

清单8. 8)ddselect或d_select:
int d_select(dev_t devno, ushort events, ushort *reventp,  int chan)

它检查在给定设备上发生的events标志指定的一个或多个事件,并在reventp返回指向发生的事件的reventp 。 它由selectpoll系统调用或fp_select内核服务调用。

清单9. 9)d_mpx或ddmpx:
int d_mpx(dev_t devno, chan_t* chanp, char* channame)

它为多路复用设备分配和取消分配逻辑通道。 在d_open调用之前为设备文件的每个open ()调用一次,以分配通道,在d_close之后为设备文件的每次关闭d_open调用一次。 仅字符类设备驱动程序支持它。 chanp是指向通道ID的指针,而channame是要分配的通道的路径名扩展。

清单10. 10)d_revoke或ddrevoke:
int d_revoke (dev_t devno, chan_t chan, int flag)

对于需要可信计算路径的设备的驱动程序, ddrevoke ()提供了到终端的安全路径。 仅字符类设备驱动程序支持它。 它由revoke()系统调用或frevoke()内核API调用。

清单11. 11)d_dump或dddump:
int d_dump(dev_t devno, struct uio * uiop, int cmd, int arg, chan_t chan, int ext)

它将系统转储数据写入设备。 这是设备驱动程序的可选例程。 仅当设备驱动程序支持设备作为可能的内核转储的目标时才需要。 此例程不得调用任何可能出现页面错误的内核服务。

设备配置例程

清单12. 1)devswadd内核服务:
int devswadd (dev_t devno, struct devsw *dswptr)

这增加了由指向的设备条目dswptr用于通过指定的设备devno到设备转换表。 通常由设备驱动程序的ddconfig ()例程调用。

清单13. 2)devswdel()内核服务:
int devswdel(dev_t devno)

这将从设备切换表中删除设备驱动程序条目。 ddconfig ()例程调用此ddconfig以终止设备驱动程序。

uio结构的组成和意义

uio结构包含一个内存缓冲区,用于在实现驱动程序例程时在用户空间与内核空间之间交换数据。 uio结构描述了虚拟内存中不连续的缓冲区。 ureadcuwritecuiomoveuphysio内核服务都执行向uio结构描述的数据缓冲区中的数据传输。 uio结构在/usr/include/sys/uio.h文件中定义。

struct uio {
/* ptr to array of iovec structs describing  user buffer for data transfer */
   struct  iovec *uio_iov;
/* ptr to array of xmem structs containing cross memory descriptors for iovec array.*/	
   struct  xmem  *uio_xmem;
/* #iovec elements remaining to be processed*/
   int32long64_t  uio_iovcnt;

/* #iovec elements already processed */
   int32long64_t  uio_iovdcnt;
#ifdef _LONG_LONG
/* byte offset in file/dev to read/write */
   offset_t uio_offset;    
#else /* _LONG_LONG */
#ifdef __64BIT__
/* off_t offset for ANSI-C mode */
   off_t   uio_offset;     
#else
/* ANSI-C does not support long long */
   int     uio_rsvd;
/* off_t offset for ANSI-C mode      */       
   off_t   uio_offset;     
#endif /* __64BIT__ */
#endif  /* _LONG_LONG */
/* Byte count for data transfer  */
   int32long64_t uio_resid;                
/* Type of buffer being described by uio structure. Data pointed by 
  buffer can either be in user or kernel or cross-memory region. */
   short   uio_segflg;
/* copy of file modes from open file structure */    
   long    uio_fmode;      
};

设备驱动程序示例

sample_driver.c

#include <stdio.h>
#include <syslog.h>
#include <sys/types.h> /* for dev_t and other types */
#include <sys/errno.h> /* for errno declarations */
#include <sys/sysconfig.h> /* for sysconfig() */
#include <sys/device.h> /* for devsw */
#include <sys/uio.h> /* for uiomove */
#include <sys/sysmacros.h>

struct dr_data
{
  char buffer[1024];
}dr_data[5];

int dr_open(dev_t devno, ulong devflag, chan_t chan, int ext)
{
   bsdlog(LOG_KERN|LOG_DEBUG,"Inside dr_open\n");
   return 0;
}


int dr_close (dev_t devno, chan_t chan)
{
   bsdlog(LOG_KERN|LOG_DEBUG,"Inside dr_close \n");
   return 0;
}

int dr_read (dev_t devno, struct uio *uiop, chan_t chan, int ext)
{
   uint min;
   int rc;

   min = minor_num(devno);
   rc = uiomove(dr_data[min].buffer, 1024, UIO_READ, uiop);
   bsdlog(LOG_KERN | LOG_DEBUG, "Inside dr_read min: 
   %d, buffer: %s \n", min, dr_data[min].buffer);
   return rc;
}

int dr_write (dev_t devno, struct uio *uiop, chan_t chan, int ext)
{
   uint min;
   int rc;

   min = minor_num(devno);
   rc = uiomove(dr_data[min].buffer, 1024, UIO_WRITE, uiop);
   bsdlog(LOG_KERN | LOG_DEBUG,"Inside dr_write min: 
   %d, buffer: %s \n", min, dr_data[min].buffer);
   return rc;
}

int driverdd_config (dev_t devno, int cmd, struct uio *uiop)
{
   struct devsw dswp;
   int rc = 0;

   switch (cmd)
   {
       case CFG_INIT:
            dswp.d_open     = dr_open;
            dswp.d_close    = dr_close;
            dswp.d_read     = dr_read;
            dswp.d_write    = dr_write;
            dswp.d_ioctl    = nodev;
            dswp.d_strategy = nodev;
            dswp.d_ttys     = NULL;
            dswp.d_select   = nodev;
            dswp.d_config   = driverdd_config;
            dswp.d_print    = nodev;
            dswp.d_dump     = nodev;
            dswp.d_mpx      = nodev;
            dswp.d_revoke   = nodev;
            dswp.d_dsdptr   = NULL;
            dswp.d_selptr   = NULL;
            dswp.d_opts     = DEV_MPSAFE|DEV_64BIT;



            if((rc = devswadd(devno, &dswp)) != 0)
            {
                rc = major_num(devno);
                printf("Error in devswadd: %d\n", rc);
                return rc;
            }
            break;

        case CFG_TERM:
            if((rc = devswdel(devno)) != 0)
            {
                printf("Error in devswdel: %d\n", rc);
                return rc;
            }
            break;
        default:
             printf("Invalid command \n");
            return EINVAL;
    }
    return 0;
}

给定设备驱动程序的配置应用程序示例

Config_mgr.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/sysmacros.h>
#include <sys/sysconfig.h>
#include <sys/device.h>
#include <sys/mode.h>
#include <odmi.h>
#include <cf.h>
#include <sys/cfgodm.h>
#include <sys/cfgdb.h>

int main()
{
   struct cfg_load ext_load; /* to load kernel extension */
   struct cfg_dd ddcfg; /* to invoke driver config() */
   char c, str[80];
   int rc;
   int major, minor = 0;
   dev_t devno;

   rc = odm_initialize();

   printf("\n Enter choice, (l)oad, (u)nload \n");
   while((c = getchar()) < 'a' && c > 'z');

   switch(c) {
      case 'l':
          ext_load.path = "sample_driver";
          ext_load.libpath = NULL;
          ext_load.kmid = 0;

          if(sysconfig(SYS_KLOAD,
          &ext_load, sizeof(struct cfg_load))) {
             printf("Error in loading extension\n");
             exit (1);
          }
          else
             printf("Extension Successfully loaded, kmid is %d\n", ext_load.kmid);

          major = genmajor("sample_driver");
          printf("Major number: %d\n", major);
          devno = makedev64(major, minor);
          ddcfg.kmid = ext_load.kmid;
          ddcfg.devno = devno;
          ddcfg.cmd = CFG_INIT;
          ddcfg.ddsptr = NULL;
          ddcfg.ddslen = 0;

          if (rc = sysconfig(SYS_CFGDD,
             &ddcfg, sizeof(ddcfg))) {
             printf("Error in configuring device %d %d\n", rc, errno);
             exit (1);
          }

          for(minor = 0; minor <=2; minor++) {
             devno = makedev64(major, minor);
             sprintf(str, "/dev/drvdd%d", minor);
             if (mknod(str, 0666 | _S_IFCHR, devno) == -1){
               printf("Error while creating device %s\n", str);
               exit (1);
             }
          }
          break;

    case 'u':
         ext_load.path = " sample_driver";
         ext_load.libpath = NULL;
         ext_load.kmid = 0;

         if(sysconfig(SYS_QUERYLOAD, &ext_load,
         sizeof(struct cfg_load)))
            printf("Error while querying\n");

         if(sysconfig(SYS_KULOAD, &ext_load,
         sizeof(struct cfg_load)))
            printf("Error in unloading extension\n");
         else
            printf("Extension Successfully unloaded\n");
         break;


    default:
         printf("Incorrect option\n");
         break;
 }
        return 0;
}

使用已实现的设备驱动程序的应用程序示例

应用程序

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
  int fd0, fd1;
  char rbuf[1024];

  fd0 = open("/dev/drvdd0", O_RDWR);
  if(fd0 < 0){
    printf("Error in opening /dev/drvdd0\n");
    return -1;
  }
  fd1 = open("/dev/drvdd1", O_RDWR);
  if(fd1 < 0){
    printf("Error in opening /dev/drvdd1\n");
    return -1;
  }
  write(fd0, "Hello", 5);
  write(fd1, "World", 5);

  read(fd0, rbuf, 5);
  printf("Read from /dev/drvdd0 : %s\n",rbuf);

  read(fd1, rbuf, 5);
  printf("Read from /dev/drvdd1 : %s\n",rbuf);
  return 0;
}

生成文件

all: sample_driver config_mgr application

config_mgr: config_mgr.c
          cc -q64 -o config_mgr -g config_mgr.c -lodm -lcfg

application: application.c
	  cc -o application application.c

K_LIBS= -bI:/usr/lib/kernex.exp -lsys -lcsys

sample_driver: sample_driver.c
	  cc -q64 -o sample_driver64.o -c sample_driver.c -D_KERNEL -D_64BIT_KERNEL
	  ld -b64 -o sample_driver sample_driver64.o -e driverdd_config $(K_LIBS)
        
clean:
	  rm -f *.o sample_driver sample_driver32 
        sample_driver64 config_mgr application  2> /dev/null

样本驱动程序的编译和测试

以root用户特权登录到使用AIX 6.1或更高版本的系统。 将上述文件复制到您的开发目录中,然后从shell的命令提示符处运行make命令。

这将构建必要的驱动程序,配置实用程序以及一个依次调用驱动程序API的应用程序。 然后在命令提示符下运行./config_mgr实用程序,并检查驱动程序是否已成功加载。 如果未引发任何错误,则可以继续./application ,它将测试驱动程序API。


翻译自: https://www.ibm.com/developerworks/aix/library/au-aix-device-driver/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值