3.修改第一个程序来点亮LED

在上一节中已经将驱动程序框架搭建好了

接下来开始写硬件的操作(控制LED):

(1)看原理图,确定引脚

(2)看2440手册

(3)写代码(需要使用ioremap()函数映射虚拟地址,在linux中只能使用虚拟地址)

(4)修改上一节的测试程序

(5)使用次设备号来控制设备下不同的灯

1.看led引脚

最终确定: LED1 ->GPF4  LED2 ->GPF5   LED3 ->GPF6

2.看2440手册

配置GPFCON[15:0](0x56000050)的位[8:9]、位[10:11]、位[12:13] 都等于0x01(输出模式)

控制GPFDAT[7:0](0x56000054)中的位4~6来使灯亮灭(低电平亮)

3.写代码

3.1添加全局变量:

volatile unsigned long *GPFcon=NULL;       

volatile unsigned long *GPFdat=NULL;

3.2 first_drv_init入口函数中使用ioremap()映射虚拟地址:

GPFcon = ioremap(0x56000050, 16);   //ioremap:物理地址映射,返回虚拟地址

GPFdat=GPFcon+1;             //long:32位,所以GPFdat=0x56000050+(32/8)

3.3 first_drv_exit出口函数中注销虚拟地址:

iounmap(GPFcon);          //注销虚拟地址

3.4 first_drv_open函数中添加配置GPFCON:

*GPFcon&=~ ((0X11<<8)| (0X11<<10)| (0X11<<12)); 

*GPFcon|=    ((0X01<<8)| (0X01<<10)| (0X01<<12)); 

3.5 first_drv_write函数中添加拷贝应用层数据,然后来控制GPFDAT:

/*copy_to_user():将数据上给用户*/
copy_from_user(&val,buf,count);      //从用户(应用层)拷贝数据                                                            
 if(val==1)                  //点灯(低电平亮)
 {  
      *GPFdat&=~((0X1<<4)| (0X1<<5)| (0X1<<6)); 
 }
 else                  //灭灯
 {
     *GPFdat|=((0X1<<4)| (0X1<<5)| (0X1<<6));    
 }

4.修改测试程序main()

代码如下:

int main(int argc,char **argv) //argc:参数个数,argv数组
{
int fd1, fd2;
int val=1;
fd1 = open("/dev/xyz",O_RDWR);  //打开/dev/xxx设备节点
if(fd1<0)                   //无法打开,返回-1
  printf("can't open%d!\n", fd1); 
   if(argc!=2)
     {
            printf("Usage:\n");
            printf("%s <on|off>",argv[0]);
            return 0;
     }

  if(strcmp(argv[1],"on")==0)   //开灯
      {
          printf("led on...\n");
          val=1;
      } 
  else                         //关灯
      {
          printf("led off...\n");
          val=0;
      }

write(fd1, &val, 4);
return 0;
}

当输入first_driver_text on点3个灯, 否则关3个灯

若参数不等于2时,不能控制点灯

如果我们想分别控制不同的灯,该怎么做?

可以使用此设备号,此设备号就是用来区分同一设备下不同子设备

5使用次设备号来控制设备下不同的灯

我们先来看下面两个函数MAJOR和MINOR,分别是提取主次设备号

minor=MINOR(inode->i_rdev);    //open函数中提取次设备号
major=MAJOR(inode->i_rdev);    //open函数中提取主设备号

minor=MINOR (file->f_dentry->d_inode->i_rdev);  //write/read函数中提取次设备号
major= MAJOR (file->f_dentry->d_inode->i_rdev); //write/read函数中提取主设备号

思路如下:

在测试程序中:

通过dev[1]来open打开不同的子设备节点,然后通过dev[2]来write写入数据

实例: first_driver_text led1 on        //点亮led1

在first_dev.c驱动文件中:

first_drv_init函数中创建不同的子设备节点

first_drv_exti函数中注销不同的子设备节点

first_drv_open函数中通过MINOR(inode->i_rdev)来初始化不同的灯

first_drv_write函数中通过MINOR(file->f_dentry->d_inode->i_rdev)来控制不同的灯

如下图,insmod后自动注册3个设备节点

测试程序如下:     

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
 
 /*
  *  ledtest <dev> <on|off>
  */

