Chinaunix首页 | 论坛 | 博客
  • 博客访问: 445674
  • 博文数量: 111
  • 博客积分: 4290
  • 博客等级: 上校
  • 技术积分: 1301
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-24 14:22
个人简介

努力工作,建立一个幸福的家庭。

文章分类

全部博文(111)

文章存档

2015年(4)

2013年(9)

2012年(6)

2011年(17)

2010年(69)

2009年(6)

分类: LINUX

2010-05-29 16:33:03

Linux设备驱动程序学习(9)-与硬件通信
I/O 端口和 I/O 内存

每种外设都是通过读写寄存器来进行控制。

在硬件层,内存区和 I/O 区域没有概念上的区别: 它们都是通过向在地址总线和控制总线发出电平信号来进行访问,再通过数据总线读写数据。

因为外设要与I\O总线匹配,而大部分流行的 I/O 总线是基于个人计算机模型(主要是 x86 家族:它为读和写 I/O 端口提供了独立的线路和特殊的 CPU 指令),所以即便那些没有单独I/O 端口地址空间的处理器,在访问外设时也要模拟成读写I\O端口。这一功能通常由外围芯片组(PC 中的南北桥)或 CPU 中的附加电路实现(嵌入式中的方法)

Linux 在所有的计算机平台上实现了 I/O 端口。但不是所有的设备都将寄存器映射到 I/O 端口。虽然ISA设备普遍使用 I/O 端口,但大部分 PCI 设备则把寄存器映射到某个内存地址区,这种 I/O 内存方法通常是首选的。因为它无需使用特殊的处理器指令,CPU 核访问内存更有效率,且编译器在访问内存时在寄存器分配和寻址模式的选择上有更多自由
I/O 寄存器和常规内存

I/O 寄存器和 RAM 的主要不同就是 I/O 寄存器操作有side effect, 而内存操作没有。

因为存储单元的访问速度对 CPU 性能至关重要,编译器会对源代码进行优化,主要是: 使用高速缓存保存数值 和 重新编排读/写指令顺序。但对I/O 寄存器操作来说,这些优化可能造成致命错误。因此,驱动程序必须确保在操作I/O 寄存器时,不使用高速缓存,且不能重新编排读/写指令顺序。

解决方法:

硬件缓存问题:只要把底层硬件配置(自动地或者通过 Linux 初始化代码)成当访问 I/O 区域时(不管内存还是端口)禁止硬件缓存即可。

硬件指令重新排序问题:在硬件(或其他处理器)必须以一个特定顺序执行的操作之间设置内存屏障(memory barrier)。

Linux 提供以下宏来解决所有可能的排序问题:

#include <linux/kernel.h>
void barrier(void) /*告知编译器插入一个内存屏障但是对硬件没有影响。编译后的代码会将当前CPU 寄存器中所有修改过的数值保存到内存中, 并当需要时重新读取它们。可阻止在屏障前后的编译器优化,但硬件能完成自己的重新排序。其实 中并没有这个函数,因为它是在kernel.h包含的头文件compiler.h中定义的*/
#include <linux/compiler.h>
# define barrier() __memory_barrier()

#include <asm/system.h>
void rmb(void); /*保证任何出现于屏障前的读在执行任何后续的读之前完成*/
void wmb(void); /*保证任何出现于屏障前的写在执行任何后续的写之前完成*/
void mb(void); /*保证任何出现于屏障前的读写操作在执行任何后续的读写操作之前完成*/
void read_barrier_depends(void); /* 一种特殊的、弱些的读屏障形式。rmb 阻止屏障前后的所有读指令的重新排序,read_barrier_depends 只阻止依赖于其他读指令返回的数据的读指令的重新排序。区别微小, 且不在所有体系中存在。除非你确切地理解它们的差别, 并确信完整的读屏障会增加系统开销,否则应当始终使用 rmb。*/
/*以上指令是barrier的超集*/


void smp_rmb(void);
void smp_read_barrier_depends(void);
void smp_wmb(void);
void smp_mb(void);
/*仅当内核为 SMP 系统编译时插入硬件屏障; 否则, 它们都扩展为一个简单的屏障调用。*/


