linux 驱动笔记(二)

第三章 字符设备驱动模型cdev

1 设备驱动的分类

1.1 字符设备 c

应用程序以“字符”的方式来访问驱动程序。应用程序和驱动程序之间交互的数据是一个字节一个字节的。这些数据是以“流”的方式进行,实时的传递。数据是没有缓存的。

 

系统IO编程:open()/read()/write()/ioctl()/mmap()/close()

将字符设备看成了一个文件  --->设备文件/设备文件节点

 

crw-rw----    1 root     root       10, 131 Jan  1 12:00 /dev/adc

crw-rw----    1 root     root       29,   0 Jan  1 12:00 /dev/fb0

crw-rw----    1 root     root       14,   3 Jan  1 12:00 /dev/dsp

crw-rw----    1 root     root       13,  64 Jan  1 12:00 /dev/event0

 

1.2 块设备 b

带有缓存的,是以“块”为单位进行数据的传递。块设备都是有文件系统的,块设备可以分区

brw-rw----    1 root     root       31,   0 Jan  1 12:00 /dev/mtdblock0

brw-rw----    1 root     root       31,   1 Jan  1 12:00 /dev/mtdblock1

brw-rw----    1 root     root       31,   2 Jan  1 12:00 /dev/mtdblock2

brw-rw----    1 root     root       31,   3 Jan  1 12:00 /dev/mtdblock3

brw-rw----    1 root     root       31,   4 Jan  1 12:00 /dev/mtdblock4

 

挂载--->将带有文件系统的块设备挂载到根文件系统的某个目录下,然后通过该目录就可以访问块设备。

#mount -t vfat /dev/sda1 /mnt/data

标准IO编程:fopen()/fread()/fwrite()/fclose()

 

1.3 网络设备 s

有线网卡 无线网卡。socket链接来访问网络链接。

 

2 一个字符设备驱动的设计流程(简单)

2.1 定义一个字符设备--->cdev

2.2 申请一个设备号 --->10--主设备号, 131--次设备号,设备号32,12位主设备号,20位次设备号, 10<<20+130 (左移20位加上130)。动态申请or静态注册

2.3 定义文件操作集并初始化 --->file_operations给应用程序提供一个标准的接口

2.4 字符设备的初始化

2.5 将字符设备加入内核

2.6 创建设备文件

3 cdev结构体 (描述一个字符设备)

每个字符设备驱动都有一个cdev,在linux的内核中,使用cdev来描述一个字符设备

/***********************************

#include <linux/cdev.h>

struct cdev {

struct kobject kobj; --->kernel管理设备驱动使用的一个结构体,/sys下的内容

struct module *owner; --->cdev属于谁?,一般使用THIS_MODULE

const struct file_operations *ops; --->文件操作集

struct list_head list; --->kernel管理cdev的一个链表

dev_t dev; --->设备号

unsigned int count; --->次设备的数量

};

**************************************/

dev_t dev; --->设备号

 

typedef __kernel_dev_t dev_t;

typedef __u32 __kernel_dev_t;  //32位 无符号整形

typedef unsigned int __u32;

 

unsigned int count; --->次设备的数量

如:串口设备文件 /dev/s3c2410_serila # -l

crw-rw----    1 root     root      204,  64 Jan  1 12:08 /dev/s3c2410_serial0

crw-rw----    1 root     root      204,  65 Jan  1 12:00 /dev/s3c2410_serial1

crw-rw----    1 root     root      204,  66 Jan  1 12:00 /dev/s3c2410_serial2

crw-rw----    1 root     root      204,  67 Jan  1 12:00 /dev/s3c2410_serial3

: 在开发板里 # echo 123456 > /dev//s3c2410_serila0  //把数字写到串口0 (即控制台,屏幕显示)

uart有四个,这4Uart是同一个类型的设备,驱动模型是相同的。则这四个设备共用一个主设备号,用次设备号来区分每个不同的子设备。

 

例:定义一个cdev

struct cdev led_drv;

 