void print_usage(char *file)         //报错打印帮助
{
    printf("Usage:\n");
    printf("%s <dev> <on|off>\n",file);
    printf("eg. \n");
    printf("%s /dev/leds on\n", file);
    printf("%s /dev/leds off\n", file);
    printf("%s /dev/led1 on\n", file);
    printf("%s /dev/led1 off\n", file);
} 

int main(int argc, char **argv)
{
    int fd;
    char* filename;
    char val;
        if (argc != 3)       
    {
        print_usage(argv[0]);
        return 0;
     }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("error, can't open %s\n", filename);
        return 0;
    }

    if (!strcmp("on", argv[2]))
    {
        // 亮灯
        val = 0;
        write(fd, &val, 1);
    }

    else if (!strcmp("off", argv[2]))
    {
        // 灭灯
        val = 1;
        write(fd, &val, 1);
    }
    else        //数据输入错误,打印帮助提示
    {
        print_usage(argv[0]);
        return 0;
    }     
    return 0;
}

驱动程序如下:

#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/uaccess.h>
#include <asm/io.h>
 

static struct class *firstdrv_class;               //创建一个class类
static struct class_device   *firstdrv_class_devs[4]; //创建类的设备,led,led1,led2,led3

 
volatile unsigned long *GPFcon=NULL;       
volatile unsigned long *GPFdat=NULL;

 

/*1写出驱动程序first_drv_open first_drv_write */
static int first_drv_open(struct inode *inode, struct file  *file)
{ 
  int minor=MINOR(inode->i_rdev); 
  printk("first_drv_open\n");      //打印,在内核中打印只能用printk() 
  GPFcon = ioremap(0x56000050, 16);   //ioremap:物理地址映射,返回虚拟地址
  GPFdat=GPFcon+1;                   //long:32位,所以GPFdat=0x56000050+(32/8)
       switch(minor)
   {
   case 0:                              //进入led设备,控制所有led
   *GPFcon&=~ ((0X3<<8)| (0X3<<10)| (0X3<<12)); 
   *GPFcon|=    ((0X01<<8)| (0X01<<10)| (0X01<<12)); 
        break;

   case 1:                              //进入led1设备,控制 led1
   *GPFcon&=~ ((0X3<<8) ); 
   *GPFcon|=    (0X1<<8) ; 
        break;     

   case 2:                                                 //进入led2设备,控制 led2
  *GPFcon&=~ ((0X3<<10) ); 
  *GPFcon|=  (0X1<<10) ; 
         break;

   case 3:                              //进入led3设备,控制 led3
   *GPFcon&=~ ((0X3<<12) ); 
   *GPFcon|=    ((0X1<<12) ); 
        break;
   }
   return 0;

}

 

/*参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界*/
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
       int val;
       int minor=MINOR(file->f_dentry->d_inode->i_rdev);
       copy_from_user(&val,buf,count);      //通过用户(应用层)拷贝数据                                                           
       switch(minor)
        {
        case 0:                                               //进入led设备,控制所有led
         printk("led0,%d\n",val);

        if(val)       //开灯
        {*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); 
        *GPFdat|=      ((0X0<<4)| (0X0<<5)| (0X0<<6));   }
        else     //关灯
          {*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); 
          *GPFdat|=      ((0X1<<4)| (0X1<<5)| (0X1<<6));   }
                break;
     
        case 1:                                               //进入led1设备,控制 led1
              printk("led1,%d\n",val);
               if(val)      //开灯
                     {*GPFdat&=~ (0X1<<4); 
                      *GPFdat|=      (0X0<<4);  }
                      else         //关灯
                     {  *GPFdat&=~  (0X1<<4); 
                             *GPFdat|=      (0X1<<4);  }
                break;
        
        case 2:                                         //进入led2设备,控制 led2
               printk("led2,%d\n",val);
               if(val)      //开灯
                      {*GPFdat&=~ (0X1<<5); 
                      *GPFdat|=      (0X0<<5);  }
                      else         //关灯
                             {*GPFdat&=~  (0X1<<5); 
                             *GPFdat|=      (0X1<<5);  }
              break;
      
        case 3:                                               //进入led3设备,控制 led3
              printk("led3,%d\n",val);                  
               if(val)      //开灯
                      {*GPFdat&=~ (0X1<<6); 
                      *GPFdat|=      ( 0X0<<6); }
                      else         //关灯
                             {*GPFdat&=~ (0X1<<6); 
                             *GPFdat|=      (0X1<<6);  }                
                break;       
        }
     return 0;
}
     
