字符設備程序實列一

1用戶空間無傳值給內核空間

本程序網絡根文件系統位於:/work/nfs_root/xyc_first_fs/
模塊文件位於:		/work/xyc_drivers_and_test
模塊源文件:first_drv.c
模塊文件first_drv.ko
測試文件: firsttest.c

    1.1 手工在設備上創建設備文件(即在設備上創建設備文件是手工輸入mknod命令的)

1.1.1 手工分配設備號

1.1.1.1 編寫驅動源文件first_drv.c

.編輯open, write對應的驅動程序first_drv_open,first_drv_write,first_drv_open ,first_drv_write只是打印一些信息
static int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}
static int first_drv_write(struct file *file, const char *data, size_t len, loff_t *ppos)

{
	printk("first_drv_write\n");
	return 0;
}
.定義並填充file_operations 結構體first_drv_fops:

static const struct file_operations first_drv_fops = {
	.owner		= THIS_MODULE,
	.write		= first_drv_write,
	.open		= first_drv_open,
};

.file_operations 結構體first_drv_fops掛載到鏈表中,鏈表索引爲111(即主設備號爲111),設備名填充爲xxx
用register_chrdev()字符設備註冊函數將主設備號和file_operations 結構體first_drv_fops聯繫起來,並在驅動入口(初始化)函數first_drv_init()調用register_chrdev()字符註冊函數
同理,用unregister_chrdev()字符設備卸載函數將主設備號和file_operations 結構體first_drv_fops分離,並釋放設備號,並在驅動入口(初始化)函數first_drv_exit()調用unregister_chrdev()字符卸載註冊函數
/*
 * 執行insmod命令時就會調用這個函數 
 */
static int __init first_drv_init(void)
{
    int ret;

    /* 註冊字符設備
     * 參數爲主設備號、設備名字、file_operations結構;
     * 這樣,主設備號就和具體的file_operations結構聯繫起來了,
     * 操作主設備爲LED_MAJOR的設備文件時,就會調用s3c24xx_leds_fops中的相關成員函數
     * LED_MAJOR可以設爲0,表示由內核自動分配主設備號
     */
   // ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
    ret = register_chrdev(111, "xxx", &first_drv_fops);// /* 加載模式後,執行”cat /proc/devices”命令看到的設備名稱 */
	
    if (ret < 0) {
      printk( " can't register major number\n");
      return ret;
    }
    
    printk( " initialized\n");
    return 0;
}

/*
 * 執行rmmod命令時就會調用這個函數 
 */
static void __exit first_drv_exit(void)
{
    /* 卸載驅動程序 */
    unregister_chrdev(111, "xxx");
}
insmod first_drv.ko後,cat /proc/devices可以看到 111  xxx這一字符設備,111主設備號,xxx爲設備名,是測試前cat  /proc/devices 沒被佔用的主設備號

.告知first_drv.ko模塊其入口和出口函數
insmod first_drv.ko/rmmod first_drv.ko 加載/卸載驅動模塊時還不知道first_drv.ko的入口函數和出口函數,因此需要告知內核,first_drv_init(),first_drv_exit()爲first_drv.ko的入口和出口函數,這樣insmod first_drv.ko時就會調用first_drv_init()執行驅動模塊的初始化,rmmod first_drv.ko調用first_drv_exit()執行驅動模塊的卸載
/* 這兩行指定驅動程序的初始化函數和卸載函數 */
module_init(first_drv_init);
module_exit(first_drv_exit);

.加上所有函數的頭文件:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

實際上所有設備在內存中表現爲以設備號爲鏈表的索引,xxx設備名和first_drv_fops爲鏈表填充內容,Ⅰ,Ⅱ,Ⅲ,Ⅳ,Ⅴ將first_drv_fops結構體掛載到鏈表中,鏈表索引爲111,填充的設備名爲xxx,填充first_drv_fops結構體爲open/write對應的驅動程序中的函數first_drv_open /first_drv_write的地址
當應用程序open/write  /dev/xyz時,通過主設備號,次設備號/設備種類(c字符設備)找到鏈表項,得到
first_drv_open()函數和first_drv_write()函數的地址,調用之
#ls -l /dev/xyz
crw-rw----    1 0        0        251,   0 Jan  1 00:44 /dev/xyz  
合起來Ⅰ,Ⅱ,Ⅲ,Ⅳ,Ⅴ的驅動源文件first_drv.c:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}
static int first_drv_write(struct file *file, const char *data, size_t len, loff_t *ppos)

