中斷處理

作者:劉昊昱 

博客:http://blog.csdn.net/liuhaoyutz

編譯環境:Ubuntu 10.10

內核版本:2.6.32-38-generic-pae

LDD3源碼路徑:examples/short/

 
本分析LDD3第9和第10章的示例代碼short。short涉及的主要知識點有通過I/O端口或I/O內存操作設備寄存器及設備內存,註冊中斷處理函數處理中斷。本來第9和第10章的代碼應該分別進行討論,但是因爲short的代碼相互關聯比較緊密,所以這裏放在同一篇文章中分析。
 
一、short模塊編譯
在新的內核下,編譯short模塊時,會遇到一些問題,這裏列出遇到的問題及解決方法。
第一次make時,出現如下錯誤:

修改Makefile的第12,13,35行,將CFLAGS改爲EXTRA_CFLAGS,即可解決這個問題。再次make,會出現如下錯誤:

修改short.c,把第24行#include <linux/config.h>屏蔽掉。再次編譯出現如下問題:
這是因爲SA_INTERRUPT和SA_SHIRQ標誌在新內核中發生了變化,SA_INTERRUPT標誌已經不存在了,SA_SHIRQ標誌位變爲IRQF_SHARED。所以做以下修改:
514,638,658行把flag標誌設置爲0,624行把flag設置爲IRQF_SHARED,修改完成後,再次編譯,出現如下錯誤:
修改597行爲INIT_WORK(&short_wq, (void (*)(struct work_struct *)) short_do_tasklet);
再次make,編譯通過,但還有一些警告信息如下:
這是因爲在新的內核版本中中斷處理函數的原型只有兩個參數,而在2.6.10中有三個參數,這裏只要把相應中斷處理函數的第三個參數去掉即可,修改後的函數原型如下:
494irqreturn_t short_probing(int irq, void *dev_id)
443irqreturn_t short_sh_interrupt(int irq, void *dev_id)
431irqreturn_t short_tl_interrupt(int irq, void *dev_id)
413irqreturn_t short_wq_interrupt(int irq, void *dev_id)
336irqreturn_t short_interrupt(int irq, void *dev_id)
再次編譯,通過。
 
