Linux-ldd

Linux设备驱动,LDD是基础,而且C语言面向对象思想非常的直接借鉴,看过后可以写一些项目驱动了,B1205B1202中一些驱动几乎都是字符设备的小驱动,通用性很强,相对USBNET的驱动要简单多

1.       驱动框架

    Linux驱动是一个很好的OOP教程,下面是驱动模块加载到内核中的流程。

ldd_frame



static struct file_operations iic_fops = {

          owner:  THIS_MODULE,

          ioctl:  iic_ioctl,

          open:  iic_open,

          read:  iic_read,

          write:  iic_write,

          release: iic_release,

};

struct file_operations {

        struct module *owner;

...

};

module_init(iic_init);

iic_major_num = register_chrdev(0,IIC_DEV_NAME,&iic_fops);

 

1register_chrdev时的major为零的话,这个函数就会选择一个空闲号码并做为返回值返回。

加载动态分配主设备号驱动程序的脚本可以利用象awk这类工具从/proc/devices中获取信息,并在/dev中创建文件。

 

2:在devfs_fs_kernel.h文件中有一个变量CONFIG_DEVFS_FS,来控制是调用externdevfs_register还是调用inlinedevfs_register函数,inline的函数体是空的,什么都不做

如果你的内核没有配置CONFIG_DEVFS_FS,不能自动生成设备节点的,需要手工生成,或者用现成的脚本可以在insmod的时候生成

struct cdev {

        struct kobject kobj;

        struct module *owner;

        struct file_operations *ops;

        struct list_head list;//链表头

        dev_t dev;

        unsigned int count;

};

 

int register_chrdev(unsigned int major, const char *name,

                    const struct file_operations *fops)

{

        cd = __register_chrdev_region(major, 0, 256, name);

        cdev->owner = fops->owner;

        cdev->ops = fops;

        err = cdev_add(cdev, MKDEV(cd->major, 0), 256);

}

__register_chrdev_region():

chrdevs[CHRDEV_MAJOR_HASH_SIZE];

按照上面结构体分配出来设备号

 

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

{

        memset(cdev, 0, sizeof *cdev);

        INIT_LIST_HEAD(&cdev->list);

        kobject_init(&cdev->kobj, &ktype_cdev_default);

        cdev->ops = fops;

}

添加到链表中,所有字符设备通过这个链表维护

module_exit(iic_exit);

void unregister_chrdev(unsigned int major, const char *name)

{

 __unregister_chrdev_region(major, 0, 256);

        kfree(cd);

}

 

void cdev_del(struct cdev *p)

{

        cdev_unmap(p->dev, p->count);

        kobject_put(&p->kobj);

}

链表中删除

Open/read:

 

注:一旦设备已经注册到内核表中,无论何时操作与你的设备驱动程序的主设备号匹配的设备文件,内核都会通过在fops跳转表索引调用驱动程序中的正确函数。

Struct scull_dev 是《ldd》书中的结构体,理解为只是为了方便阅读而提供的,和struct cdev的上一层而已,open可以通过scull_dev的得到cdev结构,最终提供给readwrite使用

 

 

2.        驱动模块

每一个OS的驱动都有一个标准的接口,RTEMS驱动接口明显就是模仿Linux的,做驱动的时候只要填写相应的模块API即可

直接从LDD代码参考一份驱动过来

static struct file_operations iic_fops = {

 owner:  THIS_MODULE,

 ioctl:  iic_ioctl,

 open:  iic_open,

 read:  iic_read,

 write:  iic_write,

 release: iic_release,

};

 

module_init(iic_init);

module_exit(iic_exit);

需要编写的代码为iic开头的函数

3.        初始化

注册字符设备,register_chrdev中的参数0为自动分配设备号,也可以手动分,还有一种非标准设备则使用misc_register,主设备号是10

BAN产品中,等到insmod加载驱动过后,lsmod就可以看到的

 

static __init int iic_init(void)