使用 I/O 端口

I/O 端口是驱动用来和许多设备之间的通讯方式。
I/O 端口分配
在尚未取得端口的独占访问前,不应对端口进行操作。内核提供了一个注册用的接口,允许驱动程序声明它需要的端口:

#include <linux/ioport.h>
struct resource *request_region(unsigned long first, unsigned long n, const char *name);/*告诉内核:要使用从 first 开始的 n 个端口,name 参数为设备名。若分配成功返回非 NULL,否则将无法使用需要的端口。*/
/*所有的的端口分配显示在 /proc/ioports 中。若不能分配到需要的端口,则可以到这里看看谁先用了。*/

/*当用完 I/O 端口集(可能在模块卸载时), 应当将它们返回给系统*/
void release_region(unsigned long start, unsigned long n);

int check_region(unsigned long first, unsigned long n);
/*检查一个给定的 I/O 端口集是否可用,若不可用, 返回值是一个负错误码。不推荐使用*/


操作 I/O 端口

在驱动程序注册I/O 端口后,就可以读/写这些端口。大部分硬件会把8、16和32位端口区分开,不能像访问系统内存那样混淆使用。驱动必须调用不同的函数来存取不同大小的端口。

只支持内存映射的 I/O 寄存器的计算机体系通过重新映射I/O端口到内存地址来伪装端口I/O。为了提高移植性,内核向驱动隐藏了这些细节。Linux 内核头文件(体系依赖的头文件 ) 定义了下列内联函数(有的体系是宏,有的不存在)来访问 I/O 端口:

unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
/*访问 32位 端口。 longword 声明有的平台为 unsigned long ,有的为 unsigned int。*/


平台相关性

 由于自身的特性,I/O 指令与处理器密切相关的,非常难以隐藏系统间的不同。所以大部分的关于端口 I/O 的源码是平台依赖的。以下是x86和ARM所使用函数的总结:

IA-32 (x86)

x86_64
这个体系支持所有的以上描述的函数,端口号是 unsigned short 类型。

ARM
端口映射到内存,支持所有函数。串操作 用C语言实现。端口是 unsigned int 类型

使用 I/O 内存

除了 x86上普遍使用的I/O 端口外,和设备通讯另一种主要机制是通过使用映射到内存的寄存器或设备内存,统称为 I/O 内存。因为寄存器和内存之间的区别对软件是透明的。I/O 内存仅仅是类似 RAM 的一个区域,处理器通过总线访问这个区域,以实现设备的访问。


根据平台和总线的不同,I/O 内存可以就是否通过页表访问分类。若通过页表访问,内核必须首先安排物理地址使其对设备驱动程序可见,在进行任何 I/O 之前必须调用 ioremap。若不通过页表,I/O 内存区域就类似I/O 端口,可以使用适当形式的函数访问它们。因为“side effect”的影响,不管是否需要 ioremap ,都不鼓励直接使用 I/O 内存的指针。而使用专用的 I/O 内存操作函数,不仅在所有平台上是安全,而且对直接使用指针操作 I/O 内存的情况进行了优化。

 
I/O 内存分配和映射

I/O 内存区域使用前必须先分配,函数接口在 定义:

struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);/* 从 start 开始,分配一个 len 字节的内存区域。成功返回一个非NULL指针,否则返回NULL。所有的 I/O 内存分配情况都 /proc/iomem 中列出。*/

/*I/O内存区域在不再需要时应当释放*/
void release_mem_region(unsigned long start, unsigned long len);

/*一个旧的检查 I/O 内存区可用性的函数,不推荐使用*/
int check_mem_region(unsigned long start, unsigned long len);


然后必须设置一个映射,由 ioremap 函数实现,此函数专门用来为I/O 内存区域分配虚拟地址。经过ioremap 之后,设备驱动即可访问任意的 I/O 内存地址。注意:ioremap 返回的地址不应当直接引用;应使用内核提供的 accessor 函数。以下为函数定义:

#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);/*如果控制寄存器也在该区域,应使用的非缓存版本,以实现side effect。*/
void iounmap(void * addr);


