A)對比新老接口
(1)老接口:register_chrdev
(2)新接口:register_chrdev_region/alloc_chrdev_region
+ cdev_alloc/cdev_init/ cdev_add
+class_create/device create //藉助udev生成/dev/test111設備文件
B)/etc/init.d/rcS裏面添加mdev(爲udev的嵌入式版本)支持
mount -a
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s
C)驅動文件module_test.c如下:
----------------------------------------------------------------
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
#include <linux/device.h>
//#define MYMAJOR 200
#define MYCNT 1
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
//int mymajor;
static dev_t mydev;
//static struct cdev test_cdev;
static struct cdev *pcdev;
static struct class *test_class;
char kbuf[100]; // 內核空間的buf
static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 這個函數中真正應該放置的是打開這個設備的硬件操作代碼部分
// 但是現在暫時我們寫不了這麼多,所以用一個printk打印個信息來做代表。
printk(KERN_INFO "test_chrdev_open\n");
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 寫函數的本質就是將應用層傳遞過來的數據先複製到內核中,然後將之以正確的方式寫入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用該函數將應用層傳過來的ubuf中的內容拷貝到驅動空間中的一個buf中
//memcpy(kbuf, ubuf); // 不行,因爲2個不在一個地址空間中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");
if (kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (kbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
/*
// 真正的驅動中,數據從應用層複製到驅動中後,我們就要根據這個數據
// 去寫硬件完成硬件的操作。所以這下面就應該是操作硬件的代碼
if (!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
*/
return 0;
}
// 自定義一個file_operations結構體變量,並且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 慣例,直接寫即可
.open = test_chrdev_open, // 將來應用open打開這個設備時實際調用的
.release = test_chrdev_release, // 就是這個.open對應的函數
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模塊安裝函數
static int __init chrdev_init(void)
{
int retval;
printk(KERN_INFO "chrdev_init helloworld init\n");
// 使用新的cdev接口來註冊字符設備驅動
// 新的接口註冊字符設備驅動需要2步
// 第1步:分配主次設備號
retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
if (retval < 0)
{
printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success\n");
printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev));
// 第2步:註冊字符設備驅動
pcdev = cdev_alloc(); // 給pcdev分配內存,指針實例化
//cdev_init(pcdev, &test_fops);
pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;
retval = cdev_add(pcdev, mydev, MYCNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success\n");
// 註冊字符設備驅動完成後,添加設備類的操作,以讓內核幫我們發信息
// 給udev,讓udev自動創建和刪除設備文件
test_class = class_create(THIS_MODULE, "aston_class");
if (IS_ERR(test_class))
return -EINVAL;
// 最後1個參數字符串,就是我們將來要在/dev目錄下創建的設備文件的名字
// 所以我們這裏要的文件名是/dev/test
device_create(test_class, NULL, mydev, NULL, "test111");
// 使用動態映射的方式來操作寄存器
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
// return -EINVAL;
goto flag3;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
// return -EINVAL;
goto flag3;
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
//goto flag0:
return 0;
// 如果第4步纔出錯跳轉到這裏來
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
// 如果第3步纔出錯跳轉到這裏來
flag3:
cdev_del(pcdev);
// 如果第2步纔出錯跳轉到這裏來
flag2:
// 在這裏把第1步做成功的東西給註銷掉
unregister_chrdev_region(mydev, MYCNT);
// 如果第1步纔出錯跳轉到這裏來
flag1:
return -EINVAL;
//flag0:
// return 0;
}
// 模塊下載函數
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
// 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
/*
// 在module_exit宏調用的函數中去註銷字符設備驅動
unregister_chrdev(mymajor, MYNAME);
*/
device_destroy(test_class, mydev);
class_destroy(test_class);
// 使用新的接口來註銷字符設備驅動
// 註銷分2步:
// 第一步真正註銷字符設備驅動用cdev_del
cdev_del(pcdev);
// 第二步去註銷申請的主次設備號
unregister_chrdev_region(mydev, MYCNT);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx這種宏作用是用來添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("aston"); // 描述模塊的作者
MODULE_DESCRIPTION("module test"); // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
D)Makefile
#ubuntu的內核源碼樹,如果要編譯在ubuntu中安裝的模塊就打開這2個
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
# 開發板的linux內核的源碼樹目錄
KERN_DIR = /home/jimmy/news5pv210/x210ii_kernel
obj-m += module_test.o
all:
make -C $(KERN_DIR) M=`pwd` modules
arm-linux-gcc app.c -o app
cp:
cp *.ko /tftpboot/nfs/driver_test/
cp app /tftpboot/nfs/driver_test/app-chrdevsenior
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf app
E) app文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FILE "/dev/test111" // 剛纔mknod創建的設備文件名
char buf[100];
int main(void)
{
int fd = -1;
int i = 0;
fd = open(FILE, O_RDWR);
if (fd < 0)
{
printf("open %s error.\n", FILE);
return -1;
}
printf("open %s success..\n", FILE);
/*
// 讀寫文件
write(fd, "on", 2);
sleep(2);
write(fd, "off", 3);
sleep(2);
write(fd, "on", 2);
sleep(2);
*/
/*
write(fd, "1", 1);
sleep(2);
write(fd, "0", 1);
sleep(2);
write(fd, "1", 1);
sleep(2);
*/
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("請輸入 on | off \n");
scanf("%s", buf);
if (!strcmp(buf, "on"))
{
write(fd, "1", 1);
}
else if (!strcmp(buf, "off"))
{
write(fd, "0", 1);
}
else if (!strcmp(buf, "flash"))
{
for (i=0; i<3; i++)
{
write(fd, "1", 1);
sleep(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 關閉文件
close(fd);
return 0;
}