1. gzyueqian
      13352868059
      首頁 > 新聞中心 > > 正文

      ARM的嵌入式Linux移植體驗(yàn)之設(shè)備驅(qū)動(dòng)

      更新時(shí)間: 2008-07-24 14:13:27來源: 粵嵌教育瀏覽量:1077

        設(shè)備驅(qū)動(dòng)程序是操作系統(tǒng)內(nèi)核和機(jī)器硬件之間的接口,它為應(yīng)用程序屏蔽硬件的細(xì)節(jié),一般來說,Linux的設(shè)備驅(qū)動(dòng)程序需要完成如下功能:

        ·設(shè)備初始化、釋放;

        ·提供各類設(shè)備服務(wù);

        ·負(fù)責(zé)內(nèi)核和設(shè)備之間的數(shù)據(jù)交換;

        ·檢測和處理設(shè)備工作過程中出現(xiàn)的錯(cuò)誤。

        Linux下的設(shè)備驅(qū)動(dòng)程序被組織為一組完成不同任務(wù)的函數(shù)的集合,通過這些函數(shù)使得Windows的設(shè)備操作猶如文件一般。在應(yīng)用程序看來,硬件設(shè)備只是一個(gè)設(shè)備文件,應(yīng)用程序可以象操作普通文件一樣對(duì)硬件設(shè)備進(jìn)行操作,如open ()、close ()、read ()、write () 等。

        Linux主要將設(shè)備分為二類:字符設(shè)備和塊設(shè)備。字符設(shè)備是指設(shè)備發(fā)送和接收數(shù)據(jù)以字符的形式進(jìn)行;而塊設(shè)備則以整個(gè)數(shù)據(jù)緩沖區(qū)的形式進(jìn)行。在對(duì)字符設(shè)備發(fā)出讀/寫請(qǐng)求時(shí),實(shí)際的硬件I/O一般就緊接著發(fā)生了;而塊設(shè)備則不然,它利用一塊系統(tǒng)內(nèi)存作緩沖區(qū),當(dāng)用戶進(jìn)程對(duì)設(shè)備請(qǐng)求能滿足用戶的要求,就返回請(qǐng)求的數(shù)據(jù),如果不能,就調(diào)用請(qǐng)求函數(shù)來進(jìn)行實(shí)際的I/O操作。塊設(shè)備主要針對(duì)磁盤等慢速設(shè)備。

        1.內(nèi)存分配

        由于Linux驅(qū)動(dòng)程序在內(nèi)核中運(yùn)行,因此在設(shè)備驅(qū)動(dòng)程序需要申請(qǐng)/釋放內(nèi)存時(shí),不能使用用戶級(jí)的malloc/free函數(shù),而需由內(nèi)核級(jí)的函數(shù)kmalloc/kfree () 來實(shí)現(xiàn),kmalloc()函數(shù)的原型為:





      void kmalloc (size_t size ,int priority);

        參數(shù)size為申請(qǐng)分配內(nèi)存的字節(jié)數(shù),kmalloc多只能開辟128k的內(nèi)存;參數(shù)priority說明若kmalloc()不能馬上分配內(nèi)存時(shí)用戶進(jìn)程要采用的動(dòng)作:GFP_KERNEL 表示等待,即等kmalloc()函數(shù)將一些內(nèi)存安排到交換區(qū)來滿足你的內(nèi)存需要,GFP_ATOMIC 表示不等待,如不能立即分配到內(nèi)存則返回0 值;函數(shù)的返回值指向已分配內(nèi)存的起始地址,出錯(cuò)時(shí),返回0。

        kmalloc ()分配的內(nèi)存需用kfree()函數(shù)來釋放,kfree ()被定義為:





      # define kfree (n) kfree_s( (n) ,0)

        其中kfree_s () 函數(shù)原型為:





      void kfree_s (void * ptr ,int size);

        參數(shù)ptr為kmalloc()返回的已分配內(nèi)存的指針,size是要釋放內(nèi)存的字節(jié)數(shù),若為0 時(shí),由內(nèi)核自動(dòng)確定內(nèi)存的大小。

        2.中斷

        許多設(shè)備涉及到中斷操作,因此,在這樣的設(shè)備的驅(qū)動(dòng)程序中需要對(duì)硬件產(chǎn)生的中斷請(qǐng)求提供中斷服務(wù)程序。與注冊(cè)基本入口點(diǎn)一樣,驅(qū)動(dòng)程序也要請(qǐng)求內(nèi)核將特定的中斷請(qǐng)求和中斷服務(wù)程序聯(lián)系在一起。在Linux中,用request_irq()函數(shù)來實(shí)現(xiàn)請(qǐng)求:





      int request_irq (unsigned int irq ,void( * handler) int ,unsigned long type ,char * name);



        參數(shù)irq為要中斷請(qǐng)求號(hào),參數(shù)handler為指向中斷服務(wù)程序的指針,參數(shù)type 用來確定是正常中斷還是快速中斷(正常中斷指中斷服務(wù)子程序返回后,內(nèi)核可以執(zhí)行調(diào)度程序來確定將運(yùn)行哪一個(gè)進(jìn)程;而快速中斷是指中斷服務(wù)子程序返回后,立即執(zhí)行被中斷程序,正常中斷type 取值為0 ,快速中斷type 取值為SA_INTERRUPT),參數(shù)name是設(shè)備驅(qū)動(dòng)程序的名稱。

      4.塊設(shè)備驅(qū)動(dòng)

        塊設(shè)備驅(qū)動(dòng)程序的編寫是一個(gè)浩繁的工程,其難度遠(yuǎn)超過字符設(shè)備,上千行的代碼往往只能搞定一個(gè)簡單的塊設(shè)備,而數(shù)十行代碼就可能搞定一個(gè)字符設(shè)備。因此,非得有相當(dāng)?shù)幕竟Σ拍芡瓿纱隧?xiàng)工作。下面先給出一個(gè)實(shí)例,即mtdblock塊設(shè)備的驅(qū)動(dòng)。我們通過分析此實(shí)例中的代碼來說明塊設(shè)備驅(qū)動(dòng)程序的寫法(由于篇幅的關(guān)系,大量的代碼被省略,只保留了必要的主干):





      #include <Linux/config.h>
      #include <Linux/devfs_fs_kernel.h>
      static void mtd_notify_add(struct mtd_info* mtd);
      static void mtd_notify_remove(struct mtd_info* mtd);
      static struct mtd_notifier notifier = {
       mtd_notify_add,
       mtd_notify_remove,
       NULL
      };
      static devfs_handle_t devfs_dir_handle = NULL;
      static devfs_handle_t devfs_rw_handle[MAX_MTD_DEVICES];

      static struct mtdblk_dev {
       struct mtd_info *mtd; /* Locked */
       int count;
       struct semaphore cache_sem;
       unsigned char *cache_data;
       unsigned long cache_offset;
       unsigned int cache_size;
       enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;
      } *mtdblks[MAX_MTD_DEVICES];

      static spinlock_t mtdblks_lock;
      /* this lock is used just in kernels >= 2.5.x */
      static spinlock_t mtdblock_lock;

      static int mtd_sizes[MAX_MTD_DEVICES];
      static int mtd_blksizes[MAX_MTD_DEVICES];

      static void erase_callback(struct erase_info *done)
      {
       wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
       wake_up(wait_q);
      }

      static int erase_write (struct mtd_info *mtd, unsigned long pos,
      int len, const char *buf)
      {
       struct erase_info erase;
       DECLARE_WAITQUEUE(wait, current);
       wait_queue_head_t wait_q;
       size_t retlen;
       int ret;

       /*
       * First, let's erase the flash block.
       */

       init_waitqueue_head(&wait_q);
       erase.mtd = mtd;
       erase.callback = erase_callback;
       erase.addr = pos;
       erase.len = len;
       erase.priv = (u_long)&wait_q;

       set_current_state(TASK_INTERRUPTIBLE);
       add_wait_queue(&wait_q, &wait);

       ret = MTD_ERASE(mtd, &erase);
       if (ret) {
        set_current_state(TASK_RUNNING);
        remove_wait_queue(&wait_q, &wait);
        printk (KERN_WARNING "mtdblock: erase of region [0x%lx, 0x%x] " "on /"%s/" failed/n",
      pos, len, mtd->name);
        return ret;
       }

       schedule(); /* Wait for erase to finish. */
       remove_wait_queue(&wait_q, &wait);

       /*
       * Next, writhe data to flash.
       */

       ret = MTD_WRITE (mtd, pos, len, &retlen, buf);
       if (ret)
        return ret;
       if (retlen != len)
        return -EIO;
       return 0;
      }

      static int write_cached_data (struct mtdblk_dev *mtdblk)
      {
       struct mtd_info *mtd = mtdblk->mtd;
       int ret;

       if (mtdblk->cache_state != STATE_DIRTY)
        return 0;

       DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for /"%s/" "
      "at 0x%lx, size 0x%x/n", mtd->name,
      mtdblk->cache_offset, mtdblk->cache_size);

       ret = erase_write (mtd, mtdblk->cache_offset,
      mtdblk->cache_size, mtdblk->cache_data);
       if (ret)
        return ret;

       mtdblk->cache_state = STATE_EMPTY;
       return 0;
      }

      static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos,
      int len, const char *buf)
      {
       …
      }

      static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos,
      int len, char *buf)
      {
       …
      }

      static int mtdblock_open(struct inode *inode, struct file *file)
      {
       …
      }

      static release_t mtdblock_release(struct inode *inode, struct file *file)
      {
       int dev;
       struct mtdblk_dev *mtdblk;
       DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release/n");

       if (inode == NULL)
        release_return(-ENODEV);

       dev = minor(inode->i_rdev);
       mtdblk = mtdblks[dev];

       down(&mtdblk->cache_sem);
       write_cached_data(mtdblk);
       up(&mtdblk->cache_sem);

       spin_lock(&mtdblks_lock);
       if (!--mtdblk->count) {
        /* It was the last usage. Free the device */
        mtdblks[dev] = NULL;
        spin_unlock(&mtdblks_lock);
        if (mtdblk->mtd->sync)
         mtdblk->mtd->sync(mtdblk->mtd);
         put_mtd_device(mtdblk->mtd);
         vfree(mtdblk->cache_data);
         kfree(mtdblk);
       } else {
        spin_unlock(&mtdblks_lock);
       }

       DEBUG(MTD_DEBUG_LEVEL1, "ok/n");
       
       BLK_DEC_USE_COUNT;
       release_return(0);
      }

      /*
      * This is a special request_fn because it is executed in a process context
      * to be able to sleep independently of the caller. The
      * io_request_lock (for <2.5) or queue_lock (for >=2.5) is held upon entry
      * and exit. The head of our request queue is considered active so there is
      * no need to dequeue requests before we are done.
      */
      static void handle_mtdblock_request(void)
      {
       struct request *req;
       struct mtdblk_dev *mtdblk;
       unsigned int res;

       for (;;) {
        INIT_REQUEST;
        req = CURRENT;
        spin_unlock_irq(QUEUE_LOCK(QUEUE));
        mtdblk = mtdblks[minor(req->rq_dev)];
        res = 0;

        if (minor(req->rq_dev) >= MAX_MTD_DEVICES)
         panic("%s : minor out of bound", __FUNCTION__);

        if (!IS_REQ_CMD(req))
         goto end_req;

        if ((req->sector + req->current_nr_sectors) > (mtdblk->mtd->size >> 9))
         goto end_req;

        // Handle the request
        switch (rq_data_dir(req))
        {
         int err;

         case READ:
          down(&mtdblk->cache_sem);
          err = do_cached_read (mtdblk, req->sector << 9,
      req->current_nr_sectors << 9,
      req->buffer);
          up(&mtdblk->cache_sem);
          if (!err)
           res = 1;
          break;

         case WRITE:
          // Read only device
          if ( !(mtdblk->mtd->flags & MTD_WRITEABLE) )
           break;

          // Do the write
          down(&mtdblk->cache_sem);
          err = do_cached_write (mtdblk, req->sector << 9,req->current_nr_sectors << 9, req->buffer);
          up(&mtdblk->cache_sem);
          if (!err)
           res = 1;
          break;
        }

       end_req:
       spin_lock_irq(QUEUE_LOCK(QUEUE));
       end_request(res);
      }
      }

      static volatile int leaving = 0;
      static DECLARE_MUTEX_LOCKED(thread_sem);
      static DECLARE_WAIT_QUEUE_HEAD(thr_wq);

      int mtdblock_thread(void *dummy)
      {
       …
      }

      #define RQFUNC_ARG request_queue_t *q

      static void mtdblock_request(RQFUNC_ARG)
      {
       /* Don't do anything, except wake the thread if necessary */
       wake_up(&thr_wq);
      }

      static int mtdblock_ioctl(struct inode * inode, struct file * file,
      unsigned int cmd, unsigned long arg)
      {
       struct mtdblk_dev *mtdblk;
       mtdblk = mtdblks[minor(inode->i_rdev)];
       switch (cmd) {
        case BLKGETSIZE: /* Return device size */
         return put_user((mtdblk->mtd->size >> 9), (unsigned long *) arg);

        case BLKFLSBUF:
         if(!capable(CAP_SYS_ADMIN))
          return -EACCES;
         fsync_dev(inode->i_rdev);
         invalidate_buffers(inode->i_rdev);
         down(&mtdblk->cache_sem);
         write_cached_data(mtdblk);
         up(&mtdblk->cache_sem);
         if (mtdblk->mtd->sync)
          mtdblk->mtd->sync(mtdblk->mtd);
          return 0;
        default:
         return -EINVAL;
       }
      }

      static struct block_device_operations mtd_fops =
      {
       owner: THIS_MODULE,
       open: mtdblock_open,
       release: mtdblock_release,
       ioctl: mtdblock_ioctl
      };

      static void mtd_notify_add(struct mtd_info* mtd)
      {
       …
      }

      static void mtd_notify_remove(struct mtd_info* mtd)
      {
       if (!mtd || mtd->type == MTD_ABSENT)
        return;

       devfs_unregister(devfs_rw_handle[mtd->index]);
      }

      int __init init_mtdblock(void)
      {
       int i;

       spin_lock_init(&mtdblks_lock);
       /* this lock is used just in kernels >= 2.5.x */
       spin_lock_init(&mtdblock_lock);

       #ifdef CONFIG_DEVFS_FS
       if (devfs_register_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME, &mtd_fops))
       {
        printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices./n",
      MTD_BLOCK_MAJOR);
        return -EAGAIN;
       }

       devfs_dir_handle = devfs_mk_dir(NULL, DEVICE_NAME, NULL);
       register_mtd_user(&notifier);
       #else
        if (register_blkdev(MAJOR_NR,DEVICE_NAME,&mtd_fops)) {
         printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices./n",
      MTD_BLOCK_MAJOR);
        return -EAGAIN;
       }
       #endif

      /* We fill it in at open() time. */
      for (i=0; i< MAX_MTD_DEVICES; i++) {
       mtd_sizes[i] = 0;
       mtd_blksizes[i] = BLOCK_SIZE;
      }
      init_waitqueue_head(&thr_wq);
      /* Allow the block size to default to BLOCK_SIZE. */
      blksize_size[MAJOR_NR] = mtd_blksizes;
      blk_size[MAJOR_NR] = mtd_sizes;

      BLK_INIT_QUEUE(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request, &mtdblock_lock);

      kernel_thread (mtdblock_thread, NULL, CLONE_FS|CLONE_FILES|CLONE_SIGHAND);
      return 0;
      }

      static void __exit cleanup_mtdblock(void)
      {
       leaving = 1;
       wake_up(&thr_wq);
       down(&thread_sem);
       #ifdef CONFIG_DEVFS_FS
        unregister_mtd_user(&notifier);
        devfs_unregister(devfs_dir_handle);
        devfs_unregister_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME);
       #else
        unregister_blkdev(MAJOR_NR,DEVICE_NAME);
       #endif
       blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
       blksize_size[MAJOR_NR] = NULL;
       blk_size[MAJOR_NR] = NULL;
      }

      module_init(init_mtdblock);
      module_exit(cleanup_mtdblock);

        從上述源代碼中我們發(fā)現(xiàn),塊設(shè)備也以與字符設(shè)備register_chrdev、unregister_ chrdev 函數(shù)類似的方法進(jìn)行設(shè)備的注冊(cè)與釋放:





      int register_blkdev(unsigned int major, const char *name, struct block_device_operations *bdops);
      int unregister_blkdev(unsigned int major, const char *name);

        但是,register_chrdev使用一個(gè)向 file_operations 結(jié)構(gòu)的指針,而register_blkdev 則使用 block_device_operations 結(jié)構(gòu)的指針,其中定義的open、release 和 ioctl 方法和字符設(shè)備的對(duì)應(yīng)方法相同,但未定義 read 或者 write 操作。這是因?yàn)椋猩婕暗綁K設(shè)備的 I/O 通常由系統(tǒng)進(jìn)行緩沖處理。

        塊驅(qū)動(dòng)程序終必須提供完成實(shí)際塊 I/O 操作的機(jī)制,在 Linux 當(dāng)中,用于這些 I/O 操作的方法稱為"request(請(qǐng)求)"。在塊設(shè)備的注冊(cè)過程中,需要初始化request隊(duì)列,這一動(dòng)作通過blk_init_queue來完成,blk_init_queue函數(shù)建立隊(duì)列,并將該驅(qū)動(dòng)程序的 request 函數(shù)關(guān)聯(lián)到隊(duì)列。在模塊的清除階段,應(yīng)調(diào)用 blk_cleanup_queue 函數(shù)。

        本例中相關(guān)的代碼為:





      BLK_INIT_QUEUE(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request, &mtdblock_lock);
      blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));

        每個(gè)設(shè)備有一個(gè)默認(rèn)使用的請(qǐng)求隊(duì)列,必要時(shí),可使用 BLK_DEFAULT_QUEUE(major) 宏得到該默認(rèn)隊(duì)列。這個(gè)宏在 blk_dev_struct 結(jié)構(gòu)形成的全局?jǐn)?shù)組(該數(shù)組名為 blk_dev)中搜索得到對(duì)應(yīng)的默認(rèn)隊(duì)列。blk_dev 數(shù)組由內(nèi)核維護(hù),并可通過主設(shè)備號(hào)索引。blk_dev_struct 接口定義如下:





      struct blk_dev_struct {
       /*
       * queue_proc has to be atomic
       */
       request_queue_t request_queue;
       queue_proc *queue;
       void *data;
      };

        request_queue 成員包含了初始化之后的 I/O 請(qǐng)求隊(duì)列,data 成員可由驅(qū)動(dòng)程序使用,以便保存一些私有數(shù)據(jù)。

        request_queue定義為:





      struct request_queue
      {
       /*
       * the queue request freelist, one for reads and one for writes
       */
       struct request_list rq[2];

       /*
       * Together with queue_head for cacheline sharing
       */
       struct list_head queue_head;
       elevator_t elevator;

       request_fn_proc * request_fn;
       merge_request_fn * back_merge_fn;
       merge_request_fn * front_merge_fn;
       merge_requests_fn * merge_requests_fn;
       make_request_fn * make_request_fn;
       plug_device_fn * plug_device_fn;
       /*
       * The queue owner gets to use this for whatever they like.
       * ll_rw_blk doesn't touch it.
       */
       void * queuedata;

       /*
       * This is used to remove the plug when tq_disk runs.
       */
       struct tq_struct plug_tq;

       /*
       * Boolean that indicates whether this queue is plugged or not.
       */
       char plugged;

       /*
       * Boolean that indicates whether current_request is active or
       * not.
       */
       char head_active;

       /*
       * Is meant to protect the queue in the future instead of
       * io_request_lock
       */
       spinlock_t queue_lock;

       /*
       * Tasks wait here for free request
       */
       wait_queue_head_t wait_for_request;
      };

        下圖表征了blk_dev、blk_dev_struct和request_queue的關(guān)系:









        下圖則表征了塊設(shè)備的注冊(cè)和釋放過程:









        5.小結(jié)

        本章講述了Linux設(shè)備驅(qū)動(dòng)程序的入口函數(shù)及驅(qū)動(dòng)程序中的內(nèi)存申請(qǐng)、中斷等,并分別以實(shí)例講述了字符設(shè)備及塊設(shè)備的驅(qū)動(dòng)開發(fā)方法。

      免費(fèi)預(yù)約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 亚洲日韩欧美一区二区不卡 | 最新国产秒拍福利 | 亚洲视频高清在线人 | 伊人青青久精品 | 一级特黄录像免费播放中文 | 日韩欧美亚洲每日更新在线国产精品 |