访问I/O 内存

访问I/O 内存的正确方式是通过一系列专用于此目的的函数(在 中定义的)

/*I/O 内存读函数*/
unsigned int ioread32(void *addr);
/*addr 是从 ioremap 获得的地址(可能包含一个整型偏移量), 返回值是从给定 I/O 内存读取的值*/

/*对应的I/O 内存写函数*/
void iowrite32(u32 value, void *addr);

/*读和写一系列值到一个给定的 I/O 内存地址,从给定的 buf 读或写 count 个值到给定的 addr */
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);

/*需要操作一块 I/O 地址,使用一下函数*/
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);


像 I/O 内存一样使用端口

一些硬件有一个有趣的特性:一些版本使用 I/O 端口,而其他的使用 I/O 内存。为了统一编程接口,使驱动程序易于编写,2.6 内核提供了一个ioport_map函数:

void *ioport_map(unsigned long port, unsigned int count);/*重映射 count 个I/O 端口,使其看起来像 I/O 内存。,此后,驱动程序可以在返回的地址上使用 ioread8 和同类函数。其在编程时消除了I/O 端口和I/O 内存的区别。

/*这个映射应当在它不再被使用时撤销:*/

void ioport_unmap(void *addr);

/*注意:I/O 端口仍然必须在重映射前使用 request_region 分配I/O 端口。ARM9不支持这两个函数!*/


=================================================================================
源代码:
1,驱动
IO_men.c

/*
 * main.c -- the bare scull char module
 *
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h>    /* printk() */
#include <linux/slab.h>        /* kmalloc() */
#include <linux/fs.h>        /* everything... */
#include <linux/errno.h>    /* error codes */
#include <linux/types.h>    /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>    /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <linux/sched.h> /* current and everything */

#include <linux/spinlock.h> /* spinlock_t */ // dw



#include <asm/system.h>        /* cli(), *_flags */
#include <asm/uaccess.h>    /* copy_*_user */
#include <asm/atomic.h> /* atomic_t */

#include "IO_mem.h"        /* local definitions */

/*
 * Our parameters which can be set at load time.
 */

int IO_mem_major = 0;
int IO_mem_minor = 0;

unsigned int gpjcon_old =0;
unsigned int gpjdat_old =0;
unsigned int gpjup_old =0;

unsigned long io_addr;

module_param(IO_mem_major, int, S_IRUGO);
module_param(IO_mem_minor, int, S_IRUGO);



struct IO_mem_dev *IO_mem_devices;    /* allocated in scull_init_module */
static atomic_t IO_mem_available = ATOMIC_INIT(1); // #define ATOMIC_INIT(i) { (i) } //用于在定义原子变量时,初始化为指定的值。

static spinlock_t IO_mem_lock = SPIN_LOCK_UNLOCKED; // 自旋锁初始化

static DECLARE_WAIT_QUEUE_HEAD(IO_mem_wait); // 生成一个等待队列头,名字为IO_men_wait

struct resource *IO_mem_resource; // 用来映射物理地址

/*
 * Open and close
 */


int IO_mem_open(struct inode *inode, struct file *filp)
{
    struct IO_mem_dev *dev; /* device information */

    spin_lock(&IO_mem_lock); // 进入临界区前,获得所需要的锁


    while (! atomic_dec_and_test (&IO_mem_available)) {
        atomic_inc(&IO_mem_available);
        spin_unlock(&IO_mem_lock);
        if (filp->f_flags & O_NONBLOCK) return -EAGAIN;
        if (wait_event_interruptible (IO_mem_wait, atomic_read (&IO_mem_available)))
            return -ERESTARTSYS; /* tell the fs layer to handle it */
        
        spin_lock(&IO_mem_lock);
    } // 原子操作 todo 《ldd3》P133


    spin_unlock(&IO_mem_lock);

    dev = container_of(inode->i_cdev, struct IO_mem_dev, cdev);
                                                                       
    io_addr =(unsigned long) ioremap_nocache(0x56000010 , 0x0c); //GPBCON 0x56000010 《s3c240 data》

    printk( "io_addr : %lx\n", io_addr);

    /* now trim to 0 the length of the device if open was write-only */
    gpjcon_old = ioread32 (io_addr); // 从I/O内存中读取

    gpjdat_old = ioread32 (io_addr+4);
    gpjup_old = ioread32 (io_addr+8);

    
    mb(); //加入内存屏障 保证上面的操作完成


    iowrite32(0x55555555,io_addr); // 设置端口为输出模式

    iowrite32(0x1fff,io_addr+8); // 关闭上拉功能

    
    wmb(); //why 上面的iowrite32() 和iowrite32(0x0,io_addr+4) 隔开


    iowrite32(0x00f0,io_addr+4); // 初始电平为高,关闭LED灯

    filp->private_data = dev; /* for other methods */

    return nonseekable_open(inode, filp); /* success */ //设备不支持 llseek

}