4 设备号与设备号的申请

   dev_t dev; 是一个32bits的整性值,其中高12bits是主设备号,低20bits是次设备号。每个设备有唯一的设备号。

 

#include <linux/cdev.h>

#define MINORBITS 20

#define MINORMASK ((1U << MINORBITS) - 1)   

 

4.1 设备号的小函数:

#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))    //第一个参数:主设备号, 第二个参数:次设备号, 将主设备号左移20位 或上(加上) 次设备号

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))    //得到设备号里的 主设备号

#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))   //得到设备号里的 次设备号

 

4.2 静态注册一个设备号

/**

 * register_chrdev_region() - register a range of device numbers

 * @from: the first in the desired range of device numbers; must include

 *        the major number.

 * @count: the number of consecutive device numbers required

 * @name: the name of the device or driver.

 *

 * Return value is zero on success, a negative error code on failure. //返回值

 */

int register_chrdev_region(dev_t from, unsigned count, const char *name)  // (设备号, 1个子设备, 设备名称)

参数说明:

dev_t from --->静态注册的设备号。如果我们一次注册多个子设备,from就是第一个子设备的设备号

unsigned count --->子设备的数量

const char *name --->设备名称  #cat /proc/devices

返回值:

成功---返回0

失败---返回负数的错误码

 

例:申请一个led指示灯的驱动

unsigned int led_major=200;   //主设备号

unsigned int led_minor = 0;    //次设备号

dev_t dev_num;      //设备号的变量

int ret;                      //返回值

dev_num = MKDEV(led_major,led_minor); //通过主设备号,次设备号, 得到设备号

ret = register_chrdev_region(dev_num, 1, "gec210_led");  // (设备号, 1个子设备, 设备名称) ,把这个设备号注册到内核

if(ret < 0)

{

return ret; //设备号注册不成功,退出到返回值,不往后运行,

}

 

例:申请4uart驱动的设备号, 串口

crw-rw----    1 root     root      204,  64 Jan  1 12:08 /dev/s3c2410_serial0

crw-rw----    1 root     root      204,  65 Jan  1 12:00 /dev/s3c2410_serial1

crw-rw----    1 root     root      204,  66 Jan  1 12:00 /dev/s3c2410_serial2

crw-rw----    1 root     root      204,  67 Jan  1 12:00 /dev/s3c2410_serial3

 

dev_t dev_num;   //设备号的变量

int ret; //返回值

dev_num = MKDEV(204,64);   //通过主设备号,次设备号, 得到设备号

ret = register_chrdev_region(dev_num, 4, "s3c2410_serial");  // (设备号, 1个子设备, 设备名称--后面的序号系统添加) ,把这个设备号注册到内核

if(ret < 0)

{

return ret;

}

 

4.3 动态分配一个设备号

/**

 * alloc_chrdev_region() - register a range of char device numbers

 * @dev: output parameter for first assigned number

 * @baseminor: first of the requested range of minor numbers

 * @count: the number of minor numbers required

 * @name: the name of the associated device or driver

 *

 * Allocates a range of char device numbers.  The major number will be

 * chosen dynamically, and returned (along with the first minor number)

 * in @dev.  Returns zero or a negative error code.

 */

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

 

参数说明:

dev_t *dev --->分配后的设备号,指针

unsigned baseminor --->指定的次设备号,如果注册多个次设备,则是次设备号的开始值

unsigned count --->子设备的数量

const char *name --->设备名称  #cat /proc/devices

返回值:

成功---返回0

失败---返回负数的错误码

 

4.4 注销一个设备号

一般在驱动安装过程中是申请设备号,在驱动的卸载过程中,来注销这个设备号。

void unregister_chrdev_region(dev_t from, unsigned count)

 

5 文件操作集 (提供函数原型)

在驱动程序中定义的文件操作集是给应用程序提供系统调用的接口

应用程序(用户空间)open() ----->系统调用(内核空间) sys_open()---->vfs_open() (虚拟文件系统)--->file_operations.open() 驱动程序 (文件操作集结构体里函数)

 