二、short模塊初始化
先來看short模塊初始化函數:
  1. 548int short_init(void)  
  2. 549{  
  3. 550    int result;  
  4. 551  
  5. 552    /* 
  6. 553     * first, sort out the base/short_base ambiguity: we'd better 
  7. 554     * use short_base in the code, for clarity, but allow setting 
  8. 555     * just "base" at load time. Same for "irq". 
  9. 556     */  
  10. 557    short_base = base;  
  11. 558    short_irq = irq;  
  12. 559  
  13. 560    /* Get our needed resources. */  
  14. 561    if (!use_mem) {  
  15. 562        if (! request_region(short_base, SHORT_NR_PORTS, "short")) {  
  16. 563            printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",  
  17. 564                    short_base);  
  18. 565            return -ENODEV;  
  19. 566        }  
  20. 567  
  21. 568    } else {  
  22. 569        if (! request_mem_region(short_base, SHORT_NR_PORTS, "short")) {  
  23. 570            printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n",  
  24. 571                    short_base);  
  25. 572            return -ENODEV;  
  26. 573        }  
  27. 574  
  28. 575        /* also, ioremap it */  
  29. 576        short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS);  
  30. 577        /* Hmm... we should check the return value */  
  31. 578    }  
  32. 579    /* Here we register our device - should not fail thereafter */  
  33. 580    result = register_chrdev(major, "short", &short_fops);  
  34. 581    if (result < 0) {  
  35. 582        printk(KERN_INFO "short: can't get major number\n");  
  36. 583        release_region(short_base,SHORT_NR_PORTS);  /* FIXME - use-mem case? */  
  37. 584        return result;  
  38. 585    }  
  39. 586    if (major == 0) major = result; /* dynamic */  
  40. 587  
  41. 588    short_buffer = __get_free_pages(GFP_KERNEL,0); /* never fails */  /* FIXME */  
  42. 589    short_head = short_tail = short_buffer;  
  43. 590  
  44. 591    /* 
  45. 592     * Fill the workqueue structure, used for the bottom half handler. 
  46. 593     * The cast is there to prevent warnings about the type of the 
  47. 594     * (unused) argument. 
  48. 595     */  
  49. 596    /* this line is in short_init() */  
  50. 597    INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);  
  51. 598  
  52. 599    /* 
  53. 600     * Now we deal with the interrupt: either kernel-based 
  54. 601     * autodetection, DIY detection or default number 
  55. 602     */  
  56. 603  
  57. 604    if (short_irq < 0 && probe == 1)  
  58. 605        short_kernelprobe();  
  59. 606  
  60. 607    if (short_irq < 0 && probe == 2)  
  61. 608        short_selfprobe();  
  62. 609  
  63. 610    if (short_irq < 0) /* not yet specified: force the default on */  
  64. 611        switch(short_base) {  
  65. 612            case 0x378: short_irq = 7; break;  
  66. 613            case 0x278: short_irq = 2; break;  
  67. 614            case 0x3bc: short_irq = 5; break;  
  68. 615        }  
  69. 616  
  70. 617    /* 
  71. 618     * If shared has been specified, installed the shared handler 
  72. 619     * instead of the normal one. Do it first, before a -EBUSY will 
  73. 620     * force short_irq to -1. 
  74. 621     */  
  75. 622    if (short_irq >= 0 && share > 0) {  
  76. 623        result = request_irq(short_irq, short_sh_interrupt,  
  77. 624                SA_SHIRQ | SA_INTERRUPT,"short",  
  78. 625                short_sh_interrupt);  
  79. 626        if (result) {  
  80. 627            printk(KERN_INFO "short: can't get assigned irq %i\n", short_irq);  
  81. 628            short_irq = -1;  
  82. 629        }  
  83. 630        else { /* actually enable it -- assume this *is* a parallel port */  
  84. 631            outb(0x10, short_base+2);  
  85. 632        }  
  86. 633        return 0; /* the rest of the function only installs handlers */  
  87. 634    }  
  88. 635  
  89. 636    if (short_irq >= 0) {  
  90. 637        result = request_irq(short_irq, short_interrupt,  
  91. 638                SA_INTERRUPT, "short", NULL);  
  92. 639        if (result) {  
  93. 640            printk(KERN_INFO "short: can't get assigned irq %i\n",  
  94. 641                    short_irq);  
  95. 642            short_irq = -1;  
  96. 643        }  
  97. 644        else { /* actually enable it -- assume this *is* a parallel port */  
  98. 645            outb(0x10,short_base+2);  
  99. 646        }  
  100. 647    }  
  101. 648  
  102. 649    /* 
  103. 650     * Ok, now change the interrupt handler if using top/bottom halves 
  104. 651     * has been requested 
  105. 652     */  
  106. 653    if (short_irq >= 0 && (wq + tasklet) > 0) {  
  107. 654        free_irq(short_irq,NULL);  
  108. 655        result = request_irq(short_irq,  
  109. 656                tasklet ? short_tl_interrupt :  
  110. 657                short_wq_interrupt,  
  111. 658                SA_INTERRUPT,"short-bh", NULL);  
  112. 659        if (result) {  
  113. 660            printk(KERN_INFO "short-bh: can't get assigned irq %i\n",  
  114. 661                    short_irq);  
  115. 662            short_irq = -1;  
  116. 663        }  
  117. 664    }  
  118. 665  
  119. 666    return 0;  
  120. 667}  