{
	printk("first_drv_write\n");
	return 0;
}

static const struct file_operations first_drv_fops = {
	.owner		= THIS_MODULE,
	.write		= first_drv_write,
	.open		= first_drv_open,
};

/*
 * 執行insmod命令時就會調用這個函數 
 */
static int __init first_drv_init(void)
{
    int ret;

    /* 註冊字符設備
     * 參數爲主設備號、設備名字、file_operations結構;
     * 這樣,主設備號就和具體的file_operations結構聯繫起來了,
     * 操作主設備爲LED_MAJOR的設備文件時,就會調用s3c24xx_leds_fops中的相關成員函數
     * LED_MAJOR可以設爲0,表示由內核自動分配主設備號
     */
   // ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
    ret = register_chrdev(111, "xxx", &first_drv_fops);// /* 加載模式後,執行”cat /proc/devices”命令看到的設備名稱 */
	
    if (ret < 0) {
      printk( " can't register major number\n");
      return ret;
    }
    
    printk( " initialized\n");
    return 0;
}

/*
 * 執行rmmod命令時就會調用這個函數 
 */
static void __exit first_drv_exit(void)
{
    /* 卸載驅動程序 */
    unregister_chrdev(111, "xxx");
}

/* 這兩行指定驅動程序的初始化函數和卸載函數 */
module_init(first_drv_init);
module_exit(first_drv_exit);

1.1.1.2 編寫驅動模塊Makefile:

KERN_DIR = /work/system/linux-2.6.22.6

all:
        make -C $(KERN_DIR) M=`pwd` modules 

clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order

obj-m   += first_drv.o

KERN_DIR是內核存放的位置,這個內核和開發板flash啓動的內核是同一內核,如果此內核沒有製作,需要先製作:
#cd /work/system   //linux-2.6.22.6.tar.bz2內核源碼存放在/work/system
#tar xvjf linux-2.6.22.6.tar.bz2
#cd linux-2.6.22.6
#patch -p1 < ../linux-2.6.22.6_jz2440.patch
#cp config_ok .config
#make uImage
#cd /work/xyc_drivers_and_test 
#make
這時first_drv.ko驅動模塊已經製作好了,並且位於/work/xyc_drivers_and_test下,然後copy到開發板網絡根文件系統下:
#cp first_drv.ko  /work/nfs_root/xyc_first_fs/

1.1.1.3 編寫測試文件firsttest.c:

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <unistd.h>
  5 
  6 int main()
  7 {
  8         int fd1;
  9         fd1 = open("/dev/xyz", O_RDWR);
 10         if(fd1<0)
 11         printf("open failed\n");
 12         write(fd1, "aaa", 4);
 13         return 0;
 14 }

其中/dev/xyz爲設備文件名,register_chrdev(111, "xxx", &first_drv_fops)中的"xxx"爲設備名,編譯測試文件firsttest.c
#arm-linux-gcc -o firsttest firsttest.c
#cp firsttest /work/nfs_root/xyc_first_fs/

  1.1.1.4 測試驅動模塊:

在開發板根文件系統下:
#cat /proc/devices   //查看設備掛載了的主設備號和設備名
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 90 mtd
 99 ppdev
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c2410_serial
253 usb_endpoint
254 rtc

Block devices:
  1 ramdisk
  7 loop
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc

可以發現111沒被使用,故register_chrdev(111, "xxx", &first_drv_fops)註冊時可以使用111這個設備號。
#insmod first_drv.ko  //掛載驅動模塊
first_drv: module license 'unspecified' taints kernel.  //這裏警告是因爲驅動源文件沒有MODULE_LICENSE("GPL");這一行


 initialized