每个字符设备驱动都有一个自己的file_operations,file_operations是属于cdev(结构体struct cdev 里的成员 )。我们要定义自己的file_operations,然后对其进行初始化。

 

5.1文件操作集原型:

#inlcude <linux/fs.h>

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **);

};

 

5.2 实际我们设计驱动的时候,常用的成员:

struct file_operations {

struct module *owner; //结构体指针

int (*open) (struct inode *, struct file *); //(文件节点, 文件指针); 打开一个驱动, 给用户程序的open调用的接口,与用户程序的open的参数不一样.

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //

int (*mmap) (struct file *, struct vm_area_struct *);

int (*release) (struct inode *, struct file *); //用户程序 close 系统调用的接口

};

 

例:led灯的驱动

    open("/dev/led", O_RDWR) -->系统调用-->file_operations.open =gec210_led_open() -->GPJ2_0~3配置成输出,led初始是灭

    write(fd,buf,sizeof(buf)) -->系统调用-->file_operations.write =gec210_led_write() -->根据buf的值,控制LED的状态

    closefd -->系统调用-->file_operations.release =gec210_led_release() -->led

 //系统调用(是对内核保护,用户程序不能随便访问内核), 用户程序函数是自己定的,要初始化到文件操作集的函数上,来控制硬件

static int gec210_led_open (struct inode *inode, struct file *filp)  //函数定义

//(在内核里管理一个文件的节点, 指向这个文件的指针),用户自定义函数gec210_led_open 用初始化为文件操作集里的成员open函数

{

printk("openning the driver of led\n");

return 0;  //一般内核, 成功返回0 ,失败返回负数错误码

}

 

ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) //(文件指针, 用户空间程序写下来的数据,  数据大小, 文件指针的偏移值)

{

//接收用户空间write函数写下来的数据,并用这些数据控制led灯的状态。

}

 

static int gec210_led_release(struct inode *inode, struct file *filp) //close

{

printk("openning the driver of led\n");

return 0;

}

         

static struct file_operations gec210_led_fops = {      //自定义一个文件操作集结构体,对结构体初始化,

.owner =  THIS_MODULE,  //此文件操作集结构体 ,属于当前模块, {点 成员},特点1可以缺省初始化, 2 可以打乱成员初始化,定义顺序

.open   =  gec210_led_open,  //用户自定义函数gec210_led_open 初始化为文件操作集里的成员open函数

.write =  gec210_led_write, //同上

.release =  gec210_led_release, //同上

};

  :本例没用到read ,如要读灯的状态,亮灭(数据寄存器),可用read函数

6 字符设备初始化

#include <linux/cdev.h>

/**

 * cdev_init() - initialize a cdev structure //初始化一个结构体

 * @cdev: the structure to initialize

 * @fops: the file_operations for this device

 *

 * Initializes @cdev, remembering @fops, making it ready to add to the

 * system with cdev_add().

 */

原型

void cdev_init(struct cdev *cdev, const struct file_operations *fops)  //把文件操作集放到cdev结构体里里,把两者捆绑

 

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

{

memset(cdev, 0, sizeof *cdev); //cdev结构体里的数据清空

INIT_LIST_HEAD(&cdev->list); //初始化cdev链表

kobject_init(&cdev->kobj, &ktype_cdev_default); //初始化cdev里有kobj

cdev->ops = fops; //把文件操作集给了cdev的一个成员

}

 

例:

static struct cdev led_drv;   //定义cdev

 

static struct file_operations gec210_led_fops = {    //定义文件操作集

.owner =  THIS_MODULE,

.open   =  gec210_led_open,

.write =  gec210_led_write,

.release =  gec210_led_release,

};

 

cdev_init(&led_drv,&gec210_led_fops);   //初始化, 参数是指针,要加取址符

 

 

另一种:错误 ,段错误

static struct cdev *led_drv; //指针 ,要先分配空间 ,不然初始化,清空指向的地址没有分配,

 

static struct file_operations *gec210_led_fops = {

.owner = THIS_MODULE,

.open  = gec210_led_open,

.write = gec210_led_write,

.release = gec210_led_release,

};

 

