Linux设备驱动(五)–系统时钟与定时器

时钟脉冲:脉冲信号是一个按一定电压幅度,一定时间间隔连续发出的脉冲信号,脉冲信号之间的时间间隔称为周期。
时钟频率:在单位时间(如1秒)内产生的时钟脉冲个数。
频率的其相应的单位有:Hz(赫)、kHz(千赫)、MHz(兆赫)、GHz(吉赫);其中1GHz=1000MHz,1MHz=1000kHz.1kHz=1000Hz。

s3c2440有外部晶振XTIPLL和外部时钟频率EXTCLK.并有两个PLL(锁相环):MPLL和UPLL.
UPLL:USB设备专用

  • UCLK:USB设备的时钟频率

MPLL:用于CPU和其他外围设备,MPLL会产生3个部分的时钟频率:FCLK、HCLK、PCLK

  • FCLK:用于CPU核
  • HCLK:用于高速总线设备
  • PCLK:用于低速总线设备

下图为PLL设置流程图:

LOCKTIME寄存器设置Lock Time所需的时间

MPLLCON寄存器设置FCLK和Fin(时钟源频率)的倍数
通过MPLLCON寄存器设置MDIV、PDIV、SDIV三个值得到FCLK与Fin的倍率
FCLK和Fin的计算公式:

MPLL(FCLK)= (2*m*Fin)/(p*2^s)
m=MDIV+8 p=PDIV+2 s=SDIV

CLKDIVN寄存器设置FCLK、HCLK、PCLK三者的比例

定时器输出时钟频率=PCLK/{prescaler value+ 1}/{divider value}
{prescaler value+ 1} = 0 ~ 255
prescaler value=TCFG0
{divider value} = 2 , 4 , 8 , 16
divider value=TCFG1

TCON:设置装载定时器计数值,并启动定时器.

定时器内部控制逻辑的工作流程:
①初始化或者获取PCLK值。
②程序初始,设置TCMPBn、TCNTBn这两个寄存器,它们表示定时器n的比较值、初始计数值。
③随之设置TCON寄存器启动定时器n,这时,TCMPBn、TCNTBn的值将被装入其内部寄存器TCMPn、TCNTn中。在定时器n的工作频率下,TCNTn开始减一计数,其值可以通过读取TCNTOn寄存器得知。
④当TCNTn的值等于TCMPn的值时,定时器n的输出管脚TOUTn反转;TCNTn继续减一计数。
⑤当TCNTn的值到达0时,其输出管脚TOUTn再次反转,并触发定时器n的中断。
⑥当TCNTn的值到达0时,如果在TCON寄存器中将定时器n设为“自动加载”,则TCMPB0和TCNTB0寄存器的值被自动装入TCMP0和TCNT0寄存器中,下一个计数流程开始。
定时器n的输出管脚TOUTn初始状态为高电平,以后在TCNTn的值等于TCMPn的值、TCNTn的值等于0时反转。

下图为详细的工作流程图:

最后通过例子结合芯片手册的定时器设置章节理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <plat/regs-timer.h>
#include <mach/regs-irq.h>
#include <asm/mach/time.h>
#include <linux/clk.h>
#include <linux/cdev.h>
#include <linux/device.h>
 
#define DEVICE_NAME "motor"
 
#define Age_Life	10
#define Back_Life	110
#define Car_Stop	00
 