561 - 567行,如果指定使用I/O端口,則調用request_region函數分配I/O端口,這裏代碼指定要分配從short_base開始的SHORT_NR_PORTS個即8個端口。
568 - 578行,如果指定使用I/O內存,則調用request_mem_region函數分配從short_base開始的SHORT_NR_PORTS個即8個字節的I/O內存。分配I/O內存並不是在使用這些內存之前需要完成的唯一步驟,我們必須首先通過ioremap函數建立映射。ioremap返回用來訪問指定物理內存的虛擬地址。
580 - 586行,註冊字符設備short", 其文件操作函數集是short_fops。
588行,調用__get_free_pages(GFP_KERNEL,0)分配一個頁面保存在    short_buffer中。
597行,調用INIT_WORK初始化一個工作,將來用作中斷處理函數的下半部。
604 - 605行,如果short_irq<0並且probe等於1,則調用short_kernelprobe函數由內核探測中斷號。該函數的實現我們後面分析。
607 - 608行,如果short_irq<0並且probe等於2,則調用short_selfprobe函數自己手動探測中斷號,該函數的實現我們後面分析。
610 - 615行,如果探測沒有成功,根據端口地址,強制指定中斷號。
622 - 634行,以共享中斷的方式註冊中斷處理函數。需要注意的是631行調用outb(0x10, short_base+2),將並口2號寄存器的第4位置爲1,表示啓動並口中斷報告。
636 - 647行,以非共享中斷的方式註冊中斷處理函數。
653 - 664行,以上半部/下半部的方式註冊中斷處理函數。
下面我們來看short_kernelprobe函數如何實現由內核自動探測中斷號的:
  1. 466void short_kernelprobe(void)  
  2. 467{  
  3. 468    int count = 0;  
  4. 469    do {  
  5. 470        unsigned long mask;  
  6. 471  
  7. 472        mask = probe_irq_on();  
  8. 473        outb_p(0x10,short_base+2); /* enable reporting */  
  9. 474        outb_p(0x00,short_base);   /* clear the bit */  
  10. 475        outb_p(0xFF,short_base);   /* set the bit: interrupt! */  
  11. 476        outb_p(0x00,short_base+2); /* disable reporting */  
  12. 477        udelay(5);  /* give it some time */  
  13. 478        short_irq = probe_irq_off(mask);  
  14. 479  
  15. 480        if (short_irq == 0) { /* none of them? */  
  16. 481            printk(KERN_INFO "short: no irq reported by probe\n");  
  17. 482            short_irq = -1;  
  18. 483        }  
  19. 484        /* 
  20. 485         * if more than one line has been activated, the result is 
  21. 486         * negative. We should service the interrupt (no need for lpt port) 
  22. 487         * and loop over again. Loop at most five times, then give up 
  23. 488         */  
  24. 489    } while (short_irq < 0 && count++ < 5);  
  25. 490    if (short_irq < 0)  
  26. 491        printk("short: probe failed %i times, giving up\n", count);  
  27. 492}  