cdev_init(led_drv,gec210_led_fops);

 

7 将字符设备加入内核

7.1 添加

#include <linux/cdev.h>

/**

 * cdev_add() - add a char device to the system

 * @p: the cdev structure for the device

 * @dev: the first device number for which this device is responsible

 * @count: the number of consecutive minor numbers corresponding to this

 *         device

 *

 * cdev_add() adds the device represented by @p to the system, making it

 * live immediately.  A negative error code is returned on failure.  //失败返回负数错误码, 成功没有说明即为,系统默认0

 */

int cdev_add(struct cdev *p, dev_t dev, unsigned count) //(定义好的cdev, 设备号, 次设备数量)

 

7.2 删除

在驱动的卸载函数中,需要将cdev从内核中删除:

/**

 * cdev_del() - remove a cdev from the system

 * @p: the cdev structure to be removed

 *

 * cdev_del() removes @p from the system, possibly freeing the structure

 * itself.

 */

void cdev_del(struct cdev *p)

 

8 用户空间和内核空间交换数据的函数

#include <linux/uaccess.h>

8.1 放在驱动程序的write函数--->应用程序向驱动程序写数据

inline long copy_from_user(void *to,const void __user * from, unsigned long n)

get_user(x, ptr)

 

8.2 放在驱动程序的read函数--->应用程序从驱动程序读数据

原型:

inline long copy_to_user(void __user *to,const void *from, unsigned long n)put_user(x, ptr)

参数说明:

void *to  --->用户空间的一个buffer

const void *from  --->内核空间的buffer

unsigned long n  --->拷贝的大小

返回值:

拷贝出错,返回负数的错误码

拷贝成功,返回0

例:

static ssize_t

s3c_adc_read(struct file *file, char __user *buffer,

size_t size, loff_t *pos)

{

unsigned int  adc_value = 0;

adc_value = s3c_adc_convert(); //ADC转换,然后得到数字量

//buffer -->用户空间的buffer,应用程序read函数就是从这个buffer来读取数据

//&adc_value -->内核空间的数据,ADC转换后的数字量

//sizeof(unsigned int) -->数据的大小

if (copy_to_user(buffer, &adc_value, sizeof(unsigned int)))  //把内核空间的数据拷贝到用户空间,用户空间应用程序才能从驱动程序得到数据,要read函数里

return -EFAULT; //返回一个负数错误码

return sizeof(unsigned int); //返回正确拷贝的字节数

}

 

ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)

{

int ret;

char kbuf[20];

if(len > 20)

return -EINVAL;

ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据

if(ret!= 0)

return -EFAULT;

return len;

}

9 错误码

9.1 Linux errno 错误对照表

errno.h C语言C标准函式库里的标头档,定义了通过错误码来回报错误信息的宏。

errno本身是一个整型的全局变量,当使用errno的库函数,在执行出错时,只通过函数返回值返回一个表示出错的标识,如-1NULL等,具体的出错原因会被赋值到errno中。通过查询errno可以确定具体的出错原因。

errno.h中定义了一系列常见的宏,其形式为

#define EPERM 1 /* Operation not permitted */

 

可以划分为

1 定义一个宏名,以E开头;

2 定义其值,为一个正整数;

3 一个注释区域,说明该错误号出现时的具体错误内容。

 

errno.h中的条目因不同编译器的实现而有所区别,一般在100~128条范围内,具体内容可以在编译器的系统标准头文件夹下查看对应文件。

 

只有当一个库函数失败时,errno才会被设置。当函数成功运行时,errno的值不会被修改。这意味着我们不能通过测试errno的值来判断是否有错误存在。反之,只有当被调用的函数提示有错误发生时检查errno的值才有意义。

查看错误代码errno是调试程序的一个重要方法。当linux C api函数发生异常时,一般会将errno变量(include errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因。在实际编程中用这一招解决了不少原本看来莫名其妙的问题。

 

9.1.1文件名: errno.h  errno-base.h

 

9.1.2所在文件夹:

A) linux 2.4.20-18内核代码

/usr/include/asm/errno.h

 

B) linux 2.6.32的内核代码中