static struct cdev *motor_cdev;
static dev_t devno;
static struct semaphore lock;
 
 
static void Motor_Ferg(unsigned long freq,unsigned int direction)
{
	unsigned long tcon;
	unsigned long tcnt;
	unsigned long tcfg1;
	unsigned long tcfg0;
 
	struct clk *clk_p;
	unsigned long pclk;
 
        /*设置GPH10为OUTPUT模式*/
	s3c2410_gpio_cfgpin(S3C2410_GPH(10),S3C2410_GPIO_OUTPUT);
	/*设置GPH10的转向,由direction控制方向*/
	s3c2410_gpio_setpin(S3C2410_GPH(10),direction);
	/*设置GPB1为定时器1模式*/
	s3c2410_gpio_cfgpin(S3C2410_GPB(1),S3C2410_GPB1_TOUT1);
 
        /*读取TCON、TCFG1、TCFG0三个寄存器的值*/
	tcon	= __raw_readl(S3C2410_TCON);
	tcfg1	= __raw_readl(S3C2410_TCFG1);
	tcfg0	= __raw_readl(S3C2410_TCFG0);
 
        /*定时器0和1的预分频值的掩码*/
	tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK;
	/*设置预分频率为50赫兹*/
	tcfg0 |=  (50-1);
 
        /*定时器定1分频值的掩码*/
	tcfg1 &= ~S3C2410_TCFG1_MUX1_MASK;
	/*设置定时器1为16分频*/
	tcfg1 |= S3C2410_TCFG1_MUX1_DIV16;
 
        /*将设置好的分频值写入寄存器TCFG0和TCFG1当中*/
	__raw_writel(tcfg1, S3C2410_TCFG1);
        __raw_writel(tcfg0, S3C2410_TCFG0);
 
        /*获取PCLK值*/
	clk_p = clk_get(NULL,"pclk");
	pclk  = clk_get_rate(clk_p);
 
        /*得到定时器的输入时钟,进而设置PWM的调制频率*/
	tcnt  = (pclk/50/16)/freq;
 
        /*设置TCNTB1和TCMPB1的值并写入寄存器*/
	__raw_writel(tcnt, S3C2410_TCNTB(1));
        __raw_writel(tcnt/2, S3C2410_TCMPB(1));
 
        /*设置定时器1启动,装载TCNTB1和TCMPB1并自动重载模式*/
	tcon &= ~0xff0;
	tcon |= 0xb00;
 
        /*写入寄存器TCON*/
	__raw_writel(tcon,S3C2410_TCON);
 
        /*设置TCNTB1和TCMPB1的值手动更新并写入寄存器*/
	tcon &= ~0x600;
	__raw_writel(tcon,S3C2410_TCON);
}
 
/*停止运作函数*/
static void Motor_Stop(void)
{
	s3c2410_gpio_cfgpin(S3C2410_GPB(1),S3C2410_GPIO_OUTPUT);
	s3c2410_gpio_setpin(S3C2410_GPB(1),0);
	s3c2410_gpio_cfgpin(S3C2410_GPH(10),S3C2410_GPIO_OUTPUT );
	s3c2410_gpio_setpin(S3C2410_GPH(10),0);
}
 
/*控制函数*/
static long motor_ioctl(
		struct file *file,
		unsigned int cmd,
		unsigned long arg)
{
    unsigned int direction;
	switch(cmd){
		case Age_Life:
            direction=1;
			Motor_Ferg(arg,direction);
			break;
 
        case Back_Life:
            direction=0;
            Motor_Ferg(arg,direction);
            break;
 
		case Car_Stop:
			Motor_Stop();
			break;
	}
	return 0;
}
 
/*打开函数*/
static int motor_open(struct inode *inode,struct file *file)
{
	if(!down_trylock(&lock))
		return	0;
	else
		return -EBUSY;
}
 
/*关闭函数*/
static int motor_release(struct inode *inode,struct file *file)
{
	Motor_Stop();
	up(&lock);
	return	0;
}
 
/*设置各函数*/
static struct file_operations motor_fops=
{
	.owner		= THIS_MODULE,
	.unlocked_ioctl = motor_ioctl,
	.release	= motor_release,
	.open		= motor_open,
};
 
/*注册驱动函数*/
static int __init motor_init(void)
{
	int ret,err;
	int major,minor;
	major=200;
	minor=0;
 
	sema_init(&lock,1);
	ret=register_chrdev_region(major,2,DEVICE_NAME);
 
	if(ret<0)
		return ret;
 
	devno=MKDEV(major,minor);
 
	motor_cdev=cdev_alloc();
	cdev_init(motor_cdev,&motor_fops);
	err=cdev_add(motor_cdev,devno,2);
	if(err)
	{
		unregister_chrdev_region(devno,2);
		return -EFAULT;
	}
 
	printk(DEVICE_NAME "\tinitialized!\n");
	return ret;
}
 
/*注销驱动函数*/
static void __exit motor_exit(void)
{
	cdev_del(motor_cdev);
	unregister_chrdev_region(devno,2);
	printk(DEVICE_NAME "\tunloaded\n");
}
 