#lsmod first_drv.ko//查看驅動模塊是否掛載上
Module                  Size  Used by    Tainted: P  
first_drv               1824  0 
#cat /proc/devices   //此時可以查看到字符設備下有主設備號爲111,設備名爲"xxx"的設備了
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 90 mtd
 99 ppdev
111 xxx
#./firsttest
open failed   //失敗是因爲測試文件/dev/xyz 和"xxx"(爲主設備號爲111的設備名)設備還沒有聯繫起來,在"xxx"設備上創建設備文件/dev/xyz失敗,可以ls -l /dev/xyz可知此設備文件不//存在
#mknod /dev/xyz c 111 0  //在主設備號爲111,次設備號爲0的字符設備上創建設備文件/dev/xyz  。ls-l /dev/xyz可查看是否創建成功
#./firsttest   //運行測試文件,看是否打印出first_drv_open,first_drv_write
first_drv_open
first_drv_write

1.1.2 自動分配設備號

有了上面的基礎,程序主動分配設備號就容易了,大體改動 major= register_chrdev(0, "xxx", &first_drv_fops) //函數的第一個參數爲0表示自動分配,返回值爲分配的主設備號,同理unregister_chrdev(111, "xxx")需要改爲unregister_chrdev(major, "xxx") 就行了:
/*
 * 執行insmod命令時就會調用這個函數 
 */
 int major;
static int __init first_drv_init(void)
{


    /* 註冊字符設備
     * 參數爲主設備號、設備名字、file_operations結構;
     * 這樣,主設備號就和具體的file_operations結構聯繫起來了,
     * 操作主設備爲LED_MAJOR的設備文件時,就會調用s3c24xx_leds_fops中的相關成員函數
     * LED_MAJOR可以設爲0,表示由內核自動分配主設備號
     */
   // ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
    major= register_chrdev(0, "xxx", &first_drv_fops);// /* 加載模式後,執行”cat /proc/devices”命令看到的設備名稱 */
	
    if (major < 0) {
      printk( " can't register major number\n");
      return major;
    }
    
    printk( " initialized\n");
    return 0;
}

/*
 * 執行rmmod命令時就會調用這個函數 
 */
static void __exit first_drv_exit(void)
{
    /* 卸載驅動程序 */
    unregister_chrdev(major, "xxx");
}
unregister_chrdev(111, "xxx")改爲unregister_chrdev(major, "xxx") 不能忘記,否則cat /proc/devices還存在111 xxx的字符設備

編譯first_drv.c並將編譯出來的驅動模塊first_drv.ko拷貝到網絡根文件目錄/work/nfs_root/xyc_first_fs/下
測試驅動模塊:
#rmmod  first_drv.ko //先卸載,否則不會加載新編譯的first_drv.ko
#insmod first_drv.ko //加載新編譯的first_drv.ko
#cat /proc/devices  //查看major xxx的字符設備是否註冊上
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 90 mtd
 99 ppdev
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c2410_serial
252 xxx
可知xxx的主設備號爲252
#rm /dev/xyz   //刪除設備文件/dev/xyz  
#mknod /dev/xyz c 252 0  //在主設備號爲252,次設備號爲0的字符設備上創建設備文件/dev/xyz  。ls-l /dev/xyz可查看是否創建成功
#./firsttest   //運行測試文件,看是否打印出first_drv_open,first_drv_write
first_drv_open
first_drv_write

NOTE:1.自動分配設備號需要注意卸載函數的主設備號需要變更爲註冊函數得到的主設備號
    2.連續測試時,需要將同名驅動模塊 first_drv.ko卸載,在設備上建立的設備文件刪除 rm /dev/xyz 
    

1.2 自動在設備上創建設備文件

上面測試驅動模塊 first_drv.ko時,每次均用mknod 命令來實現在設備上創建設備文件,現在來講解自動在設備上創建設備文件的方法:
1.在函數體外定義類以及該類的設備:
static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev;