int IO_mem_release(struct inode *inode, struct file *filp)
{
    iowrite32((u32) gpjcon_old ,io_addr); // IO内存释放为什么这么表示?

    iowrite32((u32) gpjdat_old ,io_addr+4);
    iowrite32((u32) gpjup_old ,io_addr+8);

    iounmap((void *)io_addr); // 解除映射

//    ioport_unmap((void *)io_addr); /*WARNING: "ioport_unmap" undefined! Don't use it*/

    atomic_inc(&IO_mem_available); /* release the device */
    wake_up_interruptible_sync(&IO_mem_wait); /* awake other uid's */

    return 0;
}

/*
 * The ioctl() implementation
 */


int IO_mem_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{

    int err = 0;
    int retval = 0;
    unsigned int current_status;
      /*
     * extract the type and number bitfields, and don't decode
     * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
     */

    if (_IOC_TYPE(cmd) != IO_MEM_MAGIC) return -ENOTTY;
    if (_IOC_NR(cmd) > IO_MEM_MAXNR) return -ENOTTY;

    /*
     * the direction is a bitmask, and VERIFY_WRITE catches R/W
     * transfers. `Type' is user-oriented, while
     * access_ok is kernel-oriented, so the concept of "read" and
     * "write" is reversed
     */

    if (_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); // arg 用户空间地址

    if (err) return -EFAULT;

    switch(cmd) {

     case IO_MEM_0:
    iowrite32(0x0,io_addr+4); // 低电平LED亮

    break;
        
     case IO_MEM_1:
    iowrite32(0x0030,io_addr+4); // 亮7,8

    break;

     case IO_MEM_2:
    iowrite32(0x00c0,io_addr+4); // 亮4,5

    break;

     case IO_MEM_3:
    iowrite32(0x0800,io_addr+4); // 亮 4,5,6

    break;

    case IO_MEM_STATUS:
    current_status = ioread32 (io_addr+4);
    retval = __put_user(current_status, (unsigned int __user *)arg);
    break;

     default: /* redundant, as cmd was checked against MAXNR */
        return -ENOTTY;
    }
    return retval;

}

struct file_operations IO_mem_fops = {
    .owner = THIS_MODULE,
    .ioctl = IO_mem_ioctl,
    .open = IO_mem_open,
    .release = IO_mem_release,
    .llseek = no_llseek,

};

/*
 * Finally, the module stuff
 */


/*
 * The cleanup function is used to handle initialization failures as well.
 * Thefore, it must be careful to work correctly even if some of the items
 * have not been initialized
 */

void IO_mem_cleanup_module(void)
{
    dev_t devno = MKDEV(IO_mem_major, IO_mem_minor);

    if (IO_mem_resource!=NULL) release_mem_region(0x56000010, 0x0c);

    /* Get rid of our char dev entries */
    if (IO_mem_devices) {
        cdev_del(&IO_mem_devices->cdev);
        kfree(IO_mem_devices);
    }

    

    /* cleanup_module is never called if registering failed */
    unregister_chrdev_region(devno, 1);


}


/*
 * Set up the char_dev structure for this device.
 */