module_init(motor_init);
module_exit(motor_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yayi");

Linux设备驱动(四)-中断服务

Linux中断服务大多数情况下,一个驱动程序只需要为它的设备注册一个中断处理例程,当有中断时进行正确的处理。内核负责维护了一个中断服务信号线的注册表,驱动程序在启动中断服务前会请求一个中断通道,使用完后会释放。

注册中断服务函数:
中断注册是使用了的”requset_irq“函数:

1
2
3
4
5
6
7
8
9
extern int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev);
 
irq:申请的硬件中断号
handler:向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它
*name:中断名称,/proc/interrupts中可以看到此名称
*dev:在中断共享时会用到,让handler知道是处理哪个中断,也可以用来指定中断服务函数需要参考的数据地址,一般设置为这个设备的设备结构体或者NULL
flags:中断处理的属性,详细参数在中说明.详细看代码1

代码1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
 * These flags used only by the kernel as part of the
 * irq handling routines.
 *
 * IRQF_DISABLED - keep irqs disabled when calling the action handler.
 *                 DEPRECATED. This flag is a NOOP and scheduled to be removed
 * IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
 * IRQF_SHARED - allow sharing the irq among several devices
 * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
 * IRQF_TIMER - Flag to mark this interrupt as timer interrupt
 * IRQF_PERCPU - Interrupt is per cpu
 * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
 * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
 *                registered first in an shared interrupt is considered for
 *                performance reasons)
 * IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
 *                Used by threaded interrupts which need to keep the
 *                irq line disabled until the threaded handler has been run.
 * IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
 * IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
 * IRQF_NO_THREAD - Interrupt cannot be threaded
 */
#define IRQF_DISABLED				0x00000020
#define IRQF_SAMPLE_RANDOM			0x00000040
#define IRQF_SHARED				0x00000080
#define IRQF_PROBE_SHARED			0x00000100
#define __IRQF_TIMER				0x00000200
#define IRQF_PERCPU					0x00000400
#define IRQF_NOBALANCING			0x00000800
#define IRQF_IRQPOLL				0x00001000
#define IRQF_ONESHOT				0x00002000
#define IRQF_NO_SUSPEND			0x00004000
#define IRQF_FORCE_RESUME			0x00008000
#define IRQF_NO_THREAD				0x00010000
 
#define IRQF_TIMER              (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)

关闭中断服务函数:

1
2
extern void disable_irq(unsigned int irq);
irq:需要屏蔽的硬件中断号

使能中断服务函数:

1
2
extern void enable_irq(unsigned int irq);
irq:需要使能硬件中断号

释放中断服务函数:

1
2
3
void free_irq (unsigned int irq, void *dev_id);
irq:将要注销掉的中断服务函数的中断号;
dev_id:值指定与"request_irq()"函数中使用的"dev_id"值相同的值

中断处理例程的返回值说明:
中断处理例程应当返回一个值指示是否真正处理了一个中断。如果处理例程发现设备确实需要处理,应当返回”IRQ_HANDLED”; 否则返回值”IRQ_NONE”。
下面是文件的内容,更为详细的说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef _LINUX_IRQRETURN_H
#define _LINUX_IRQRETURN_H
 
/**
 * enum irqreturn
 * @IRQ_NONE            interrupt was not from this device
 * @IRQ_HANDLED         interrupt was handled by this device
 * @IRQ_WAKE_THREAD     handler requests to wake the handler thread
 */
enum irqreturn {
        IRQ_NONE                = (0 << 0),
        IRQ_HANDLED             = (1 << 0),
        IRQ_WAKE_THREAD         = (1 << 1),
};
 
typedef enum irqreturn irqreturn_t;
#define IRQ_RETVAL(x)   ((x) != IRQ_NONE)
 
#endif

当硬件中断到达处理器时, 内核提供的一个内部计数器会递增,产生的中断报告显示在”/proc/interrupts”中。这一方法可以用来检查设备是否按预期地工作。此文件只显示当前已安装处理例程的中断的计数。若以前request_irq的一个中断,现在已经free_irq了,那么就不会显示在这个文件中,但是它可以显示终端共享的情况。

