驅(qū)動的分層分離概念(platform)及實例講解(點亮led)

引言

分層就是將一個復(fù)雜的工作分成了4層, 分而做之,降低難度倒谷。每一層只專注于自己的事情, 系統(tǒng)已經(jīng)將其中的核心層和事件處理層寫好了骗绕,所以我們只需要來寫硬件相關(guān)的驅(qū)動層代碼即可。

分離是指把硬件相關(guān)的部分(驅(qū)動層)從純軟件部分(事件處理層)抽離出來,使我們只需要關(guān)注硬件相關(guān)部分代碼的編寫。具體來說就是在驅(qū)動層中使用platform機制(將所有設(shè)備掛接到一個虛擬的總線上,方便sysfs節(jié)點和設(shè)備電源的管理奔滑,使得驅(qū)動代碼,具有更好的擴展性和跨平臺性,就不會因為新的平臺而再次編寫驅(qū)動)把硬件相關(guān)的代碼(固定的,如板子的網(wǎng)卡顺少、中斷地址)和驅(qū)動(會根據(jù)程序作變動,如點哪一個燈)分離開來朋其,即要編寫兩個文件:dev.c和drv.c(platform設(shè)備和platform驅(qū)動)

image-20210717161931798

接下來我們來分析platform機制以及分離概念。

1脆炎、platform機制

bus-drv-dev模型
  • device設(shè)備:掛接在platform總線下的設(shè)備梅猿,屬于platform_device結(jié)構(gòu)體類型;

    • platform_device結(jié)構(gòu)體定義如下

    • struct platform_device {
          const char* name;   //設(shè)備名稱秒裕,要與platform_driver的name一樣,這樣總線才能匹配成功
          u32 id;   //id號,插入總線下相同name的設(shè)備編號(一個驅(qū)動可以有多個設(shè)備),如果只有一個設(shè)備填-1
          struct device dev;  //內(nèi)嵌的具體的device結(jié)構(gòu)體,其中成員platform_data,是個void *類型,可以給平臺driver提供各種數(shù)據(jù)(比如:GPIO引腳等等)
          u32 num_resources;      //資源數(shù)量袱蚓,
          struct resource  * resource;    //資源結(jié)構(gòu)體,保存設(shè)備的信息
      };
      
    • resource結(jié)構(gòu)體定義如下

    • struct resource {
               resource_size_t start;                    //起始資源,如果是地址的話,必須是物理地址
               resource_size_t end;                      //結(jié)束資源,如果是地址的話,必須是物理地址
               const char *name;                         //資源名
               unsigned long flags;                      //資源的標(biāo)志
               //比如IORESOURCE_MEM,表示地址資源, IORESOURCE_IRQ表示中斷引腳... ...
      
               struct resource *parent, *sibling, *child;   //資源拓撲指針父、兄几蜻、子,可以構(gòu)成鏈表
      };
      
    • 涉及到的函數(shù)如下(在dev設(shè)備的入口出口函數(shù)中用到)

    • int platform_device_register(struct platform_device * pdev);//注冊dev設(shè)備
      int platform_device_unregister(struct platform_device * pdev);//注銷dev設(shè)備
      
  • driver驅(qū)動:也是掛接在platform總線下喇潘,與某類設(shè)備相應(yīng)的驅(qū)動程序,屬于platform_driver結(jié)構(gòu)體類型梭稚;

    • platform_driver(驅(qū)動)結(jié)構(gòu)體的定義

    • struct platform_driver {
          int (*probe)(struct platform_device *);       //查詢設(shè)備的存在
          int (*remove)(struct platform_device *);             //刪除
          void (*shutdown)(struct platform_device *);         //斷電
          int (*suspend)(struct platform_device *, pm_message_t state);  //休眠
          int (*suspend_late)(struct platform_device *, pm_message_t state);
          int (*resume_early)(struct platform_device *);
          int (*resume)(struct platform_device *);           //喚醒
          struct device_driver driver;       //內(nèi)嵌的driver,其中的name成員要等于設(shè)備的名稱才能匹配
      };
      
    • 定義一個platform_driver(驅(qū)動)示例

    • struct platform_driver gpio_keys_device_driver = {
          .probe  = gpio_keys_probe,   //設(shè)備的檢測,當(dāng)匹配成功就會調(diào)用這個函數(shù)(需要自己編寫)   
          .remove = __devexit_p(gpio_keys_remove), //刪除設(shè)備(需要自己編寫)
          .driver = {
              .name  = "gpio-keys",            //驅(qū)動名稱,用來與設(shè)備名稱匹配用的
          }
      };
      
    • 涉及到的函數(shù)如下

    • //位于init入口函數(shù)中颖低,用來注冊driver驅(qū)動
      int platform_driver_register(struct platform_driver *drv)
      {
         drv->driver.bus = &platform_bus_type;    //(1)掛接到虛擬總線platform_bus_type上
         if (drv->probe)
               drv->driver.probe = platform_drv_probe;
         if (drv->remove)
              drv->driver.remove = platform_drv_remove;
         if (drv->shutdown)
              drv->driver.shutdown = platform_drv_shutdown;
         if (drv->suspend)
              drv->driver.suspend = platform_drv_suspend;
          if (drv->resume)
              drv->driver.resume = platform_drv_resume;
      
         return driver_register(&drv->driver);        //注冊到driver目錄下
      }
      
      
      int platform_driver_unregister(struct platform_driver *drv); //位于exit出口函數(shù)中,用來卸載驅(qū)動
      
      struct resource * platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num);
      //獲取設(shè)備的某個資源,獲取成功,則返回一個resource資源結(jié)構(gòu)體
      //參數(shù):
      // *dev :指向某個platform device設(shè)備
      // type:獲取的資源類型
      // num: type資源下的第幾個數(shù)組
      
  • platform總線:屬于虛擬設(shè)備總線(全局變量)弧烤,屬于platform_bus_type類型忱屑,正是通過這個總線將設(shè)備和驅(qū)動聯(lián)系了起來,屬于Linux中bus的一種。

    • platform_match()匹配函數(shù)

    • static int platform_match(struct device * dev, struct device_driver * drv)
      {
          /*找到所有的device設(shè)備*/
          struct platform_device *pdev = container_of(dev, struct platform_device, dev);
      
          return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); //找BUS_ID_SIZE次
      }
      
    • 通過查看/sys/bus/platform/目錄莺戒,可以看到兩個文件:devices和drivers伴嗡。它們分別用來存放platform的設(shè)備和驅(qū)動。無論驅(qū)動或設(shè)備从铲,只要有一方注冊,就會調(diào)用platform_bus_type的.match匹配函數(shù)來尋找對方瘪校。如果匹配成功,就調(diào)用driver驅(qū)動結(jié)構(gòu)體里的.probe函數(shù)來使總線將設(shè)備和驅(qū)動聯(lián)系起來食店。

