ARM内核驱动-设备驱动之字符驱动程序学习

一、linux设备驱动程序概述

1、驱动模型

2、驱动分类

3、驱动程序学习方法

1、驱动模型1


驱动模型2

模型1:没有实现驱动程序的复用,对每一个使用此串口的应用程序都要重新编写驱动程序。这样造成了很大浪费。

模型2:很好的解决了模型1中,驱动程序没有复用的问题,实现了驱动的复用,可是驱动的编写没有统一的规范。

2、设备驱动的分类:字符设备、块设备、网络接口、其他分类法、设备访问。

2.1、字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现open,close,read和write系统调用。例如:串口,LED,按键。

2.2、块设备

在大部分的Unix系统中,块设备定义为:以块(通常为512字节)为最小传输单位的设备,块设备不能按字节处理数据。

而linux则允许块设备传送任意数目的字节(最少访问字节没有限制)。因此,块和字符设备的区别仅仅是驱动与内核的接口不同。常用的块设备包括硬盘,flash,SD卡......

2.3、网络接口

网络接口可以是一个硬件设备,如网卡;但也可以是一个纯粹的软件设备,比如回环接口(lo)。一个网络接口负责发送和接收数据报文。

2.4、其他分类

总线方式划分:

USB设备,PCI设备,平台总线设备(平台总线是linux系统的一重要总线,platform_bus为虚拟的总线,目的是为了提高驱动程序的可移植性)。

2.5、设备使用



3、驱动到底怎么去学:1、硬件工作原理 2、linux驱动模型的学习

驱动的学习方法:相关理论->分析范例程序->制作思维导图->自己编写代码

驱动的学习初期,最好不要深入的读内核代码。

二、使用字符设备驱动

1、编译/安装驱动

在linux系统中,驱动程序通常采用内核模块的程序结构来进行编码。因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块。

2、创建设备文件


通过字符设备文件,应用程序可以使用相应的字符设备驱动程序来控制字符设备。创建字符设备文件的方法一般有两种

1、mknod /dev/文件名 c(代表一个字符设备) 主设备号 次设备号

可以通过cat /proc/devices中看到安装的驱动程序的主设备号,创建的设备文件要和这个号码一致,因为设备文件是通过主设备号和设备驱动程序建立一一对应关系。而次设备号可以随意取一非负整数。次设备号是代表同一种设备区分多种接口。

2、使用函数在驱动程序中实现。

3、访问设备

整个程序代码如下:

注:memdev是使用数组模拟了驱动程序

memdev.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>


int dev1_registers[5];
int dev2_registers[5];

struct cdev cdev; 
dev_t devno;

/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
    
    /*获取次设备号*/
    int num = MINOR(inode->i_rdev);
    
    if (num==0)
        filp->private_data = dev1_registers;
    else if(num == 1)
        filp->private_data = dev2_registers;
    else
        return -ENODEV;  //无效的次设备号
    
    return 0; 
}

/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/*读函数*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/

  /*判断读位置是否有效*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;

  /*读数据到用户空间*/
  if (copy_to_user(buf, register_addr+p, count))
  {
    ret = -EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/*写函数*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  int *register_addr = filp->private_data; /*获取设备的寄存器地址*/
  
  /*分析和获取有效的写长度*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;
    
  /*从用户空间写入数据*/
  if (copy_from_user(register_addr + p, buf, count))
    ret = -EFAULT;
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/* seek文件定位函数 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;

    switch(whence) {
      case SEEK_SET: 
        newpos = offset;
        break;

      case SEEK_CUR: 
        newpos = filp->f_pos + offset;
        break;

      case SEEK_END: 
        newpos = 5*sizeof(int)-1 + offset;
        break;

      default: 
        return -EINVAL;
    }
    if ((newpos<0) || (newpos>5*sizeof(int)))
    	return -EINVAL;
    	
    filp->f_pos = newpos;
    return newpos;

}

/*文件操作结构体*/
static const struct file_operations mem_fops =
{
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
};

/*设备驱动模块加载函数*/
static int memdev_init(void)
{
  /*初始化cdev结构*/
  cdev_init(&cdev, &mem_fops);
  
  /* 注册字符设备 */
  alloc_chrdev_region(&devno, 0, 2, "memdev");
  cdev_add(&cdev, devno, 2);
}

/*模块卸载函数*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*注销设备*/
  unregister_chrdev_region(devno, 2); /*释放设备号*/
}

MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);

Makefile

obj-m :=memdev.o

KDIR :=/home/kernal_driver/linux-tiny6410/

all:
	make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
write_mem.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
int fd;
int src = 2013;

fd = open("/dev/memdev0", O_RDWR);
write(fd, &src, sizeof(int));

return 0;
}
read_mem.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
int fd; 
int dst = 0;

fd = open("/dev/memdev0", O_RDWR);
read(fd, &dst, sizeof(int));

printf("dst is %d\n", dst);

return 0;
}
注解:read_mem和write_mem使用静态交叉编译的方式,因为开发板中没有动态链接库,除非你将动态链接库复制到开发板中。

memdev.c的编译使用的是Makefile文件组织编译、链接成内核模块,然后就是使用insmod安装模块了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值