“/proc/stat”记录了几个关于系统活动的底层统计信息, 包括(但不仅限于)自系统启动以来收到的中断数。 stat 的每一行以一个字符串开始, 是该行的关键词:intr 标志是中断计数。第一个数是所有中断的总数, 而其他每一个代表一个单独的中断线的计数, 从中断 0 开始(包括当前没有安装处理例程的中断),无法显示终端共享的情况。

以上两个文件的一个不同是:”/proc/interrupts”几乎不依赖体系,而”/proc/stat”的字段数依赖内核下的硬件中断,其定义在中。

Linux设备驱动(三)-file_operations结构体

字符设备驱动一旦注册完后就会进入file_operations结构体对设备操作进行定义。
file_operations:是一个字符设备把驱动的操作和设备号联系在一起的纽带,是一系列指针的集合,把系统调用与驱动程序关联起来的关键数据结构,这个结构体的每个成员都对应着一个系统调用。
传统上, 一个 file_operations结构或者其一个指针称为 fops( 或者它的一些变体). 结构中的每个成员必须指向驱动中的函数, 这些函数实现一个特别的操作, 或者对于不支持的操作留置为 NULL. 当指定为 NULL 指针时内核的确切的行为是每个函数不同的。
file_operations中不少参数包含字串”__user”,这种注解是一种文档形式, 注意, 一个指针是一个不能被直接解引用的用户空间地址. 对于正常的编译”__user”没有效果, 但是它可被外部检查软件使用来找出对用户空间地址的错误使用。
下面看看file_operations中的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct file_operations {
	struct module *owner;
	//所属模块,新注册的设备必须由"THIS_MODULE"这个值来指定
	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);
	//仅用于读取目录,对于设备文件,该字段为NULL
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	//轮询函数,判断目前是否可以进行非阻塞的读取或者写入操作
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	//不使用BLK文件系统,将使用此钟函数指针代替ioctl
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	//在64位系统上,32位的ioctl调用将使用此函数指针代替
	int (*mmap) (struct file *, struct vm_area_struct *);
	//用于请教设备内存映射到进程地址空间
	int (*open) (struct inode *, struct file *);
	//打开函数
	int (*flush) (struct file *, fl_owner_t id);
	//发生在进程关闭设备文件描述符副本,执行并等待,若设置为NULL,内核将忽略用户应用程序的请求
	int (*release) (struct inode *, struct file *);
	//关闭函数
	int (*fsync) (struct file *, int datasync);
	//刷新等待处理的数据
	int (*aio_fsync) (struct kiocb *, int datasync);
	//异步的fsync
	int (*fasync) (int, struct file *, int);
	//通知设备FASYNC标志发生变化
	int (*lock) (struct file *, int, struct file_lock *);
	// 实现文件锁,设备驱动常不去实现此lock
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	//内核调用将其数据发送到对应文件,每次一个数据页,设备驱动通常将其设置为NULL
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	//允许模块检查船体给fcntl(F_SETEL...)调用的标志
	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);
	//readv和weritev:分散/聚集型的读写操作
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
};

一般比较常用的有read,write,open,llseek,unlocked_ioctl,poll和mmap。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int (*open)(struct inode *inode, struct file *filp);
功能:打开文件指针为filp,文件节点为inode的文件
 
int (*release) (struct inode *inode, struct file *filp);
功能:关闭文件指针为filp,文件节点为inode的文件
 
ssize_t (*read) (struct file *filp, char __user *buff, size_t count, loff_t *offp);
功能:把文件指针filp所指定文件的当前位置offp中读取count大小的数据到buff所指向的缓冲区
 
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
功能:把buff所指向的缓冲区中count大小的数据写入到文件指针filp所指定文件的当前位置offp中
 
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait);
功能:如果进程发生阻塞则则使用poll_wait()函数把指定的等待队列添加到wait中,并返回描述设备的掩码
掩码取值:
POLLIN		设备可读
POLLRDNORM	数据可读
POLLOUT	设备可写
POLLWRNORM	数据可写
 
loff_t (*llseek) (struct file *filp, loff_t p, int where);
功能:定位文件指针filp的起始地址where中目标偏移P的位置
where取值:
SEEK_SET	0	文件开头
SEEK_CUR	1	当前位置
SEEK_END	2	文件末尾