Linux內核提供了探測可用中斷號的接口,但這種接口只能在非共享中斷模式下使用。內核提供的接口由兩個函數組成:
unsigned long probe_irq_on(void);
這個函數返回一個未分配中斷的位掩碼,驅動程序必須保存返回的位掩碼,並將它傳遞給probe_irq_off函數。調用probe_irq_on函數之後,驅動程序要安排設備產生至少一次中斷。
int probe_irq_off(unsigned long);
在請求設備產生中斷之後,驅動程序要調用這個函數,並將前面probe_irq_on返回的位掩碼作爲參數傳遞給它。probe_irq_off返回probe_irq_on之後發生的中斷編號。如果沒有中斷髮生,就返回0。如果產生了多次中斷,出現了二義性,就返回負數。
使用內核提供的接口探測中斷號時,需要注意在調用probe_irq_on之後啓用設備中斷,在調用probe_irq_off之前禁用中斷。另外,在probe_irq_off之後,需要處理設備上待處理的中斷。
472行,調用probe_irq_on函數。
473行,將2號端口的第4位(0x10)設置爲1,啓用中斷。
474行,將0號端口清0。
475行,將0號端口置1,觸發中斷。
476行,將2號端口的第4位(0x10)設置爲0,禁用中斷。
477行,延時一會,以保證中斷的傳遞時間。
478行,調用probe_irq_off函數,並把472行probe_irq_on函數返回的位掩碼傳遞給它。
480行,probe_irq_off函數返回0,說明沒有中斷髮生。
489行,probe_irq_off函數返回負值,說明發生了不止一箇中斷,需要重新探測,這裏限定最多探測5次。
下面我們看short_selfprobe函數如何實現DIY探測中斷號:
  1. 501void short_selfprobe(void)  
  2. 502{  
  3. 503    int trials[] = {3, 5, 7, 9, 0};  
  4. 504    int tried[]  = {0, 0, 0, 0, 0};  
  5. 505    int i, count = 0;  
  6. 506  
  7. 507    /* 
  8. 508     * install the probing handler for all possible lines. Remember 
  9. 509     * the result (0 for success, or -EBUSY) in order to only free 
  10. 510     * what has been acquired 
  11. 511      */  
  12. 512    for (i = 0; trials[i]; i++)  
  13. 513        tried[i] = request_irq(trials[i], short_probing,  
  14. 514                SA_INTERRUPT, "short probe", NULL);  
  15. 515  
  16. 516    do {  
  17. 517        short_irq = 0; /* none got, yet */  
  18. 518        outb_p(0x10,short_base+2); /* enable */  
  19. 519        outb_p(0x00,short_base);  
  20. 520        outb_p(0xFF,short_base); /* toggle the bit */  
  21. 521        outb_p(0x00,short_base+2); /* disable */  
  22. 522        udelay(5);  /* give it some time */  
  23. 523  
  24. 524        /* the value has been set by the handler */  
  25. 525        if (short_irq == 0) { /* none of them? */  
  26. 526            printk(KERN_INFO "short: no irq reported by probe\n");  
  27. 527        }  
  28. 528        /* 
  29. 529         * If more than one line has been activated, the result is 
  30. 530         * negative. We should service the interrupt (but the lpt port 
  31. 531         * doesn't need it) and loop over again. Do it at most 5 times 
  32. 532         */  
  33. 533    } while (short_irq <=0 && count++ < 5);  
  34. 534  
  35. 535    /* end of loop, uninstall the handler */  
  36. 536    for (i = 0; trials[i]; i++)  
  37. 537        if (tried[i] == 0)  
  38. 538            free_irq(trials[i], NULL);  
  39. 539  
  40. 540    if (short_irq < 0)  
  41. 541        printk("short: probe failed %i times, giving up\n", count);  
  42. 542}  
  43. 494irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs)  
  44. 495{  
  45. 496    if (short_irq == 0) short_irq = irq;    /* found */  
  46. 497    if (short_irq != irq) short_irq = -irq; /* ambiguous */  
  47. 498    return IRQ_HANDLED;  
  48. 499}  
DIY探測與內核自動探測的原理是一樣的:先啓動所有未被佔用的中斷,然後觀察會發生什麼。但是,我們要充分發揮對具體設備的瞭解。通常,設備能使用3或4個IRQ號中的一個來進行配置,探測這些IRQ號,使我們能不必測試所有可能的IRQ就能檢測到正確的IRQ號。
並口允許用戶選擇的IRQ號有3,5,7,9,所以在short中,我們探測這幾個中斷號。
503行,trials數組列出了以0作爲結束標誌的需要測試的IRQ。
504行,tried數組用來記錄哪個中斷號被short驅動程序註冊了。
512 - 514行,循環trials數組,爲每個要探測的中斷號註冊中斷處理函數short_probing。注意, request_irq函數如果註冊成功,返回0保存在tried[i]中。
517 - 522行,觸發中斷,引起short_probing函數的執行。在short_probing函數中,將發生中斷的中斷號保存在short_irq中,如果發生多次中斷,將設置short_irq值爲負數。
525 - 527行,如果short_irq的值爲0,說明沒有發生中斷。
533行,如果short_irq的值小於或等於0,則重新探測,最多探測5次。
536 - 538行,釋放IRQ。
完成自動探測或DIY探測後,我們回到short_init函數:
610 - 615行,short_irq小於0,說明沒有探測到中斷號,short根據端口地址,強制指定默認中斷號。
622 - 634行,如果(short_irq >= 0 && share > 0),則以共享中斷方式註冊中斷處理函數short_sh_interrupt。其中,631行使用outb(0x10, short_base + 2)啓動中斷報告。
636 - 647行,如果沒有指定共享中斷,則以非共享中斷方式註冊中斷處理函數short_interrupt。其中645行outb(0x10,short_base+2)啓動中斷報告。
653 - 663行,註冊以頂半部/底半部的方式執行中斷處理。如果使用tasklet,對應的中斷處理函數是short_tl_interrupt,如果使用工作隊列,對應的中斷處理函數是short_wq_interrupt。
按照在short_init中出現的順序,下面我們要看short_sh_interrupt函數了:
  1. 443irqreturn_t short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)  
  2. 444{  
  3. 445    int value, written;  
  4. 446    struct timeval tv;  
  5. 447  
  6. 448    /* If it wasn't short, return immediately */  
  7. 449    value = inb(short_base);  
  8. 450    if (!(value & 0x80))  
  9. 451        return IRQ_NONE;  
  10. 452  
  11. 453    /* clear the interrupting bit */  
  12. 454    outb(value & 0x7F, short_base);  
  13. 455  
  14. 456    /* the rest is unchanged */  
  15. 457  
  16. 458    do_gettimeofday(&tv);  
  17. 459    written = sprintf((char *)short_head,"%08u.%06u\n",  
  18. 460            (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));  
  19. 461    short_incr_bp(&short_head, written);  
  20. 462    wake_up_interruptible(&short_queue); /* awake any reading process */  
  21. 463    return IRQ_HANDLED;  
  22. 464}  
  23.  93/* 
  24.  94 * Atomicly increment an index into short_buffer 
  25.  95 */  
  26.  96static inline void short_incr_bp(volatile unsigned long *index, int delta)  
  27.  97{  
  28.  98    unsigned long new = *index + delta;  
  29.  99    barrier();  /* Don't optimize these two together */  
  30. 100    *index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;  
  31. 101}  