{

 struct proc_dir_entry *iicdir;

 iic_major_num = register_chrdev(0,IIC_DEV_NAME,&iic_fops);

 if(iic_major_num <=0)

 {

  printk(KERN_CRIT "register_chrdev failed/n");

  return iic_major_num;

 }

#ifdef CONFIG_DEVFS_FS

 devfs_iic=devfs_register (NULL, IIC_DEV_NAME,DEVFS_FL_DEFAULT,

     iic_major_num, 1,

     S_IFCHR | S_IRUGO | S_IWUGO,

     &iic_fops, NULL);

#endif

 iicdir=proc_mkdir(IIC_DEV_NAME, NULL);

 create_proc_read_entry (PROC_MAX6634_NAME, 0444, iicdir, max6634_read_proc, NULL);

 create_proc_read_entry (PROC_AT24C02_NAME, 0444, iicdir, at24c02_read_proc, NULL);

 create_proc_read_entry (PROC_MAX6615_NAME, 0444, iicdir, max6615_read_proc, NULL);

 create_proc_read_entry (PROC_PCF8574_NAME, 0444, iicdir, pcf8574_read_proc, NULL);

 printk(KERN_INFO "Driver successfully loaded for MAX6634/n");

 return 0;

}

 

这个时候只是加载了驱动而已,还需要创建设备节点,下面的脚本就是从/proc/devices解析出来设备号,用mknod创建设备节点,这里解析出来的设备号就是lsmod看到的设备号,因为这里用ioctl区分,这里的只有一个主次设备dev/iic;LDD书中是major都相同,次设备不同,他open的时候就区分

 

declare -i num=0

for dev in $ALL_DEVICES ; do

 if [ ! -f /dev/${dev} ]

 then

  major=`cat /proc/devices | awk "//$2==/"$dev/" {print //$1}"`

  rm /dev/${dev}

  mknod /dev/${dev} c $major 1

 fi

 num=num+1

done

 

如果是misc_register注册的非标准设备,则需要根据代码中定义的次设备号手动创建节点

 

rm /dev/led

mknod /dev/led c 10 5

rm /dev/bflash

mknod /dev/bflash c 10 6


4.        create_proc_read_entry

驱动加载之后,通常应用中读取IIC,都是直接open驱动后,直接API读写操作即可

这个函数作用就是在内核启动后驱动还没加载,用户空间想要知道IIC相关寄存器的值时,这个时候可能需要直接从内核空间读取一些信息,比如boardinfo,RTC之类的,create_proc_read_entry可以直接在脚本中`cat /proc/iic/at24c02`,在create_proc_read_entry之后可以看到proc下面的文件的,at24c02_read_proc就是输出到文件的函数

root@10.4.38.252:/proc/iic# ls

at24c02  max6615  max6634  pcf8574

 

5.        读写函数接口

因为又几个IIC设备都公用这个驱动,所以需要根据设备命令字来判断是哪个IIC设备的读写函数,子函数又根据CMD来处理自己IIC设备的操作动作

static int iic_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

{

 int status=0;

 if((cmd>=TEMP_IOCTL_BASE)&&(cmd<BDINFO_IOCTL_BASE+BDINFO_IOCTL_SIZE))

  return at24c02_ioctl(inode, file, cmd, arg);

 if((cmd>=TEMP_IOCTL_BASE)&&(cmd<TEMP_IOCTL_BASE+TEMP_IOCTL_SIZE))

  return max6634_ioctl(inode, file, cmd, arg);

 if((cmd>=POW_IOCTL_BASE)&&(cmd<POW_IOCTL_BASE+POW_IOCTL_SIZE))

  return pcf8574_ioctl(inode, file, cmd, arg);

 if((cmd>=FAN_IOCTL_BASE)&&(cmd<FAN_IOCTL_BASE+FAN_IOCTL_SIZE))

  return max6615_ioctl(inode,file,cmd,arg);

 return status;

}

 

5.        应用程序

应用中调驱动,不管是哪个具体的IIC设备,只需要打开IIC设备,就可以操作读取不通的IIC具体设备

 

iic_fd = open(IIC_DEV_DIR,O_RDWR,0755);

 

ioctl(iic_fd,MAX6634_GET_TEMP,&temper);

//操作温度传感器的

ioctl(iic_fd,MAX6615_REG_READ,&data);

//操作风扇

ioctl(iic_fd,GET_POW_P1_HW_REVISION,&data);

//操作电源板

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值