然活我们通过一个驱动列子来对比这些成员的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/gpio.h>
 
#define DEVICE_NAME "buttons"
 
static struct cdev *buttons_cdev;
 
static dev_t devno;
 
static volatile char keystatus[]= {'0', '0', '0', '0', '0', '0'};
 
static volatile int press = 0;
 
static DECLARE_WAIT_QUEUE_HEAD(buttons_wait);
 
struct buttons_irq_desc
{
	int irq;
	int pin;
	int pin_set;
	int number;
	char *name;
};
 
static struct buttons_irq_desc buttons_irq[]=
{
	{IRQ_EINT8 ,S3C2410_GPG(0),S3C2410_GPG0_EINT8 ,0,"KEY0"},
	{IRQ_EINT11,S3C2410_GPG(3),S3C2410_GPG3_EINT11,1,"KEY1"},
	{IRQ_EINT13,S3C2410_GPG(5),S3C2410_GPG5_EINT13,2,"KEY2"},
	{IRQ_EINT14,S3C2410_GPG(6),S3C2410_GPG6_EINT14,3,"KEY3"},
	{IRQ_EINT15,S3C2410_GPG(7),S3C2410_GPG7_EINT15,4,"KEY4"},
	{IRQ_EINT19,S3C2410_GPG(11),S3C2410_GPG11_EINT19,5,"KEY5"},
};
 
static irqreturn_t buttons_interrupt(int irq,void *dev_id)
{
	struct buttons_irq_desc *buttons_irq = (struct buttons_irq_desc *)dev_id;
	int down;
	down=!s3c2410_gpio_getpin(buttons_irq->pin);
	if(down!=(keystatus[buttons_irq->number] & 1))
	{
		keystatus[buttons_irq->number]='0'+down;
		press=1;
		wake_up_interruptible(&buttons_wait);
	}
	return IRQ_RETVAL(IRQ_HANDLED);
}
 
static int buttons_open(struct inode *inode,struct file *filp)
{
	int i;
	int err=0;
	for (i = 0; i < 6; i++)
	{
		if(buttons_irq[i].irq<0)
		{
			continue;
		}
		err=request_irq(buttons_irq[i].irq,buttons_interrupt,IRQ_TYPE_EDGE_BOTH,buttons_irq[i].name,(void *)&buttons_irq[i]);
		if(err)
			break;
	}
	if(err)
	{
		i--;
		for(; i>=0; i--)
		{
			if (buttons_irq[i].irq < 0)
			{
				continue;
			}
			disable_irq(buttons_irq[i].irq);
			free_irq(buttons_irq[i].irq,(void *)&buttons_irq[i]);
		}
		return -EBUSY;
	}
	press = 1;
	return 0;
}
 
static int buttons_read(struct file *filp,char __user *buff,size_t count, loff_t *offp)
{
	unsigned long err;
	if (!press)
	{
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		else
			wait_event_interruptible(buttons_wait, press);
	}
	press = 0;
	err = copy_to_user(buff, (const void *)keystatus, min(sizeof(keystatus), count));
	return err ? -EFAULT : min(sizeof(keystatus), count);
}
 
static unsigned int buttons_poll( struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	poll_wait(filp, &buttons_wait, wait);
	if (press)
		mask |= POLLIN | POLLRDNORM;
	return mask;
}
 
static int buttons_close(struct inode *inode, struct file *filp)
{
	int i;
	for (i = 0; i < 6; i++)
	{
		if (buttons_irq[i].irq < 0)
		{
			continue;
		}
		free_irq(buttons_irq[i].irq, (void *)&buttons_irq[i]);
	}
	return 0;
}
 
static struct file_operations buttons_fops=
{
	.owner   = THIS_MODULE,
	.read	= buttons_read,
	.open	= buttons_open,
	.release = buttons_close,
	.poll	=   buttons_poll,
};
 
static int __init buttons_init(void)
{
	int ret,err;
	int major_no,monir_no;
	ret=alloc_chrdev_region(&devno,0,1,DEVICE_NAME);
	if(ret)
		return ret;
	buttons_cdev=cdev_alloc();
	cdev_init(buttons_cdev,&buttons_fops);
	err=cdev_add(buttons_cdev,devno,1);
	if(err)
	{
		unregister_chrdev_region(devno,1);
		return -EFAULT;
	}
	major_no=MAJOR(devno);
	monir_no=MINOR(devno);
	printk(KERN_NOTICE "Major is: %d , Monir is: %d.\n", major_no, monir_no);
	printk(DEVICE_NAME "\tinitialized!\n");
	return 0;
}
 