註冊共享的中斷處理程序時,request_irq函數的flag參數必須指定SA_SHIRQ標誌,同時dev_id參數必須是唯一的,任何指向模塊地址空間的指針都可以使用,但是dev_id不能設置爲NULL。
註銷共享中斷處理程序同樣使用free_irq,傳遞dev_id參數用來從該中斷的共享處理程序列表中選擇指定的處理程序。這也是dev_id必須唯一的原因。
內核爲每個中斷維護了一個共享處理程序列表,這些處理程序的dev_id各不相同,就像是設備的簽名。
當請求一個共享中斷時,如果滿足下面條件之一,request_irq就能成功:
1.中斷號空閒。
2.任何已經註冊了該中斷號的處理例程也標識了中斷號是共享的。
當共享的中斷髮生時,內核會調用每一個已經註冊的中斷處理函數,因此,一個共享中斷的中斷處理函數必須能識別屬於自己的中斷,如果不是自己的設備被中斷,應該迅速退出。
449 - 451行,讀取端口short_base,如果ACK位爲1,則報告的中斷就是發送給short的。如果爲0,則是發給其它中斷處理函數的,此時short_sh_interrupt應該立即退出。
454行,清除ACK位。
458行,獲取當前時間。
459 - 460行,將時間信息保存在short_head中,在模塊初始化函數short_init中,有如下語句:
588    short_buffer = __get_free_pages(GFP_KERNEL,0); /* never fails */  /* FIXME */
589    short_head = short_tail = short_buffer;
所以short_head指向緩衝區short_buffer的空閒起始位置。
461行,調用short_incr_bp函數更新空閒緩衝區頭指針short_head位置。
462行,喚醒等待隊列short_queue上的進程。
如果不是使用共享中斷方式,在short_init函數中註冊的中斷處理函數是short_interrupt,該函數內容如下:
  1. 336irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs)  
  2. 337{  
  3. 338    struct timeval tv;  
  4. 339    int written;  
  5. 340  
  6. 341    do_gettimeofday(&tv);  
  7. 342  
  8. 343        /* Write a 16 byte record. Assume PAGE_SIZE is a multiple of 16 */  
  9. 344    written = sprintf((char *)short_head,"%08u.%06u\n",  
  10. 345            (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));  
  11. 346    BUG_ON(written != 16);  
  12. 347    short_incr_bp(&short_head, written);  
  13. 348    wake_up_interruptible(&short_queue); /* awake any reading process */  
  14. 349    return IRQ_HANDLED;  
  15. 350}  