LINUX/android/kernel/include/uapi/asm-generic/errno-base.h    --->errno 1-34

LINUX/android/kernel/include/uapi/asm-generic/errno.h     --->errno 35-133

LINUX/android/kernel/include/linux/errno.h --->errno512-529

 

C) errno <errno.h> 中定义,错误 Exx 的宏定义在 /usr/include/asm-generic 文件夹下面的  errno-base.h errno.h,分别定义了 1-34 35-132 的错误定义。

/usr/include/asm-generic/errno-base.h    --->errno 1-34

/usr/include/asm-generic/errno.h    --->errno 35-133

 

9.1.3 strerror() 函数依据 errno 值返回错误描述字符串,下面程序打印对照表:

A)

[cpp] view plain copy

#include <errno.h>  

#include <string.h>  

#include <stdio.h>  

int main()  

{  

    int i;  

    for(i = 0; i < 140; ++i)  

    {  

        errno = i;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

        printf("errno %d :\t\t%s\n",i,strerror(errno));  

    }  

    return 0;  

}

B)

#include<sys/types.h>                                                           

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

#include<stdlib.h>

 

int main(void)

{

    int fd;

    fd=open("abc",O_WRONLY);

    if(fd<0){

        printf("Error:fd=%d\n",fd);

        perror("open file abc");//这一行就是根据系统的编号打印出错误的信息

    }   

    return 0;

}

//*********************

 

9.2 Linux errno 错误对照表宏定义

 

9.2.1 errno-base.h  --->errno 1-34

 

#define EPERM  1 /* Operation not permitted */

#define ENOENT  2 /* No such file or directory */

#define ESRCH  3 /* No such process */

#define EINTR  4 /* Interrupted system call */

#define EIO          5 /* I/O error */

#define ENXIO  6 /* No such device or address */

#define E2BIG  7 /* Argument list too long */

#define ENOEXEC  8 /* Exec format error */

#define EBADF  9 /* Bad file number */

#define ECHILD 10 /* No child processes */

#define EAGAIN 11 /* Try again */

#define ENOMEM 12 /* Out of memory */

#define EACCES 13 /* Permission denied */

#define EFAULT 14 /* Bad address */

#define ENOTBLK 15 /* Block device required */

#define EBUSY 16 /* Device or resource busy */

#define EEXIST 17 /* File exists */

#define EXDEV 18 /* Cross-device link */

#define ENODEV 19 /* No such device */

#define ENOTDIR 20 /* Not a directory */

#define EISDIR 21 /* Is a directory */

#define EINVAL 22 /* Invalid argument */

#define ENFILE 23 /* File table overflow */

#define EMFILE 24 /* Too many open files */

#define ENOTTY 25 /* Not a typewriter */

#define ETXTBSY 26 /* Text file busy */

#define EFBIG 27 /* File too large */

#define ENOSPC 28 /* No space left on device */

#define ESPIPE 29 /* Illegal seek */

#define EROFS 30 /* Read-only file system */

#define EMLINK 31 /* Too many links */

#define EPIPE 32 /* Broken pipe */

#define EDOM 33 /* Math argument out of domain of func */

#define ERANGE 34 /* Math result not representable */

 

9.2.2 errno.h  --->errno 35-133

 

10 访问驱动的应用程序

#include <stdio.h>

#include <fcntl.h>

int main(void)

{

int fd;

int ret;

char buf[]="hello driver\n";

//"/dev/led_drv" ---linux驱动的设备文件节点(node

fd = open("/dev/led_drv", O_WRONLY);

if(fd <0)

{

perror("open led_drv:");

return -1;

}

ret = write(fd,buf,sizeof(buf));

if(ret < 0)

{

perror("write led_drv: ");

return -1;

}

sleep(1);

close(fd);

return 0;

}

 

11 创建设备文件

 

11..1 安装驱动

# insmod led_drv.ko

[  576.746579] hello gec210

 

11.2 主设备号和设备名称

 

# cat /proc/devices

Character devices:

 

100 gec210_leds

 

11.3 创建设备文件(手动)