2.在入口函數中填充類和該類的設備(first_drv_fops註冊到major後)
    firstdrv_class = class_create(THIS_MODULE, "first_drv");
    firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");
	

3.在出口函數中卸載該類以及類下的設備(first_drv_fops和major卸載後)
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
整個驅動源代碼爲:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev;


static int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}
static int first_drv_write(struct file *file, const char *data, size_t len, loff_t *ppos)

{
	printk("first_drv_write\n");
	return 0;
}

static const struct file_operations first_drv_fops = {
	.owner		= THIS_MODULE,
	.write		= first_drv_write,
	.open		= first_drv_open,
};

/*
 * 執行insmod命令時就會調用這個函數 
 */
 int major;
static int __init first_drv_init(void)
{

    /* 註冊字符設備
     * 參數爲主設備號、設備名字、file_operations結構;
     * 這樣,主設備號就和具體的file_operations結構聯繫起來了,
     * 操作主設備爲LED_MAJOR的設備文件時,就會調用s3c24xx_leds_fops中的相關成員函數
     * LED_MAJOR可以設爲0,表示由內核自動分配主設備號
     */
   // ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
    major= register_chrdev(0, "xxx", &first_drv_fops);// /* 加載模式後,執行”cat /proc/devices”命令看到的設備名稱 */
	
    if (major < 0) {
      printk( " can't register major number\n");
      return major;
    }
    
    printk( " initialized\n");

    firstdrv_class = class_create(THIS_MODULE, "first_drv");
    firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");
	
    return 0;
}

/*
 * 執行rmmod命令時就會調用這個函數 
 */
static void __exit first_drv_exit(void)
{
    /* 卸載驅動程序 */
    unregister_chrdev(major, "xxx");
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}

/* 這兩行指定驅動程序的初始化函數和卸載函數 */
module_init(first_drv_init);
module_exit(first_drv_exit);
測試驅動模塊:
#rmmod first_drv.ko
#rm /dev/xyz
#insmod first_drv.ko
first_drv: Unknown symbol class_device_create
first_drv: Unknown symbol class_device_unregister
first_drv: Unknown symbol class_create
first_drv: Unknown symbol class_destroy
insmod: cannot insert 'first_drv.ko': Unknown symbol in module (-1): No such file or directory
是因爲缺少license,在驅動源程序最後加上下面一行,然後編譯並拷貝到網絡根文件系統下:
MODULE_LICENSE("GPL");
重新測試:
#insmod first_drv.ko
# ls -l /dev/xyz 
crw-rw----    1 0        0        251,   0 Jan  1 00:44 /dev/xyz    //可知在MKDEV(251, 0)字符設備上自動創建了設備文件/dev/xyz
#cat /proc/devices  //251定義的設備名爲xxx
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 90 mtd
 99 ppdev
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c2410_serial
251 xxx
<pre name="code" class="cpp">#
#./firsttest
first_drv_open
first_drv_write
# cd sys/class/
# ls
first_drv     mem           ppdev         scsi_host     usb_host
graphics      misc          printer       sound         vc
hwmon         mmc_host      rtc           spi_master    vtconsole
i2c-adapter   mtd           scsi_device   tty
input         net           scsi_disk     usb_endpoint
# cd first_drv/
# ls
xyz
# ls -l xyz/
-r--r--r--    1 0        0            4096 Jan  1 00:44 dev
lrwxrwxrwx    1 0        0               0 Jan  1 00:46 subsystem -> ../../../class/first_drv
--w-------    1 0        0            4096 Jan  1 00:46 uevent
# cd xyz/
# vi dev 



251:0



由上可知在sys下產生了類first_drv以及在該類上的設備文件xyz ,其中 dev文件記載了xyz設備文件儲存在設備號爲(251, 0)的設備上

2 用戶空間有傳值給內核空間

在本文以上所述中,應用程序中open找到驅動程序中的first_drv_open ,open調用實際上調用的是first_drv_open ,first_drv_open(),first_drv_write()並沒有做什麼事,現在需要點亮LED1 / LED2 /LED4。