platform_bus_type的結(jié)構(gòu)體定義如下所示(位于drivers/base):

struct bus_type platform_bus_type = {              
.name         = "platform",         //設(shè)備名稱
.dev_attrs    = platform_dev_attrs, //設(shè)備屬性渣淤、含獲取sys文件名,該總線會放在/sys/bus下
.match        = platform_match,     //匹配設(shè)備和驅(qū)動,匹配成功就調(diào)用driver的.probe函數(shù)
.uevent       = platform_uevent,    //消息傳遞,比如熱插拔操作
.suspend      = platform_suspend,   //電源管理的低功耗掛起
.suspend_late = platform_suspend_late,  
.resume_early = platform_resume_early,
.resume       = platform_resume,  //恢復(fù)
};

2、利用platform機制吉嫩,編寫LED驅(qū)動層

  • 環(huán)境:JZ2440開發(fā)板V3 + linux內(nèi)核3.4.2 + arm-linux-gcc 4.3.2
  • 參考:韋東山第2期視頻

2.1 創(chuàng)建設(shè)備代碼

  • 創(chuàng)建led_dev.c文件:用來指定燈的引腳地址,當(dāng)更換平臺時只需要修改這個就行嗅定。
#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>

static struct resource led_resource[] = { //資源數(shù)組,用來保存設(shè)備的信息
    [0] = {
        .start = 0x56000050,          //led的寄存器GPFCON起始地址
        .end   = 0x56000050 + 8 - 1,  // led的寄存器GPFDAT結(jié)束地址
        .flags = IORESOURCE_MEM,      //表示地址資源
    },
    [1] = {
        .start =  5,                  //表示GPF第幾個引腳開始
        .end   = 5,                   //結(jié)束引腳
        .flags = IORESOURCE_IRQ,      //表示中斷資源    
    } 
};

//釋放函數(shù)
static void led_release(struct device * dev)
{
 //釋放函數(shù)自娩,必須向內(nèi)核提供一個release函數(shù), 否則卸載時,內(nèi)核找不到該函數(shù)會報錯。   
}