short_interrupt函數的內容和共享中斷處理函數short_sh_interrupt的後半部分完全一樣,這裏不多解釋,請參考對short_sh_interrupt函數的分析。
如果指定以頂半部/底半部的方式執行中斷處理,在short_init函數中重新註冊了中斷處理函數,如果採用tasklet,則頂半部是short_tl_interrupt,如果採用工作隊列,則頂半部是short_wq_interrupt。這兩個函數列出如下:
  1. 413irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs)  
  2. 414{  
  3. 415    /* Grab the current time information. */  
  4. 416    do_gettimeofday((struct timeval *) tv_head);  
  5. 417    short_incr_tv(&tv_head);  
  6. 418  
  7. 419    /* Queue the bh. Don't worry about multiple enqueueing */  
  8. 420    schedule_work(&short_wq);  
  9. 421  
  10. 422    short_wq_count++; /* record that an interrupt arrived */  
  11. 423    return IRQ_HANDLED;  
  12. 424}  
  13. 425  
  14. 426  
  15. 427/* 
  16. 428 * Tasklet top half 
  17. 429 */  
  18. 430  
  19. 431irqreturn_t short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs)  
  20. 432{  
  21. 433    do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning */  
  22. 434    short_incr_tv(&tv_head);  
  23. 435    tasklet_schedule(&short_tasklet);  
  24. 436    short_wq_count++; /* record that an interrupt arrived */  
  25. 437    return IRQ_HANDLED;  
  26. 438}  
在頂半部中,取得當前時間後,調用short_incr_tv函數將時間保存在tv_data數組中,然後調度tasklet或工作稍後執行:
  1. 372static inline void short_incr_tv(volatile struct timeval **tvp)  
  2. 373{  
  3. 374    if (*tvp == (tv_data + NR_TIMEVAL - 1))  
  4. 375        *tvp = tv_data;  /* Wrap */  
  5. 376    else  
  6. 377        (*tvp)++;  
  7. 378}  
short_incr_tv函數用到的幾個變量定義如下:
  1. 357#define NR_TIMEVAL 512 /* length of the array of time values */  
  2. 358  
  3. 359struct timeval tv_data[NR_TIMEVAL]; /* too lazy to allocate it */  
  4. 360volatile struct timeval *tv_head=tv_data;  
  5. 361volatile struct timeval *tv_tail=tv_data;  
工作short_wq的初始化在short_init函數中:
  1. 597    INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);  
tasklet short_tasklet定義在第91行,如下:
  1. 91DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);  
由此可見,工作隊列和tasklet的處理函數都是short_do_tasklet,它就是所謂的底半部函數:
  1. 382void short_do_tasklet (unsigned long unused)  
  2. 383{  
  3. 384    int savecount = short_wq_count, written;  
  4. 385    short_wq_count = 0; /* we have already been removed from the queue */  
  5. 386    /* 
  6. 387     * The bottom half reads the tv array, filled by the top half, 
  7. 388     * and prints it to the circular text buffer, which is then consumed 
  8. 389     * by reading processes 
  9. 390     */  
  10. 391  
  11. 392    /* First write the number of interrupts that occurred before this bh */  
  12. 393    written = sprintf((char *)short_head,"bh after %6i\n",savecount);  
  13. 394    short_incr_bp(&short_head, written);  
  14. 395  
  15. 396    /* 
  16. 397     * Then, write the time values. Write exactly 16 bytes at a time, 
  17. 398     * so it aligns with PAGE_SIZE 
  18. 399     */  
  19. 400  
  20. 401    do {  
  21. 402        written = sprintf((char *)short_head,"%08u.%06u\n",  
  22. 403                (int)(tv_tail->tv_sec % 100000000),  
  23. 404                (int)(tv_tail->tv_usec));  
  24. 405        short_incr_bp(&short_head, written);  
  25. 406        short_incr_tv(&tv_tail);  
  26. 407    } while (tv_tail != tv_head);  
  27. 408  
  28. 409    wake_up_interruptible(&short_queue); /* awake any reading process */  
  29. 410}  
在底半部函數中,把時間信息從tv_data數組中取出來,寫到short_buffer緩衝區中,然後喚醒等待隊列short_queue上的進程。這些進程將從short_buffer中讀取時間信息。
 