2.1 同時點亮或熄滅3個LED

寫驅動的步驟如下:
1.搭建驅動的框架---可直接用first_drv.c源代碼的框架
2.完善硬件操作,硬件操作又分爲
   2.1 看原理圖  
   2.2 看芯片手冊
   2.3 寫代碼 -> 配置gpfcon,往gpfdat 地址寫值,不過驅動不能操作物理地址,只能用虛擬地址,用ioremap函數將物理地址轉換爲虛擬地址,裸板程序可以直接操作物理地址
第2步,用first_drv_open()配置GPF4 GPF5 GPF6爲輸出(對應LED1  LED2  LED4),根據write傳進來的值在 first_drv_write()中亮燈和滅燈
leds.c驅動代碼如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm-arm/io.h>
#include <asm-arm/uaccess.h>
#define DEVICE_NAME "leds"
int leds_major;

static struct class *leds_class;
static struct class_device *leds_class_dev;

static volatile unsigned long *gpfcon = NULL;
static volatile unsigned long *gpfdat =NULL;

/* 應用程序對設備文件/dev/leds執行open(...)時,
 * 就會調用s3c24xx_leds_open函數
 */
static int s3c24xx_leds_open(struct inode *inode, struct file *file)
{
    /*配置GPF4,GPF5,GPF6爲輸出引腳*/
    *gpfcon &= (~(0x3<<(4*2))) &(~(0x3<<(5*2)) ) & ( ~(0x3<<(6*2)) ) ;
    *gpfcon |=(0x1<<(4*2) ) | (0x1<<(5*2)) |(0x1<<(6*2));		
    return 0;
}

static int s3c24xx_leds_write(struct file *file, const char *data, size_t len, loff_t *ppos)
{
	char value;
	copy_from_user(&value, data, 1);

	if( value ){
		/*亮燈*/
			*gpfdat &= (~(0x1<<4)) & (~(0x1<<5)) & (~(0x1<<6)) ;
	}
	else{
		/*滅燈*/
			*gpfdat |=(0x1<<4) |(0x1<<5) | (0x1<<6) ;
	}	
	
	return 0;
}
static struct file_operations s3c24xx_leds_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open   =   s3c24xx_leds_open,     
    .write  =   s3c24xx_leds_write,
};

static int __init s3c24xx_leds_init(void)
{
	leds_major = register_chrdev(0, DEVICE_NAME, &s3c24xx_leds_fops);
      if (leds_major < 0) {
      printk( " can't register major number\n");
      return leds_major;
     }
     printk( " initialized\n");
	leds_class = class_create(THIS_MODULE, "leds");
	leds_class_dev = class_device_create(leds_class, NULL, MKDEV(leds_major, 0), NULL, "leds");
	
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon +1;
	return 0;
}

static void __exit s3c24xx_leds_exit(void)
{
	unregister_chrdev(leds_major, DEVICE_NAME);
       class_device_unregister(leds_class_dev);
	class_destroy(leds_class);
	iounmap(gpfcon);
}

module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit);
MODULE_LICENSE("GPL");

比上一節多了:
1:
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon +1;
2:
	iounmap(gpfcon);

3:
first_drv_open(),first_drv_write()函數不是純打印了,需要配置引腳,給引腳對應的寄存器寫值,

4:
first_drv_write()中還需要將write 中的val (對應data)傳給驅動中的value,不能直接傳,只能用copy_from_user()函數

測試代碼ledstest.c爲:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{

	int fd1;
	char val;
	fd1 = open("/dev/leds", O_RDWR);	
	if(fd1<0)
	printf("open failed\n");

	if(argc !=2){
		printf("Usage:\n");	
		printf("%s  <on|off>\n", argv[0]);
		return -1;	
	}
	if(strcmp(argv[1], "on") == 0){
		val =1;
		write(fd1, &val, 1);
	}
	else if(strcmp(argv[1], "off") == 0){
		val =0;
		write(fd1, &val, 1);

	}
	else	{
		printf("Usage:\n");	
		printf("%s  <on|off>\n", argv[0]);
		return -1;	
	}		
	return 0;
}