static void IO_mem_setup_cdev(struct IO_mem_dev *dev)
{
    int err, devno = MKDEV(IO_mem_major, IO_mem_minor);
    
    cdev_init(&dev->cdev, &IO_mem_fops);
    dev->cdev.owner = THIS_MODULE;
    err = cdev_add (&dev->cdev, devno, 1);
    /* Fail gracefully if need be */
    if (err)
        printk(KERN_NOTICE "Error %d adding IO_mem", err);
}


int IO_mem_init_module(void)
{
    int result;
    dev_t dev = 0;

/*
 * Get a range of minor numbers to work with, asking for a dynamic
 * major unless directed otherwise at load time.
 */

    if (IO_mem_major) {
        dev = MKDEV(IO_mem_major, IO_mem_minor);
        result = register_chrdev_region(dev, 1, "IO_mem");
    } else {
        result = alloc_chrdev_region(&dev, IO_mem_minor, 1,
                "IO_mem");
        IO_mem_major = MAJOR(dev);
    }
    if (result < 0) {
        printk(KERN_WARNING "IO_mem: can't get major %d\n", IO_mem_major);
        return result;
    }

        /*
     * allocate the devices -- we can't have them static, as the number
     * can be specified at load time
     */

    IO_mem_devices = kmalloc(sizeof(struct IO_mem_dev), GFP_KERNEL);
    if (!IO_mem_devices) {
        result = -ENOMEM;
        goto fail; /* Make this more graceful */
    }
    memset(IO_mem_devices, 0, sizeof(struct IO_mem_dev));

        /* Initialize each device. */
    init_MUTEX(&IO_mem_devices->sem);
    IO_mem_setup_cdev(IO_mem_devices);
    if ((IO_mem_resource=request_mem_region(0x56000010, 0x0c,"IO_mem"))==NULL)
        goto fail;
// #define request_mem_region(start, n,name) __request_region(&iomem_resource, (start), (n), (name))


    return 0; /* succeed */

  fail:
    IO_mem_cleanup_module();
    return result;
}

module_init(IO_mem_init_module);
module_exit(IO_mem_cleanup_module);

MODULE_AUTHOR("dengwei");
MODULE_LICENSE("Dual BSD/GPL");

/**************************************
GPJCON 0x560000D0 Port J control
应该可以用来驱动LED 因为是对I/O口的控制
GPBCON 0x56000010 [17:10]
GPBDAT 0x56000014
GPBUP 0x56000018
nLED1 GPB5
nLED2 GPB6
nLED3 GPB7
nLED4 GPB8
GPBDAT [10:0] GPB10~GPB0


************************************/

IO_men.h

#ifndef _FPGA_IO_H_
#define _FPGA_IO_H_

#include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */
#include </lib/modules/2.6.30.4-EmbedSky/build/arch/arm/mach-s3c2410/include/mach/hardware.h>
#include <asm/io.h> /* ioremap() */
#include <linux/ioport.h>
#include </lib/modules/2.6.30.4-EmbedSky/build/arch/arm/mach-s3c2410/include/mach/regs-gpio.h>
#include </lib/modules/2.6.30.4-EmbedSky/build/arch/arm/mach-s3c2410/include/mach/regs-gpioj.h>



struct IO_mem_dev {
    struct semaphore sem; /* mutual exclusion semaphore */
    struct cdev cdev;     /* Char device structure        */
};

/*
 * Split minors in two parts
 */

#define TYPE(minor)    (((minor) >> 4) & 0xf)    
/* high nibble */
#define NUM(minor)    ((minor) & 0xf)        
/* low nibble */


/*
 * The different configurable parameters
 */

extern int IO_mem_major; /* main.c */
extern int IO_mem_minor;



int IO_mem_ioctl(struct inode *inode, struct file *filp,
                    unsigned int cmd, unsigned long arg);


/*
 * Ioctl definitions
 */


/* Use 'k' as magic number */
#define IO_MEM_MAGIC 'k'
/* Please use a different 8-bit number in your code */

