1. 程式人生 > >Linux:驅動之字元設備註冊新介面(未完)

Linux:驅動之字元設備註冊新介面(未完)

  • 驅動之字元設備註冊新介面

目前尚不是最終版本,還望有心人自己學習的時候,把自己整合的知識點相關的答案也好問題也好,或者實踐過程中的一些操作截圖,再或者其他的一些想要分享材料發給筆者郵箱:[email protected],我們一起完善這篇部落格!筆者寫這篇部落格的時候已經工作第四個年頭了,目前是在整理之前有過的學習資料,僅作為筆記,供同志們參考!短時間內可能不會去全部完善。

  • 新介面與老介面

老介面:register_chrdev;

新介面:register_chrdev_region/alloc_chrdev_region + cdev;

為什麼需要新介面?

cdev介紹:結構體?相關函式:cdev_alloc、cdev_init、cdev_add、cdev_del?

裝置號:主裝置號和次裝置號?dev_t型別?MKDEV、MAJOR、MINOR三個巨集?

程式設計實踐?使用register_chrdev_region + cdev_init + cdev_add進行字元裝置驅動註冊?

完整的相關程式碼見下述檔案:

module_test.c

// 為了module_init,module_exit相關的,加入下面標頭檔案
#include <linux/module.h>
// 為了__init,__exit相關的,加入下面標頭檔案
#include <linux/init.h>		
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <mach/gpio-bank.h>		
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>

#define MYMAJOR		200
#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;

// 核心空間的buf
char kbuf[100];	

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個buf不在一個地址空間中
	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 (!strcmp(kbuf, "on"))
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (!strcmp(kbuf, "off"))
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	*/
	
	if (kbuf[0] == '1')
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (kbuf[0] == '0')
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	
	return 0;
}

// 自定義一個file_operations結構體變數,並且去填充
static const struct file_operations test_fops = {
	// 慣例,直接寫即可
	.owner		= THIS_MODULE,		
	// 將來應用open開啟這個裝置時實際呼叫的
	// 就是這個.open對應的函式
	.open		= test_chrdev_open,			
	.release	= test_chrdev_release,		
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};

// 模組安裝函式
static int __init chrdev_init(void)
{	
    int retval;
	printk(KERN_INFO "chrdev_init helloworld init\n");
	/*
	// 在module_init巨集呼叫的函式中去註冊字元裝置驅動
	// major傳0進去表示要讓核心幫我們自動分配一個合適的空白的沒被使用的主裝置號
	// 核心如果成功分配就會返回分配的主裝置號;如果分配失敗會返回負數
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
	*/
	
	// 使用新的cdev介面來註冊字元裝置驅動
	// 新的介面註冊字元裝置驅動需要2步
	
	// 第1步:註冊/分配主次裝置號
	mydev = MKDEV(MYMAJOR, 0);
	retval = register_chrdev_region(mydev, MYCNT, MYNAME);
	if (retval) {
		printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev_region success\n");

	// 第2步:註冊字元裝置驅動
	cdev_init(&test_cdev, &test_fops);
	retval = cdev_add(&test_cdev, mydev, MYCNT);
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add\n");
		return -EINVAL;
	}
	printk(KERN_INFO "cdev_add success\n");
	
	/*
	// 模組安裝命令insmod時執行的硬體操作
	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	printk(KERN_INFO "GPJ0CON = %p.\n", GPJ0CON);
	printk(KERN_INFO "GPJ0DAT = %p.\n", GPJ0DAT);
	*/
	
	// 使用動態對映的方式來操作暫存器
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
		return -EINVAL;
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
		return -EINVAL;
	
	pGPJ0CON = ioremap(GPJ0CON_PA, 4);
	pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
	
	*pGPJ0CON = 0x11111111;
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));

	return 0;
}

// 模組解除安裝函式
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	*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);
	*/
	
	// 使用新的介面來登出字元裝置驅動
	// 登出分2步:
	// 第一步真正登出字元裝置驅動用cdev_del
	cdev_del(&test_cdev);
	// 第二步去登出申請的主次裝置號
	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");			

使用alloc_chrdev_region自動分配裝置號:

使用register_chrdev_region需要事先知道要使用的主、次裝置號是什麼,一般可以先cat /proc/devices去檢視裝置號的使用情況,自行選則沒有使用的裝置號進行使用;