/*2定义file_operations结构体来封装驱动函数first_drv_open first_drv_write */
 static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,     //被使用时阻止模块被卸载
    .open   =   first_drv_open,     
    .write   =   first_drv_write,   
  };
 

int major;              //定义一个全局变量,用来保存主设备号
int first_drv_init(void)
{
    int i;                       
   /*3 register_chrdev注册字符设备*/
  /*如果设置major为0,表示由内核动态分配主设备号,函数的返回值是主设备号*/
  major=register_chrdev (0, "first_drv", &first_drv_fops);  
  firstdrv_class= class_create(THIS_MODULE,"firstdrv");  
//创建类,它会在sys目录下创建firstdrv这个类  

  firstdrv_class_devs[0]=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"led");
//创建类设备,它会在firstdrv_class类下创建led设备,然后mdev通过这个自动创建/dev/xyz这个设备节点
 
  for(i=1;i<4;i++)   //创建led1 led2 led3 设备节点,控制led1 led2 led3
 {
 firstdrv_class_devs[i]=class_device_create(firstdrv_class,NULL,MKDEV(major,i),NULL,"led%d",i);
 }
  return 0;
}

/*6 写first_drv_exit出口函数*/
void first_drv_exit(void)
{
 int i;
 unregister_chrdev (major, "first_drv");  //卸载驱动,只需要主设备号和设备名就行
 class_destroy(firstdrv_class);                      //注销类,与class_create对应

  for(i=0;i<4;i++)                              //注销类设备led,led1,led2,led3
  class_device_unregister(firstdrv_class_devs[i]);      
    iounmap(GPFcon);          //注销虚拟地址
}

/*5 module_init修饰入口函数*/
module_init(first_drv_init);
 
/*7 module_exit修饰出口函数*/
module_exit(first_drv_exit); 

MODULE_LICENSE("GPL v2");  //声明许可证

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊directory traversal attack

又称Path Traversal attack,即目录遍历攻击,旨在访问web服务器根目录外的文件/目录。通过是通过url或变量里头传递”../“来进行目录遍历...

1031
来自专栏葡萄城控件技术团队

Silverlight4控件纯客户端注册验证

本文实现了一个实验性的Silverlight控件纯客户端注册验证机制。希望做过这方面的朋友多给些指导性意见。 先给大家介绍一下Silverlight客户端...

1925
来自专栏草根专栏

从头编写 asp.net core 2.0 web api 基础框架 (1)

工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net c...

5436
来自专栏信安之路

webshell 常见 Bypass waf 技巧总结

对于很多,和我一样刚刚入门,或者还在门边徘徊的小伙伴们,在渗透学习的过程中,总会遇到各种情况,例如 php 大马被 waf 拦截的时候,那么如何制作免杀 php...

2070
来自专栏python爬虫日记

cxfreeze打包python程序的方法说明(生成安装包,实现桌面快捷方式、删除快捷方式)

python代码文件转exe方法有三种,分别是cx_freeze,py2exe,PyInstaller,这三种方式各有千秋,本人只用过py2exe和cxfree...

1194
来自专栏前端新视界

构建具有用户身份认证的 React + Flux 应用程序

React 的生态系统很大,为了解决 React 中比较困难的问题,你可以选择多种模块。大多数实际的 React 应用程序都有一些共同的需求,这些需求主要包括状...

1040
来自专栏IT笔记

关于Shiro登陆退出遇到的一些问题

写在开始 最近项目中出现一些问题,以前可能不大关注,但是问题是实实在在存在的。 ? 问题一 系统重启用户登陆或者退出报错:Disk Write of 407a1...

3126
来自专栏Linux驱动

第3阶段——内核启动分析之创建si工程和分析stext启动内核函数(4)

目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的  (3) 分析st...

1796
来自专栏Android 研究

APK安装流程详解1——有关"安装ing"的实体类概述

该类包含了从AndroidManifest.xml文件中收集的所有信息。 PackageInfo.java源码地址 通过源码我们知道PackageInfo是...

992
来自专栏程序员互动联盟

【专业技术】谷歌浏览器是如何编译出来的?

当我们编译chromium时,执行glient背后发生了什么?google为chromium的编译究竟做了哪些事情,我们通常并不需要亲自改这些编译规则,古人尚且...

3655

扫码关注云+社区