#define IO_MEM_0 _IO(IO_MEM_MAGIC, 0)
#define IO_MEM_1     _IO(IO_MEM_MAGIC, 1)
#define IO_MEM_2 _IO(IO_MEM_MAGIC, 2)
#define IO_MEM_3     _IO(IO_MEM_MAGIC, 3)
#define IO_MEM_STATUS     _IOR(IO_MEM_MAGIC, 4, int)

#define IO_MEM_MAXNR 4


#endif /* _SCULL_H_ */


测试文件:
IO_men_test.c

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

#include "IO_mem_test.h"
int main()
{
    int IO_memtest;
    int code;
    unsigned int current_status;
    char input=0 ;

    if ((IO_memtest = open("/dev/IO_mem",O_RDONLY ))<0)
        {

         printf("open IO_mem error!\n");
         exit(1);
        }
    if ( lseek(IO_memtest,0,SEEK_SET) == -1) printf("IO_mem: the module can not lseek!\n");
    for ( ; input != 'q' ; getchar()) 

   {
        printf("please input the command :");
        input= getchar();
        switch(input) {
        case '0':

             if ((code=ioctl( IO_memtest , IO_MEM_0 , NULL ) ) < 0) printf("IO_mem: ioctl IO_MEM_0 error! code=%d \n",code);

             else printf("IO_mem: ioctl 0 ok! \n");

             break;

         case '1':
             if ((code=ioctl( IO_memtest , IO_MEM_1 , NULL ) ) < 0) printf("IO_mem: ioctl IO_MEM_1 error! code=%d \n",code);
             else printf("IO_mem: ioctl 1 ok! \n");

             break;

    case '2':

             if ((code=ioctl( IO_memtest , IO_MEM_2 , NULL ) ) < 0) printf("IO_mem: ioctl IO_MEM_2 error! code=%d \n",code);
             else printf("IO_mem: ioctl 2 ok! \n");
             break;

    case '3':

             if ((code=ioctl( IO_memtest , IO_MEM_3 , NULL ) ) < 0) printf("IO_mem: ioctl IO_MEM_3 error! code=%d \n",code);
             else printf("IO_mem: ioctl 3 ok! \n");
             break;

     case '8':
             if ((code=ioctl( IO_memtest ,IO_MEM_STATUS , &current_status ) ) < 0) printf("IO_mem: ioctl IO_MEM_STATUS error! code=%d \n",code);
             else printf("IO_mem: ioctl STATUS ok! current_status=0X%x\n",current_status);

             break;

    case 'q':
        break;
        default: /* redundant, as cmd was checked against MAXNR */
        printf("IO_mem: Invalid input ! only 0,1,2,3,8,q !\n");

    }

}    

    close(IO_memtest);
 exit(0);

}

IO_men_test.h

/*
 * Ioctl definitions
 */


#include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */
/* Use 'k' as magic number */

#define IO_MEM_MAGIC 'k'
/* Please use a different 8-bit number in your code */

#define IO_MEM_0 _IO (IO_MEM_MAGIC, 0)
#define IO_MEM_1     _IO(IO_MEM_MAGIC, 1)
#define IO_MEM_2 _IO(IO_MEM_MAGIC, 2)
#define IO_MEM_3     _IO(IO_MEM_MAGIC, 3)
#define IO_MEM_STATUS     _IOR(IO_MEM_MAGIC, 4, int)

===================================================================================
试验结果:
[root@(none) IO_men-dw]# insmod IO_mem.ko
[root@(none) IO_men-dw]# cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
[root@(none) IO_men-dw]# cat /proc/devices |grep IO
252 IO_mem

[root@(none) IO_men-dw]# mknod -m 666 /dev/IO_men c 252 0

[root@(none) IO_mem_test]# ./IO_mem_test
io_addr : c4894010
IO_mem: the module can not lseek!
please input the command :1
IO_mem: ioctl 1 ok! 
please input the command :2
IO_mem: ioctl 2 ok! 
please input the command :1
IO_mem: ioctl 1 ok! 
please input the command :2
IO_mem: ioctl 2 ok! 
please input the command :3
IO_mem: ioctl 3 ok!
please input the command :3
观察LED灯的变化
按照实际的结果变化,哦耶!

===================================================================================
阅读(1961) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~