三、文件操作函數
分析完了模塊初始化函數,我們可以看設備文件操作函數了,文件操作函數集是short_fops:
  1. 270struct file_operations short_fops = {  
  2. 271    .owner   = THIS_MODULE,  
  3. 272    .read    = short_read,  
  4. 273    .write   = short_write,  
  5. 274    .poll    = short_poll,  
  6. 275    .open    = short_open,  
  7. 276    .release = short_release,  
  8. 277};  
先看short_open函數:
  1. 114int short_open (struct inode *inode, struct file *filp)  
  2. 115{  
  3. 116    extern struct file_operations short_i_fops;  
  4. 117  
  5. 118    if (iminor (inode) & 0x80)  
  6. 119        filp->f_op = &short_i_fops; /* the interrupt-driven node */  
  7. 120    return 0;  
  8. 121}  
118 - 119行,如果次設備號的第8位爲1,重新設置文件操作函數集爲short_i_fops。理解這樣的設置可以看一下ldd3自帶的short_load腳本,該腳本創建的設備節點/dev/shortint和/dev/shortprint的次設備號分別爲128和129,如果對這兩個節點進行操作,採用short_i_fops,即使用中斷。對其它節點的操作,使用非中斷操作。
  1. 328struct file_operations short_i_fops = {  
  2. 329    .owner   = THIS_MODULE,  
  3. 330    .read    = short_i_read,  
  4. 331    .write   = short_i_write,  
  5. 332    .open    = short_open,  
  6. 333    .release = short_release,  
  7. 334};  
下面看short_read的實現:
  1. 190ssize_t short_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)  
  2. 191{  
  3. 192    return do_short_read(filp->f_dentry->d_inode, filp, buf, count, f_pos);  
  4. 193}  
  5.    
  6. 134ssize_t do_short_read (struct inode *inode, struct file *filp, char __user *buf,  
  7. 135        size_t count, loff_t *f_pos)  
  8. 136{  
  9. 137    int retval = count, minor = iminor (inode);  
  10. 138    unsigned long port = short_base + (minor&0x0f);  
  11. 139    void *address = (void *) short_base + (minor&0x0f);  
  12. 140    int mode = (minor&0x70) >> 4;  
  13. 141    unsigned char *kbuf = kmalloc(count, GFP_KERNEL), *ptr;  
  14. 142  
  15. 143    if (!kbuf)  
  16. 144        return -ENOMEM;  
  17. 145    ptr = kbuf;  
  18. 146  
  19. 147    if (use_mem)  
  20. 148        mode = SHORT_MEMORY;  
  21. 149  
  22. 150    switch(mode) {  
  23. 151        case SHORT_STRING:  
  24. 152        insb(port, ptr, count);  
  25. 153        rmb();  
  26. 154        break;  
  27. 155  
  28. 156        case SHORT_DEFAULT:  
  29. 157        while (count--) {  
  30. 158            *(ptr++) = inb(port);  
  31. 159            rmb();  
  32. 160        }  
  33. 161        break;  
  34. 162  
  35. 163        case SHORT_MEMORY:  
  36. 164        while (count--) {  
  37. 165            *ptr++ = ioread8(address);  
  38. 166            rmb();  
  39. 167        }  
  40. 168        break;   
  41. 169        case SHORT_PAUSE:  
  42. 170        while (count--) {  
  43. 171            *(ptr++) = inb_p(port);  
  44. 172            rmb();  
  45. 173        }  
  46. 174        break;  
  47. 175  
  48. 176        default/* no more modes defined by now */  
  49. 177        retval = -EINVAL;  
  50. 178        break;  
  51. 179    }  
  52. 180    if ((retval > 0) && copy_to_user(buf, kbuf, retval))  
  53. 181        retval = -EFAULT;  
  54. 182    kfree(kbuf);  
  55. 183    return retval;  
  56. 184}  