/*分配渠退、設(shè)置一個LED設(shè)備 */
static struct platform_device led_dev = {
    .name   = "myled",  //對應(yīng)的platform_driver驅(qū)動的名字
    .id     = -1,       //表示只有一個設(shè)備
    .num_resources  = ARRAY_SIZE(led_resource), //資源數(shù)量,ARRAY_SIZE()函數(shù):獲取數(shù)量
    .resource   = led_resource, //資源數(shù)組led_resource
    .dev = {    
        .release = led_release,
    },
};

//入口函數(shù),注冊dev設(shè)備
static int led_dev_init(void)
{
  platform_device_register(&led_dev);
  return 0;
}

//出口函數(shù),注銷dev設(shè)備
static void led_dev_exit(void) 
{
  platform_device_unregister(&led_dev); 
}
module_init(led_dev_init);   //修飾入口函數(shù)
module_exit(led_dev_exit);  //修飾出口函數(shù)
MODULE_LICENSE("GPL");   //聲明函數(shù)

2.2 創(chuàng)建驅(qū)動代碼

  • 創(chuàng)建led_drv.c文件:用來初始化燈以及如何控制燈的邏輯,當(dāng)更換控制邏輯時,只需要修改這個就行忙迁。

    • 1. 分配、設(shè)置一個platform_driver結(jié)構(gòu)體

    • #include <linux/module.h>
      #include <linux/version.h>
      
      #include <linux/init.h>
      #include <linux/fs.h>
      #include <linux/interrupt.h>
      #include <linux/irq.h>
      #include <linux/sched.h>
      #include <linux/pm.h>
      #include <linux/sysctl.h>
      #include <linux/proc_fs.h>
      #include <linux/delay.h>
      #include <linux/platform_device.h>
      
      #include <linux/input.h>
      #include <asm/uaccess.h>
      #include <asm/irq.h>
      #include <asm/io.h>
           
      /* 1)先寫要注冊的led驅(qū)動:platform_driver結(jié)構(gòu)體*/
      
      /*函數(shù)聲明*/
      static  int led_remove(struct platform_device *led_dev);
      static  int led_probe(struct platform_device *led_dev);
      
      struct platform_driver led_drv = {
          .probe  = led_probe,        //當(dāng)與設(shè)備匹配,則調(diào)用該函數(shù)
          .remove = led_remove,       //刪除設(shè)備
          .driver = {
              .name     = "myled",    //與設(shè)備名稱一樣
          }
      };
      
    • 編寫file_operations 結(jié)構(gòu)體以及成員函數(shù)(.open碎乃、.write)

    • static struct class *cls;                //類,用來注冊,和注銷
      static volatile unsigned long *gpio_con; //被file_operations的.open函數(shù)用
      static volatile unsigned long *gpio_dat; //被file_operations的.write函數(shù)用
      static int pin;                          //LED位于的引腳值
      
      static int led_open(struct inode *inode, struct file  *file)
       {
             *gpio_con &= ~(0x03<<(pin*2));
             *gpio_con |= (0x01<<(pin*2));   
             return 0;
       } 
      
      static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
      {
             char val=0;
             if(count!=1)
                 return -EINVAL;
             copy_from_user(&val,buf,count);      //從用戶(應(yīng)用層)拷貝數(shù)據(jù)       
      
             if(val)      //開燈
             {
                  *gpio_dat &= ~(0x1<<pin);
             }
             else
             {
                  *gpio_dat |= (0x1<<pin);
             }   
             return 0 ;
      }
      
      
      static struct  file_operations led_fops= {
          .owner  =   THIS_MODULE,     //被使用時阻止模塊被卸載
          .open   =   led_open,     
          .write  =   led_write,   
        };
      
    • 編寫.probe函數(shù)

    • /* 當(dāng)驅(qū)動和設(shè)備都insmod加載后,然后bus總線會匹配成功,就進入.probe函數(shù).
       * 里面的內(nèi)容沒有固定要求姊扔,你可以根據(jù)需要自行添加。
       */
      static int led_probe(struct platform_device *pdev)
      {
             printk("enter probe\n"); //調(diào)試用
      
             /* 1.根據(jù)platform_device的資源進行ioremap */
             struct resource  *res;
             res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲取LED寄存器地址
             gpio_con = ioremap(res->start, res->end - res->start + 1); //獲取虛擬地址
             gpio_dat = gpio_con + 1;
      
             res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);   //獲取LED引腳值
             pin = res->start;
      
             /* 2.注冊字符設(shè)備驅(qū)動程序 */
             major = register_chrdev(0, "myled", &led_fops);   //賦入file_operations結(jié)構(gòu)體
             cls = class_create(THIS_MODULE, "myled");
             device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
             return 0;
      }
      
    • 編寫.remove函數(shù)

    • /* 如果驅(qū)動與設(shè)備已聯(lián)系起來,當(dāng)卸載驅(qū)動時,就會調(diào)用.remove函數(shù)卸載設(shè)備 */
      static int led_remove(struct platform_device *pdev)
      {
             printk("enter remove\n"); //調(diào)試用
          
             /* 1.卸載字符設(shè)備驅(qū)動程序 */
             device_destroy(cls, MKDEV(major, 0));
             class_destroy(cls);
             unregister_chrdev(major, "myled");
      
             iounmap(gpio_con);       //注銷虛擬地址
             return 0;
      }
      
    • 編寫drv的入口出口函數(shù)

    • static int led_drv_init(void)   //入口函數(shù),注冊驅(qū)動
      {
          platform_driver_register(&led_drv);
          return 0;
      } 
      static void led_drv_exit(void)  //出口函數(shù),卸載驅(qū)動
      {
          platform_driver_unregister(&led_drv);
      }
      
      module_init(led_drv_init);     
      module_exit(led_drv_exit);
      MODULE_LICENSE("GPL");
      