更簡便、更智慧的方法是讓核心給我們自動分配一個主裝置號,使用alloc_chrdev_region就可以進行自動分配了;

自動分配的裝置號,我們必須去知道他的主次裝置號,否則後面沒法去mknod建立他對應的裝置檔案。

得到分配的主裝置號和次裝置號:

使用MAJOR巨集和MINOR巨集從dev_t得到major和minor;

反過來使用MKDEV巨集從major和minor得到dev_t;

使用這些巨集的程式碼具有可移植性。

中途出錯的倒影式錯誤處理方法:

核心中很多函式中包含了多個操作,這些操作每一步都有可能出錯,出錯後後面的步驟就沒有進行下去的必要了。

完整的相關程式碼見下述檔案(其他檔案等同於最後一段完整程式碼中的新述檔案):

module_test.c

// 為了module_init,module_exit相關的,加入下面標頭檔案
#include <linux/module.h>
// 為了__init,__exit相關的,加入下面標頭檔案
#include <linux/init.h>		
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <mach/gpio-bank.h>		
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>

#define MYMAJOR		200
#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;

// 核心空間的buf
char kbuf[100];	

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個buf不在一個地址空間中
	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 (!strcmp(kbuf, "on"))
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (!strcmp(kbuf, "off"))
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	*/
	
	if (kbuf[0] == '1')
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (kbuf[0] == '0')
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	
	return 0;
}

// 自定義一個file_operations結構體變數,並且去填充
static const struct file_operations test_fops = {
	// 慣例,直接寫即可
	.owner		= THIS_MODULE,		
	// 將來應用open開啟這個裝置時實際呼叫的
	// 就是這個.open對應的函式
	.open		= test_chrdev_open,			
	.release	= test_chrdev_release,		
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};

// 模組安裝函式
static int __init chrdev_init(void)
{	
    int retval;
	printk(KERN_INFO "chrdev_init helloworld init\n");
	/*
	// 在module_init巨集呼叫的函式中去註冊字元裝置驅動
	// major傳0進去表示要讓核心幫我們自動分配一個合適的空白的沒被使用的主裝置號
	// 核心如果成功分配就會返回分配的主裝置號;如果分配失敗會返回負數
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
	*/
	
	// 使用新的cdev介面來註冊字元裝置驅動
	// 新的介面註冊字元裝置驅動需要2步
	

	// 第1步:註冊/分配主次裝置號
	// mydev = MKDEV(MYMAJOR, 0);
	// retval = register_chrdev_region(mydev, MYCNT, MYNAME);
	retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
	// if (retval < 0)和下面的判斷等價
	if (retval) {
		// printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
		// return -EINVAL;
		printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
		goto flag1;
	}
	//printk(KERN_INFO "register_chrdev_region success\n");
	printk(KERN_INFO "alloc_chrdev_region success\n");
	printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev));
	
	// 第2步:註冊字元裝置驅動
	cdev_init(&test_cdev, &test_fops);
	retval = cdev_add(&test_cdev, mydev, MYCNT);
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add\n");
		// return -EINVAL;
		goto flag2;
	}
	printk(KERN_INFO "cdev_add success\n");
	
	/*
	// 模組安裝命令insmod時執行的硬體操作
	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	printk(KERN_INFO "GPJ0CON = %p.\n", GPJ0CON);
	printk(KERN_INFO "GPJ0DAT = %p.\n", GPJ0DAT);
	*/
	
	// 使用動態對映的方式來操作暫存器
	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步才出錯跳轉到這裡來	
flag4:
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);

// 如果第3步才出錯跳轉到這裡來
flag3:
	cdev_del(&test_cdev);

// 如果第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");
	// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	*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);
	*/
	
	// 使用新的介面來登出字元裝置驅動
	// 登出分2步:
	// 第一步真正登出字元裝置驅動用cdev_del
	cdev_del(&test_cdev);
	// 第二步去登出申請的主次裝置號
	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");			

使用cdev_alloc:

cdev_alloc的程式設計實踐;

從記憶體角度體會cdev_alloc用與不用的差別;

這就是非面向物件的語言和麵向物件的程式碼?

cdev_init的替代:

cdev_init原始碼分析;

不使用cdev_init時的程式設計;

為什麼講這個?