138行,確定要訪問的端口。
139行,確定要訪問的內存地址。
注意,對一個設備節點來說,要麼是採用I/O端口,要麼是採用I/O內存,不可能兩個同時用,所以137和138行只有一個起作用,這裏只是爲減少程序代碼而寫在一起。理解這兩句話,需要聯繫模塊初始化函數short_init中的如下代碼:
  1. 560    /* Get our needed resources. */  
  2. 561    if (!use_mem) {  
  3. 562        if (! request_region(short_base, SHORT_NR_PORTS, "short")) {  
  4. 563            printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",  
  5. 564                    short_base);  
  6. 565            return -ENODEV;  
  7. 566        }  
  8. 567  
  9. 568    } else {  
  10. 569        if (! request_mem_region(short_base, SHORT_NR_PORTS, "short")) {  
  11. 570            printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n",  
  12. 571                    short_base);  
  13. 572            return -ENODEV;  
  14. 573        }  
  15. 574  
  16. 575        /* also, ioremap it */  
  17. 576        short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS);  
  18. 577        /* Hmm... we should check the return value */  
  19. 578    }  
回到do_short_read函數:
140行,確定mode值,要理解這句,也要參考LDD3自帶的short_load腳本對設備節點次設備號的設置。/dev/short0 - /dev/short7次設備號是0 - 7,對應的mode是0,/dev/short0p - /dev/short7p次設備號是16 - 23,對應的mode是1,/dev/short0s - /dev/short7s次設備號是32 - 39,對應的mode是2。
151 - 153行,使用insb(port, ptr, count),從port端口一次讀count個字節的數據到ptr指向的內存中;
157 - 160行,使用inb(port)一次從port端口讀一個位數據,循環count次。
164 - 167行,使用ioread8(address),從I/O內存address處讀一個字節,循環count次。
169 - 173行,使用暫停式I/O函數inb_p(port),一次從port端口讀一個位數據,重複count次。
180行,將讀到的數據拷貝到用戶空間。
short_write函數的實現與short_read函數類似,只是方向相反而已,這裏不再詳細分析了。
下面我們來看使用中斷的讀函數short_i_read:
  1. 281ssize_t short_i_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)  
  2. 282{  
  3. 283    int count0;  
  4. 284    DEFINE_WAIT(wait);  
  5. 285  
  6. 286    while (short_head == short_tail) {  
  7. 287        prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);  
  8. 288        if (short_head == short_tail)  
  9. 289            schedule();  
  10. 290        finish_wait(&short_queue, &wait);  
  11. 291        if (signal_pending (current))  /* a signal arrived */  
  12. 292            return -ERESTARTSYS; /* tell the fs layer to handle it */  
  13. 293    }  
  14. 294    /* count0 is the number of readable data bytes */  
  15. 295    count0 = short_head - short_tail;  
  16. 296    if (count0 < 0) /* wrapped */  
  17. 297        count0 = short_buffer + PAGE_SIZE - short_tail;  
  18. 298    if (count0 < count) count = count0;  
  19. 299  
  20. 300    if (copy_to_user(buf, (char *)short_tail, count))  
  21. 301        return -EFAULT;  
  22. 302    short_incr_bp (&short_tail, count);  
  23. 303    return count;  
  24. 304}  
284行,創建等待隊列入口wait。
286行,如果short_head等於short_tail,說明short_buffer緩衝區中沒有數據可讀,需要休眠等待。前面在分析中斷處理函數時,我們已經看到在short設備的中斷處理函數中,會將數據寫入short_buffer緩衝區並喚醒等待隊列中的進程。
287 - 289,進入休眠。
290 - 293,被喚醒後執行清理工作。
300行,拷貝short_tail開始的count個數據到用戶空間。
302行,更新short_tail位置。
下面我們來看使用中斷的寫函數short_i_write:
  1. 306ssize_t short_i_write (struct file *filp, const char __user *buf, size_t count,  
  2. 307        loff_t *f_pos)  
  3. 308{  
  4. 309    int written = 0, odd = *f_pos & 1;  
  5. 310    unsigned long port = short_base; /* output to the parallel data latch */  
  6. 311    void *address = (void *) short_base;  
  7. 312  
  8. 313    if (use_mem) {  
  9. 314        while (written < count)  
  10. 315            iowrite8(0xff * ((++written + odd) & 1), address);  
  11. 316    } else {  
  12. 317        while (written < count)  
  13. 318            outb(0xff * ((++written + odd) & 1), port);  
  14. 319    }  
  15. 320  
  16. 321    *f_pos += count;  
  17. 322    return written;  
  18. 323}  
313 - 315,使用I/O內存,調用iowrite8寫數據。
316 - 318,使用I/O端口,調用outb寫數據。

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