將測試代碼和驅動代碼分別編譯後拷貝到網絡根文件系統目錄下:
測試:
#insmod  leds.ko
#ls -l /dev/leds
crw-rw----    1 0        0        252,   0 Jan  1 00:27 /dev/leds
#./ledstest
Usage:
./ledstest <on|off>
#./ledstest oon
Usage:
./ledstest <on|off>
#./ledstest on   //測試3個LED是否全亮
#/ledstest off //測試3個LED是否全黑
#./ledstest on   //測試3個LED是否全亮
#/ledstest off //測試3個LED是否全黑

2.2 同時點亮/熄滅 ,分別點亮/熄滅

第1種方法是根據測試程序傳過來的值,在first_drv_write()判斷值並點亮相應的LED
第2種方法將leds建立爲4個設備節點,但是在測試程序中只打開其中的一個設備節點,然後在first_drv_write()函數中根據次設備號minor,判斷哪個設備節點打開,然後若有on/off動作就打開和熄滅相應的LED

leds.c源碼:


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm-arm/io.h>
#include <asm-arm/uaccess.h>
#define DEVICE_NAME "leds"
static int leds_major;

static struct class *leds_class;
static struct class_device *leds_class_dev[4];

static volatile unsigned long *gpfcon = NULL;
static volatile unsigned long *gpfdat =NULL;

/* 應用程序對設備文件/dev/leds執行open(...)時,
 * 就會調用s3c24xx_leds_open函數
 */
static int s3c24xx_leds_open(struct inode *inode, struct file *file)
{
    /*配置GPF4,GPF5,GPF6爲輸出引腳*/
    *gpfcon &= (~(0x3<<(4*2))) &(~(0x3<<(5*2)) ) & ( ~(0x3<<(6*2)) ) ;
    *gpfcon |=(0x1<<(4*2) ) | (0x1<<(5*2)) |(0x1<<(6*2));		
    return 0;
}

static int s3c24xx_leds_write(struct file *file, const char *data, size_t len, loff_t *ppos)
{
	char value;
	int minor = MINOR(file->f_dentry->d_inode->i_rdev);
	copy_from_user(&value, data, 1);
#if 0
	if( value ){
		/*亮燈*/
			*gpfdat &= (~(0x1<<4)) & (~(0x1<<5)) & (~(0x1<<6)) ;
	}
	else{
		/*滅燈*/
			*gpfdat |=(0x1<<4) |(0x1<<5) | (0x1<<6) ;
	}	
#endif
	switch(minor)
		{
			case 0:
				{
					if( value ){
						/*全亮燈*/
							*gpfdat &= (~(0x1<<4)) & (~(0x1<<5)) & (~(0x1<<6)) ;
					}
					else{
						/*全滅燈*/
							*gpfdat |=(0x1<<4) |(0x1<<5) | (0x1<<6) ;
					}
					break;
				}
			case 1:
				{
					if( value ){
						/*LED4 亮燈*/
							*gpfdat &= (~(0x1<<4));
					}
					else{
						/*LED4 滅燈*/
							*gpfdat |=(0x1<<4) ;
					}
					break;
				   }
			case 2:
				{
					if( value ){
						/*LED5 亮燈*/
							*gpfdat &= (~(0x1<<5))  ;
					}
					else{
						/*LED5 滅燈*/
							*gpfdat |=(0x1<<5);
					}
					break;
				   }
			case 3:
				{
					if( value ){
						/*LED6 亮燈*/
							*gpfdat &=(~(0x1<<6)) ;
					}
					else{
						/*LED6 滅燈*/
							*gpfdat |= (0x1<<6) ;
					}
					break;
				   }
			default:
					break;
		}
	return 0;
}