static void __exit buttons_exit(void)
{
	cdev_del(buttons_cdev);
	unregister_chrdev_region(devno,1);
	printk(DEVICE_NAME "\tunloaded!\n");
}
 
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yayi");

Linux设备驱动(二)-字符设备驱

字符设备驱动是学习Linux驱动开发的基础入门,所以应从此开始入手。这一节主要记录字符设备驱动的注册与卸载函数。
在内核Linux-2.6中的字符设备是用cdev结构体来描述的,注册和卸载驱动也是由cdev来完成的。
这次我们来把分解注释和讲解其中的用法。

cdev结构体:

1
2
3
4
5
6
7
8
struct cdev {
	struct kobject kobj;//定义kobject结构体
	struct module *owner;//所属模块,新注册的设备必须由"THIS_MODULE"这个值来指定。
	const struct file_operations *ops;//设备对应的操作函数集
	struct list_head list;//定义list结构体
	dev_t dev;//设备号
	unsigned int count;//需要分配的设备数目
};

cdev开发的过程应包括:注册设备号、定义cdev设备结构体、动态申请cdev设备内存(此步可选)、初始化cdev设备、注册cdev设备、卸载cdev设备和释放设备号。

注册设备号的方法有两种:动态和静态。动态适用与不了解系统设备号的情况下使用,将注册设备号的任务交给内核来完成,缺点却是一旦注册的设备跟之前有所不同可能导致应用与此设备的程序做出修改。而静态则是自己完成设备号的注册,缺点可能和已经注册的设备号冲突。无论是动态还是静态注册设备号,最后都使用同样的函数来释放已注册的设备号。

静态分配设备号:

1
2
3
4
int register_chrdev_region(dev_t form, unsigned int count, const char *name)
//from:申请使用的设备号
//count:申请使用设备号数目
//name:设备名(体现在/proc/devices)

动态分配设备号:

1
2
3
4
5
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned int count, const char *name)
//dev:分配到的设备号
//baseminor:起始次设备号
//count:需要分配的设备号数目
//name:设备名(体现在/proc/devices)

释放设备号:

1
2
3
void unregister_chrdev_region(dev_t from, unsigned int count)
//from:注销的起始设备号
//count:需要注销的设备号数目

设备号的注册一般在模块开始的时候”module_init()”中就应该完成,如果已经成功注册设备号而没有完成字符设备cdev函数的注册,则应该释放已经注册的设备号。而设备号的释放一般在”module_exit()”的中完成,并且应该卸载字符cdev后再释放设备号。

定义cdev结构体:

1
2
static struct cdev *p;
//p:定义cdev结构体的指针

动态申请cdev设备内存:

1
struct cdev *cdev_alloc(void)

初始化cdev设备:

1
2
3
void cdev_init(struct cdev *p,const struct file_operations *fops)
//p:待初始化的cdev结构
//fops:设备对应的操作函数集,也就是宣告的file_operations结构

注册cdev设备:

1
2
3
4
int cdev_add(struct cdev *p, dev_t dev, unsigned int count)
//p:待添加到内核的cdev设备结构
//dev:设备号
//count:添加的设备个数

注销cdev设备:

1
2
int cdev_del(struct cdev *p)
//p:要注销的字符设备结构

下面是s3c2440的led设备驱动例子,结合上面函数了解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <mach/regs-gpio.h>
 
#define DEVICE_NAME "leds"
 
static struct cdev *leds_cdev;
static dev_t devno;
static unsigned int leds_table[]=
{
	S3C2410_GPE(14),
	S3C2410_GPE(15),
	S3C2410_GPH(9),
	S3C2410_GPH(10),
};
 
static unsigned int leds_cfg_table[]=
{
	S3C2410_GPIO_OUTPUT,
	S3C2410_GPIO_OUTPUT,
	S3C2410_GPIO_OUTPUT,
	S3C2410_GPIO_OUTPUT,
};
 