#mknod /dev/led_drv c 100 0

说明:

mknod 设备文件 设备类型 主设备号 次设备号

 

11.4 查看设备文件

[root@GEC210 /test]# ls /dev/led_drv -l

crw-r--r--    1 root     root      100,   0 Jan  1 12:19 /dev/led_drv

 

[root@GEC210 /test]# ./test

[ 1241.597908] openning the driver of led

[ 1241.597968] kbuf=hello driver

[ 1241.597972]

[ 1242.598121] closing the driver of led

A 作业

1.作业1 什么是系统调用?

2.作业2 什么是vfs

3.作业3 结构体的初始化方法

4.作业4 linux内核的编码风格

5.作业5 goto的用法

6.

1) 定义文件操作集中的read函数

      

2) 去掉文件操作集中的openrelease函数

B1 代码一   --字符设备驱动模型cdev

1. Filename: led_drv.c

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/uaccess.h>

 

//1)定义一个字符设备

static struct cdev led_drv;

 

static unsigned int led_major = 100; //0-->动态分配,>0-->静态注册

static unsigned int led_minor = 0;

static dev_t led_drv_num;

 

 //3)定义文件操作集,并初始化

static int gec210_led_open (struct inode *inode, struct file *filp)

{

printk("openning the driver of led\n");

return 0;

}

 

ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)

{

int ret;

char kbuf[20];

if(len > 20)

return -EINVAL;

ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据

if(ret!= 0)

return -EFAULT;

printk("kbuf=%s\n",kbuf);

return len;

}

 

static int gec210_led_release(struct inode *inode, struct file *filp)

{

printk("closing the driver of led\n");

return 0;

}

         

static struct file_operations gec210_led_fops = {

.owner = THIS_MODULE,

.open  = gec210_led_open,

.write = gec210_led_write,

.release = gec210_led_release,

};

 

static int __init gec210_led_init(void) //驱动的初始化及安装函数

{

int ret;

//2)申请/注册设备号

if(led_major == 0){

ret = alloc_chrdev_region(&led_drv_num, led_minor, 1, "gec210_leds");

}

else{

led_drv_num = MKDEV(led_major,led_minor);

ret = register_chrdev_region(led_drv_num,  1, "gec210_leds");

}

if(ret < 0){

printk("led_drv_num is error \n");

return ret;

}

 

//4)初始化cdev

cdev_init(&led_drv,  &gec210_led_fops);

 

//5)cdev加入kernel

ret = cdev_add(&led_drv,led_drv_num, 1 );

if(ret < 0){

printk("cdev add error\n");

goto failed_cdev_add;

}

 

printk("hello gec210\n"); //替代printf()

return 0;

failed_cdev_add:

unregister_chrdev_region(led_drv_num,  1);

return ret;

}

 

static void __exit gec210_led_exit(void) //驱动卸载函数

{

unregister_chrdev_region(led_drv_num,  1);

cdev_del(&led_drv);

printk("good bye gec210\n");

}

 

module_init(gec210_led_init); //驱动的入口

module_exit(gec210_led_exit); //驱动的出口

 

//内核模块的描述

MODULE_AUTHOR("bobeyfeng@163.com");

MODULE_DESCRIPTION("the first demo of module");

MODULE_LICENSE("GPL"); //符合GPL协议

MODULE_VERSION("V1.0");

 

//----------------------------------------

2. Filename: test.c

 

#include <stdio.h>

#include <fcntl.h>

int main(void)

{

int fd;

int ret;

char buf[]="hello driver\n"; //定义字符串

//"/dev/led_drv" ---linux驱动的设备文件节点(node

fd = open("/dev/led_drv", O_WRONLY);

if(fd <0)

{

perror("open led_drv:");

return -1;

}

ret = write(fd,buf,sizeof(buf));

if(ret < 0)

{

perror("write led_drv: ");

return -1;

}

sleep(1);

close(fd);

return 0;

}

 

//--------------------------------------

3. Filename: Makefile

obj-m += led_drv.o

#KERNELDIR := /lib/modules/$(shell uname -r)/build

KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110

PWD:=$(shell pwd)

 

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

rm -rf *.o *.mod.c *.mod.o *.ko

B2 代码二   --字符设备驱动模型cdev

1. Filename: led_drv.c

 

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/uaccess.h>

 

static struct cdev led_drv;

 

static unsigned int led_major = 200;  // 等于零是动态生成 大于零是静态注册

static unsigned int led_minor = 0;

 

static dev_t led_drv_num;

 

int ret;

 

// static int gec210_led_open (struct inode *inode, struct file *filp)

// {

// printk("openning the driver of led---open\n");

// return 0;

// }

 

ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)

{

//接收用户空间write函数写下来的数据,并用这些数据控制led灯的状态。

int ret;

char kbuf[40];

if(len > 40)

return -EINVAL;

ret = copy_from_user(kbuf,buf,len);

if(ret < 0)

return -EFAULT;

printk("kbuf:%s\n",kbuf);

return len;

}

 

ssize_t gec210_led_read(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)

{

int ret;

char kbuf[40]="hello user!";

if(len > 40)

return -EINVAL;

ret = copy_to_user(buf,kbuf,len);

if(ret < 0)

return -EFAULT;

return len;

}

 

// static int gec210_led_release(struct inode *inode, struct file *filp)

// {

// printk("openning the driver of led----release\n");

// return 0;

// }

         

static struct file_operations gec210_led_fops = {

.owner = THIS_MODULE,

//.open  = gec210_led_open,

.write = gec210_led_write,

.read = gec210_led_read,

//.release = gec210_led_release,

};

 

static int __init gec210_led_init(void)  //驱动的初始化及安装函数

{

printk("hello gec210\n"); //替代printf()

if(led_major == 0){

ret = alloc_chrdev_region(&led_drv_num,led_minor,1,"gec210_led");

}

else{

led_drv_num = MKDEV(led_major,led_minor);

ret = register_chrdev_region(led_drv_num,1,"gec210_led");

}

if(ret < 0){

printk("led_drv_num is error!\n");

return ret;

}

cdev_init(&led_drv,&gec210_led_fops);

ret = cdev_add(&led_drv,led_drv_num,1);

if(ret < 0){

printk("fail to add dev!\n");

goto failed_cdev_add;

}

failed_cdev_add:

unregister_chrdev_region(led_drv_num,1);

return ret;

 

return 0;

}

 

 

static void __exit gec210_led_exit(void)

{

unregister_chrdev_region(led_drv_num,1);

cdev_del(&led_drv);

printk("good bye gec210\n");

}

 

module_init(gec210_led_init); //驱动的入口

module_exit(gec210_led_exit); //驱动的出口

 

//内核模块的描述

MODULE_AUTHOR("bobeyfeng@163.com");

MODULE_DESCRIPTION("the first demo of module");

MODULE_LICENSE("GPL"); //符合GPL协议

MODULE_VERSION("V1.0");

//--------------------------------------

2. Filename:  test

 

#include <stdio.h>

#include <fcntl.h>

#include <string.h>

 

int main()

{

int fd;

char buf[20]="hello driver!";

char buf1[40];

fd = open("/dev/led_drv",O_RDWR);

if(fd < 0)

{

perror("open led_drv:");

return -1;

}

int ret;

bzero(buf1,40);

ret = read(fd,buf1,sizeof(buf1));

if(ret < 0)

{

printf("failed to read data from led_drv!\n");

return -1;

}

printf("buf1:%s\n",buf1);

ret = write(fd,buf,sizeof(buf));

if(ret < 0)

{

printf("failed to write to led_drv!\n");

return -1;

}

close(fd);

return 0;

}

//--------------------------------------

 

3. Filename: Makefile

 

obj-m += led_drv.o

#KERNELDIR := /lib/modules/$(shell uname -r)/build

KERNELDIR := /home/smbshare/qudong/linux-2.6.35.7-gec-v3.0-gt110

PWD:=$(shell pwd)

 

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

rm -rf *.o *.mod.c *.mod.o *.ko 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值