key.c
<span style="font-size:14px;">#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/module.h> /* For module specific items */
#include <linux/moduleparam.h> /* For new moduleparam's */
#include <linux/types.h> /* For standard types (like size_t) */
#include <linux/errno.h> /* For the -ENODEV/... values */
#include <linux/kernel.h> /* For printk/panic/... */
#include <linux/fs.h> /* For file operations */
#include <linux/ioport.h> /* For io-port access */
#include <linux/platform_device.h> /* For platform_driver framework */
#include <linux/init.h> /* For __init/__exit/... */
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
#include <linux/io.h> /* For inb/outb/... */
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/cdev.h>
struct timer_list button_timer;
struct cdev cdev;
dev_t devno;
volatile unsigned long gpncon;
volatile unsigned long gpndat;
unsigned char key;//键值
struct key_desc {
int irq;
char *name;
int pin; /* GPNDAT哪位 */
unsigned char val; /* 按键值 */
};
static DECLARE_WAIT_QUEUE_HEAD(button_waiq);
static struct key_desc key_desc[] = {
{IRQ_EINT(0), "S2", 0, 2},
{IRQ_EINT(1), "S3", 1, 3},
{IRQ_EINT(2), "S4", 2, 4},
{IRQ_EINT(3), "S5", 3, 5},
{IRQ_EINT(4), "S6", 4, 6},
{IRQ_EINT(5), "S7", 5, 7},
};
static ssize_t button_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
wait_event_interruptible(button_waiq, key);
copy_to_user(buf, &key, 1);
key = 0;
return 1;
}
static struct file_operations button_fops =
{
.owner = THIS_MODULE,
.read = button_read,
};
static irqreturn_t button_function(int irq, void *dev_id)
{
struct key_desc *key_desc = (struct key_desc *)dev_id;
disable_irq_nosync(key_desc->irq);//关闭按键中断
mod_timer(&button_timer, jiffies + HZ/100);// 定时10ms
button_timer.data = (unsigned long)dev_id;
return IRQ_HANDLED;
}
static int button_time_function(unsigned long data)
{
unsigned long value = readl(gpndat);
struct key_desc *key_desc = (struct key_desc *)data;
if(value & (1 << key_desc->pin))//如果已经松开,说明是抖动
{
enable_irq(key_desc->irq);//打开按键中断
return 0;
}
else
{
wake_up_interruptible(&button_waiq);//唤醒等待队列
key = key_desc->val;//记录键值
}
return 1;
}
static int button_init()
{
int i = 0;
cdev_init(&cdev, &button_fops);//初始化字符设备
alloc_chrdev_region(&devno, 0, 6, "mykey");//动态分配设备号,6个次设备号
cdev_add(&cdev, devno, 6);//设备注册
for(; i < 6; i++)//注册中断
{
request_irq(key_desc[i].irq, button_function, IRQF_TRIGGER_FALLING, key_desc[i].name, &key_desc[i]);
}
gpncon = ioremap(0x7f008830, 4);
gpndat = gpncon + 1;
init_timer(&button_timer);
button_timer.function = button_time_function;
add_timer(&button_timer);
return 0;
}
static void button_exit()
{
int i = 0;
del_timer(&button_timer);
// iounmap(gpncon);
// iounmap(gpndat);
for(; i < 6; i++)//注销中断
{
free_irq(key_desc[i].irq, &key_desc[i]);
}
cdev_del(&cdev);//卸载设备
unregister_chrdev_region(devno, 6);//释放设备号
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");</span>
当我们执行insmod key.ko时,程序执行button_init()函数,首先注册设备,可以通过cat /proc/devices查看所分配的设备号,执行mknod /dev/mykey c 252 0创建设备节点,应用程序以文件操作的方式处理该节点。
以struct cdev *cdev方式创建的是指针,只是在全局区或栈分配指针的空间,需要为struct cdev动态分配空间,在内核里使用kmalloc和kfree。
使用ctags查看内核源码:安装yum install ctags,ctags -R递归生成该目录下的所有函数,vi -t function查找函数,crtl+]跳到光标所在函数源码,ctrl+t返回,shift+K跳到光标所在函数手册。
按键可以使用这些中断号,这些中断号是VIC0、VIC1所分配的64个中断号之外的中断号。
主要知识:
1.字符设备结构体
<span style="font-size:14px;">struct cdev
{
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};</span>
<span style="font-size:14px;">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;
}
</span>
cdev_init()为cdev结构体绑定操作函数fops
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
alloc_chrdev_region动态分配设备号,
register_chrdev_region
指定分配的设备号,两者最终都将调用
__register_chrdev_region
dev_t是一个32位的数 ,高12位表示主设备号,其余20位表示次设备号,可以使用MKDEV得到dev_t,也可以通过MAJOR(dev_t dev);和MINOR(dev_t dev);得到主次设备号
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
2.时钟
3.中断
key_app.c
<span style="font-size:14px;">#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
int fd = open("/dev/mykey", O_RDWR);
char buf[2];//用于采集信息
while(1)
{
read(fd, buf, 1);
printf("the key %d is on\n",(int)buf[0]);
}
return 0;
}</span>
Makefile
<span style="font-size:14px;">obj-m := key.o
KDIR := /home/f/Desktop/share/linux-ok6410/
all:
make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order
</span>
KDIR是编译目录,PWD是当前路径