static	long leds_ioctl(
		struct file *file,
		unsigned int cmd,
		unsigned long arg)
{
	switch(cmd){
		case 0:
			if(arg > 4){
				return -EINVAL;}
			s3c2410_gpio_cfgpin(leds_table[arg],leds_cfg_table[arg]);
			s3c2410_gpio_setpin(leds_table[arg],!cmd);
		case 1:
			if(arg > 4){
				return -EINVAL;}
			s3c2410_gpio_cfgpin(leds_table[arg],leds_cfg_table[arg]);
			s3c2410_gpio_setpin(leds_table[arg],!cmd);
		return 0;
		default:
			return -EINVAL;
		}
}
 
static struct file_operations leds_fops=
{
	.owner = THIS_MODULE,
	.unlocked_ioctl = leds_ioctl,
};
 
static int __init leds_init(void)
{
	int ret;
	int err;
 
	ret= alloc_chrdev_region(&devno,0,1,"leds");
	if (ret<0)
	return ret;
 
	leds_cdev=cdev_alloc();
	cdev_init(leds_cdev,&leds_fops);
	err=cdev_add(leds_cdev,devno,1);
	if(err)
	{
		unregister_chrdev_region(devno,1);
		return -EFAULT;
	}
 
	printk(DEVICE_NAME "\tinitialized!\n");
	return 0;
}
 
static void __exit leds_exit(void)
{
	cdev_del(leds_cdev);
	unregister_chrdev_region(devno,1);
	printk(DEVICE_NAME "\tunloaded\n");
}
 
module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yayi");

Linux设备驱动(一)-驱动基础概述

Linux系统的设备分为三种基本类型:字符设备(char device),块设备(block device)和网络设备(network device)三种。

  • 字符设备指存取时没有缓存必须以串行顺序依次进行访问的设备。
  • 块设备指以块为单位进行操作,读写操作都有缓存来支持,并且可以任意顺序进行访问的设备。
  • 网络设备在Linux里做专门的处理,主要对数据包的接收和发送而设计的。Linux的网络系统主要是基于BSD unix的socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。但内核与网络设备的通信方式和内核与字符设备、块设备之间的通信方式完全不同。

设备文件一般都在/dev/下面,用ll(ls -l)命令可以查看到相应的文件属性,Linux-2.6的内核已经使用了udev的设备文件系统用来代替devfs。

udev完全工作在用户态,利用设备加入或者移除时内核所发送的热插拔时间来工作。在热插拔时,设备的详细信息会由内核输出到位于/sys的sysfs文件系统。(摘选Linux设备驱动开发详解)

所有在/dev目录下的设备文件都是真实存在的设备(包括可以使用的虚拟设备),这是由于udev要求在成功加载驱动模块的时候就因建立对象的设备文件,这与devfs不同,devfs可以在被使用的时候创建,反之,udev在模块卸载的时候就应该删除对应的设备文件。详细的可以查看udev的主页(http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html)

Linux的驱动设备文件有设备号这个概念,由主设备号和次设备号构成。主设备号主要是区别不同类型设备,而次设备号是用来却别同类型设备中的不同设备。内核用dev_t类型来定义设备号。在2.6的内核里dev_t类型由32位构成。其中主设备号占有12位,次设备号占有20位。关于公共定义的设备号可以查看Linux内核目录中的”Documentation\devices.txt”文件,里面定义了一些参考。

下面几个函数用于设备号的使用:

1
2
3
MKDEV(int major, int minor);// 获取设备号
MAJOR(dev_t dev);//获取主设备号
MINOR(dev_t dev);//获取次设备号

使用例子1:

1
2
3
4
5
dev_t dev_no;//设备号
int major_no,minor_no;//major_no为主设备号变量,minor_no为次设备号变量
dev_no=MKDEV(180,96);
major_no=MAJOR(dev_no);//获取设备号中的主设备号
monir_no=MONIR(dev_no);//获取设备号中的次设备号

手工创建设备文件

View Code SHELL
1
2
3
4
5
mknod filename type major minor
filename:设备文件名
type:设备文件类型(c:字符设备文件,b:块设备文件)
major:主设备号
minor:次设备号

使用例子2:

View Code SHELL
1
mknod leds c 180 96