static struct file_operations s3c24xx_leds_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open   =   s3c24xx_leds_open,     
    .write  =   s3c24xx_leds_write,
};
/*
*4個設備文件澹:/dev/leds    控制LED4 LED5 LED6
*					   /dev/leds1  控制LED4
*					   /dev/leds2  控制LED5
*					   /dev/leds3  控制LED6
*/
static int __init s3c24xx_leds_init(void)
{
	int minor = 0;
	leds_major = register_chrdev(0, DEVICE_NAME, &s3c24xx_leds_fops);
      if (leds_major < 0) {
      printk( " can't register major number\n");
      return leds_major;
     }
     printk( " initialized\n");
	leds_class = class_create(THIS_MODULE, "leds");
	
	leds_class_dev[0] = class_device_create(leds_class, NULL, MKDEV(leds_major, minor), NULL, "leds");
	
	for( minor=1; minor<4; minor++){
	leds_class_dev[minor] = class_device_create(leds_class, NULL, MKDEV(leds_major, minor), NULL, "leds%d", minor);
	}
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon +1;
	return 0;
}

static void __exit s3c24xx_leds_exit(void)
{
	int minor;
	unregister_chrdev(leds_major, DEVICE_NAME);
	
	for(minor=0; minor<4; minor++){
       class_device_unregister(leds_class_dev[minor]);
	}	
	   
	class_destroy(leds_class);
	iounmap(gpfcon);
}

module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit);
MODULE_LICENS

測試源碼ledstest.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void  print_Usage(char *file)
{
		printf("Usage:\n");	
		printf("%s  <dev> <on|off>\n", file);
		printf("eg.\n");
		printf("%s  /dev/leds <on|off>\n", file);
		printf("%s  /dev/leds1 <on|off>\n", file);
		printf("%s  /dev/leds2 <on|off>\n", file);
		printf("%s  /dev/leds3 <on|off>\n", file);
}
int main(int argc, char *argv[])
{

	int fd1;
	char val;
	
	if(argc !=3){
		print_Usage(argv[0]);
		return -1;
	}
	fd1 = open(argv[1], O_RDWR);	
	if(fd1<0)
	printf("open %s failed\n", argv[1]);
	
		
		if(!strcmp(argv[2], "on")){
			val =1;
			write(fd1, &val, 1);
		}
		else if(strcmp(argv[2], "off") == 0){
			val =0;
			write(fd1, &val, 1);

		}
		else	{
			print_Usage(argv[0]);
			return -1;	
		}
		
	return 0;
}

將測試代碼和驅動代碼分別編譯後拷貝到網絡根文件系統目錄下:

# insmod leds.ko
 initialized
# ./ledstest /dev/leds on
# ./ledstest /dev/leds off
# ./ledstest /dev/leds1 on
# ./ledstest /dev/leds1 off
# ./ledstest /dev/leds on
# ./ledstest /dev/leds off
# ./ledstest /dev/leds1 on
# ./ledstest /dev/leds1 off
# ./ledstest /dev/leds2 on 
# ./ledstest /dev/leds2 off
# ./ledstest /dev/leds2 on 
# ./ledstest /dev/leds3 on
# ./ledstest /dev/leds1 on
# ./ledstest /dev/leds on 
# ./ledstest /dev/leds3 off 
# ./ledstest /dev/leds2 off
# ./ledstest /dev/leds3 on 
# ./ledstest /dev/ on
open /dev/ failed
# ./ledstest /dev/leds on
# ./ledstest /dev/leds off
# ./ledstest /dev/leds1 on
# ./ledstest /dev/leds1 off
# ./ledstest /dev/leds2 on 
# ./ledstest /dev/leds1 on
# ./ledstest /dev/leds2 off
# ./ledstest /dev/leds3 on 
# ./ledstest /dev/leds2 off
# ./ledstest /dev/leds2 on 
# ./ledstest /dev/leds1 off
# ./ledstest /dev/leds off 
# ./ledstest /dev/leds oft
Usage:
./ledstest  <dev> <on|off>
eg.
./ledstest  /dev/leds <on|off>
./ledstest  /dev/leds1 <on|off>
./ledstest  /dev/leds2 <on|off>
./ledstest  /dev/leds3 <on|off>
# 

測試看相應的結果:
NOTE:測試程序中只打開傳進來的一個設備文件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章