完整的相關程式碼見下述檔案(其他檔案等同於最後一段完整程式碼中的新述檔案):

module_test.c

// 為了module_init,module_exit相關的,加入下面標頭檔案
#include <linux/module.h>
// 為了__init,__exit相關的,加入下面標頭檔案
#include <linux/init.h>		
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <mach/gpio-bank.h>		
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.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;

// 核心空間的buf
char kbuf[100];	

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");
	memset(kbuf, 0, sizeof(kbuf));
	// 使用該函式將應用層傳過來的ubuf中的內容拷貝到驅動空間中的一個buf中
	// memcpy(kbuf, ubuf);不行,因為2個buf不在一個地址空間中
	
	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 (!strcmp(kbuf, "on"))
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (!strcmp(kbuf, "off"))
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	*/
	
	if (kbuf[0] == '1')
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (kbuf[0] == '0')
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	
	return 0;
}

// 自定義一個file_operations結構體變數,並且去填充
static const struct file_operations test_fops = {
	// 慣例,直接寫即可
	.owner		= THIS_MODULE,		
	// 將來應用open開啟這個裝置時實際呼叫的
	// 就是這個.open對應的函式
	.open		= test_chrdev_open,			
	.release	= test_chrdev_release,		
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};

// 模組安裝函式
static int __init chrdev_init(void)
{	
	int retval;
	printk(KERN_INFO "chrdev_init helloworld init\n");
	/*
	// 在module_init巨集呼叫的函式中去註冊字元裝置驅動
	// major傳0進去表示要讓核心幫我們自動分配一個合適的空白的沒被使用的主裝置號
	// 核心如果成功分配就會返回分配的主裝置號;如果分配失敗會返回負數
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
	*/
	
	// 使用新的cdev介面來註冊字元裝置驅動
	// 新的介面註冊字元裝置驅動需要2步
	

	// 第1步:註冊/分配主次裝置號
	// mydev = MKDEV(MYMAJOR, 0);
	// retval = register_chrdev_region(mydev, MYCNT, MYNAME);
	retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
	// if (retval < 0)和下面的判斷等價
	if (retval) {
		// printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
		// return -EINVAL;
		printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
		goto flag1;
	}
	//printk(KERN_INFO "register_chrdev_region success\n");
	printk(KERN_INFO "alloc_chrdev_region success\n");
	printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev));
	
	// 第2步:註冊字元裝置驅動
	// 給pcdev分配記憶體,指標例項化
	pcdev = cdev_alloc();
	//cdev_init(pcdev, &test_fops);
	pcdev->owner = THIS_MODULE;
	pcdev->ops = &test_fops;
	// retval = cdev_add(&test_cdev, mydev, MYCNT);
	retval = cdev_add(pcdev, mydev, MYCNT);
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add\n");
		// return -EINVAL;
		goto flag2;
	}
	printk(KERN_INFO "cdev_add success\n");
	
	/*
	// 模組安裝命令insmod時執行的硬體操作
	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	printk(KERN_INFO "GPJ0CON = %p.\n", GPJ0CON);
	printk(KERN_INFO "GPJ0DAT = %p.\n", GPJ0DAT);
	*/
	
	// 使用動態對映的方式來操作暫存器
	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步才出錯跳轉到這裡來	
flag4:
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);

// 如果第3步才出錯跳轉到這裡來
flag3:
	// cdev_del(&test_cdev);
	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");
	// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	*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);
	*/
	
	// 使用新的介面來登出字元裝置驅動
	// 登出分2步:
	// 第一步真正登出字元裝置驅動用cdev_del
	// cdev_del(&test_cdev);
	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");			

Makefile

# ubuntu的核心原始碼樹,如果要編譯在ubuntu中安裝的模組就開啟下面2個註釋
# KERN_VER = $(shell uname -r)
# KERN_DIR = /lib/modules/$(KERN_VER)/build	
 
# 開發板的linux核心的原始碼樹目錄
KERN_DIR = /root/driver/kernel
 
obj-m += module_test.o
 
all:
	make -C $(KERN_DIR) M=`pwd` modules 
	arm-linux-gcc app.c -o app
 
cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
	cp app /root/porting_x210/rootfs/rootfs/driver_test
	
.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf app

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
 
// 剛才mknod建立的裝置檔名
#define FILE	"/dev/test"
 
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;
}