2.3 創(chuàng)建Make file 文件

  • KERN_DIR = /home/leon/linux-3.4.2
    
    all:
      make -C $(KERN_DIR) M=`pwd` modules 
    
    clean:
      make -C $(KERN_DIR) M=`pwd` modules clean
      rm -rf modules.order
    
    obj-m += led_drv.o
    obj-m += led_dev.o
    

2.4 創(chuàng)建測試文件

當(dāng)用戶輸入led_test on時梅誓,LED點亮恰梢;輸入led_test off時,LED熄滅梗掰。

  • #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    /* led_test on
     * led_test off
    */
    int main(int argc, char **argv)
    {
      int fd;
      char val = 1;
      fd = open("/dev/led", O_RDWR);
      if (fd < 0)
      {
          printf("can't open!\n");
      }
      if (argc != 2)
      {
          printf("Usage :\n");
          printf("%s <on|off>\n", argv[0]);
          return 0;
      }
    
      if (strcmp(argv[1], "on") == 0)
      {
          val  = 1;
      }
      else
      {
          val = 0;
      }
      
      write(fd, &val, 1); //將從變量val地址起始處的4個字節(jié)傳給驅(qū)動
      return 0;
    }
    

2.5 實驗結(jié)果

image-20210717234851686

3嵌言、小結(jié):

分層分離驅(qū)動
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市及穗,隨后出現(xiàn)的幾起案子摧茴,更是在濱河造成了極大的恐慌,老刑警劉巖埂陆,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苛白,死亡現(xiàn)場離奇詭異,居然都是意外死亡焚虱,警方通過查閱死者的電腦和手機购裙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來著摔,“玉大人缓窜,你說我怎么就攤上這事。” “怎么了禾锤?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵私股,是天一觀的道長。 經(jīng)常有香客問我恩掷,道長倡鲸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任黄娘,我火速辦了婚禮峭状,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逼争。我一直安慰自己优床,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布誓焦。 她就那樣靜靜地躺著胆敞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪杂伟。 梳的紋絲不亂的頭發(fā)上移层,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音赫粥,去河邊找鬼观话。 笑死,一個胖子當(dāng)著我的面吹牛越平,可吹牛的內(nèi)容都是我干的频蛔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喧笔,長吁一口氣:“原來是場噩夢啊……” “哼帽驯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起书闸,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤尼变,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后浆劲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫌术,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年牌借,在試婚紗的時候發(fā)現(xiàn)自己被綠了度气。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡膨报,死狀恐怖磷籍,靈堂內(nèi)的尸體忽然破棺而出适荣,到底是詐尸還是另有隱情,我是刑警寧澤院领,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布弛矛,位于F島的核電站,受9級特大地震影響比然,放射性物質(zhì)發(fā)生泄漏丈氓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一强法、第九天 我趴在偏房一處隱蔽的房頂上張望万俗。 院中可真熱鬧,春花似錦饮怯、人聲如沸闰歪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽课竣。三九已至,卻和暖如春置媳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背公条。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工拇囊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人靶橱。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓寥袭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親关霸。 傳聞我的和親對象是個殘疾皇子传黄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容