LDD3原始碼分析之與硬體通訊&中斷處理
阿新 • • 發佈:2019-01-05
作者:劉昊昱
編譯環境: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模組初始化函式:
548int short_init(void)
549{
550 int result;
551
552 /*
553 * first, sort out the base/short_base ambiguity: we'd better
554 * use short_base in the code, for clarity, but allow setting
555 * just "base" at load time. Same for "irq".
556 */
557 short_base = base;
558 short_irq = irq;
559
560 /* Get our needed resources. */
561 if (!use_mem) {
562 if (! request_region(short_base, SHORT_NR_PORTS, "short")) {
563 printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",
564 short_base);
565 return -ENODEV;
566 }
567
568 } else {
569 if (! request_mem_region(short_base, SHORT_NR_PORTS, "short")) {
570 printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n",
571 short_base);
572 return -ENODEV;
573 }
574
575 /* also, ioremap it */
576 short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS);
577 /* Hmm... we should check the return value */
578 }
579 /* Here we register our device - should not fail thereafter */
580 result = register_chrdev(major, "short", &short_fops);
581 if (result < 0) {
582 printk(KERN_INFO "short: can't get major number\n");
583 release_region(short_base,SHORT_NR_PORTS); /* FIXME - use-mem case? */
584 return result;
585 }
586 if (major == 0) major = result; /* dynamic */
587
588 short_buffer = __get_free_pages(GFP_KERNEL,0); /* never fails */ /* FIXME */
589 short_head = short_tail = short_buffer;
590
591 /*
592 * Fill the workqueue structure, used for the bottom half handler.
593 * The cast is there to prevent warnings about the type of the
594 * (unused) argument.
595 */
596 /* this line is in short_init() */
597 INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);
598
599 /*
600 * Now we deal with the interrupt: either kernel-based
601 * autodetection, DIY detection or default number
602 */
603
604 if (short_irq < 0 && probe == 1)
605 short_kernelprobe();
606
607 if (short_irq < 0 && probe == 2)
608 short_selfprobe();
609
610 if (short_irq < 0) /* not yet specified: force the default on */
611 switch(short_base) {
612 case 0x378: short_irq = 7; break;
613 case 0x278: short_irq = 2; break;
614 case 0x3bc: short_irq = 5; break;
615 }
616
617 /*
618 * If shared has been specified, installed the shared handler
619 * instead of the normal one. Do it first, before a -EBUSY will
620 * force short_irq to -1.
621 */
622 if (short_irq >= 0 && share > 0) {
623 result = request_irq(short_irq, short_sh_interrupt,
624 SA_SHIRQ | SA_INTERRUPT,"short",
625 short_sh_interrupt);
626 if (result) {
627 printk(KERN_INFO "short: can't get assigned irq %i\n", short_irq);
628 short_irq = -1;
629 }
630 else { /* actually enable it -- assume this *is* a parallel port */
631 outb(0x10, short_base+2);
632 }
633 return 0; /* the rest of the function only installs handlers */
634 }
635
636 if (short_irq >= 0) {
637 result = request_irq(short_irq, short_interrupt,
638 SA_INTERRUPT, "short", NULL);
639 if (result) {
640 printk(KERN_INFO "short: can't get assigned irq %i\n",
641 short_irq);
642 short_irq = -1;
643 }
644 else { /* actually enable it -- assume this *is* a parallel port */
645 outb(0x10,short_base+2);
646 }
647 }
648
649 /*
650 * Ok, now change the interrupt handler if using top/bottom halves
651 * has been requested
652 */
653 if (short_irq >= 0 && (wq + tasklet) > 0) {
654 free_irq(short_irq,NULL);
655 result = request_irq(short_irq,
656 tasklet ? short_tl_interrupt :
657 short_wq_interrupt,
658 SA_INTERRUPT,"short-bh", NULL);
659 if (result) {
660 printk(KERN_INFO "short-bh: can't get assigned irq %i\n",
661 short_irq);
662 short_irq = -1;
663 }
664 }
665
666 return 0;
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函式如何實現由核心自動探測中斷號的:
466void short_kernelprobe(void)
467{
468 int count = 0;
469 do {
470 unsigned long mask;
471
472 mask = probe_irq_on();
473 outb_p(0x10,short_base+2); /* enable reporting */
474 outb_p(0x00,short_base); /* clear the bit */
475 outb_p(0xFF,short_base); /* set the bit: interrupt! */
476 outb_p(0x00,short_base+2); /* disable reporting */
477 udelay(5); /* give it some time */
478 short_irq = probe_irq_off(mask);
479
480 if (short_irq == 0) { /* none of them? */
481 printk(KERN_INFO "short: no irq reported by probe\n");
482 short_irq = -1;
483 }
484 /*
485 * if more than one line has been activated, the result is
486 * negative. We should service the interrupt (no need for lpt port)
487 * and loop over again. Loop at most five times, then give up
488 */
489 } while (short_irq < 0 && count++ < 5);
490 if (short_irq < 0)
491 printk("short: probe failed %i times, giving up\n", count);
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探測中斷號:
501void short_selfprobe(void)
502{
503 int trials[] = {3, 5, 7, 9, 0};
504 int tried[] = {0, 0, 0, 0, 0};
505 int i, count = 0;
506
507 /*
508 * install the probing handler for all possible lines. Remember
509 * the result (0 for success, or -EBUSY) in order to only free
510 * what has been acquired
511 */
512 for (i = 0; trials[i]; i++)
513 tried[i] = request_irq(trials[i], short_probing,
514 SA_INTERRUPT, "short probe", NULL);
515
516 do {
517 short_irq = 0; /* none got, yet */
518 outb_p(0x10,short_base+2); /* enable */
519 outb_p(0x00,short_base);
520 outb_p(0xFF,short_base); /* toggle the bit */
521 outb_p(0x00,short_base+2); /* disable */
522 udelay(5); /* give it some time */
523
524 /* the value has been set by the handler */
525 if (short_irq == 0) { /* none of them? */
526 printk(KERN_INFO "short: no irq reported by probe\n");
527 }
528 /*
529 * If more than one line has been activated, the result is
530 * negative. We should service the interrupt (but the lpt port
531 * doesn't need it) and loop over again. Do it at most 5 times
532 */
533 } while (short_irq <=0 && count++ < 5);
534
535 /* end of loop, uninstall the handler */
536 for (i = 0; trials[i]; i++)
537 if (tried[i] == 0)
538 free_irq(trials[i], NULL);
539
540 if (short_irq < 0)
541 printk("short: probe failed %i times, giving up\n", count);
542}
494irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs)
495{
496 if (short_irq == 0) short_irq = irq; /* found */
497 if (short_irq != irq) short_irq = -irq; /* ambiguous */
498 return IRQ_HANDLED;
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函數了:
443irqreturn_t short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
444{
445 int value, written;
446 struct timeval tv;
447
448 /* If it wasn't short, return immediately */
449 value = inb(short_base);
450 if (!(value & 0x80))
451 return IRQ_NONE;
452
453 /* clear the interrupting bit */
454 outb(value & 0x7F, short_base);
455
456 /* the rest is unchanged */
457
458 do_gettimeofday(&tv);
459 written = sprintf((char *)short_head,"%08u.%06u\n",
460 (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
461 short_incr_bp(&short_head, written);
462 wake_up_interruptible(&short_queue); /* awake any reading process */
463 return IRQ_HANDLED;
464}
93/*
94 * Atomicly increment an index into short_buffer
95 */
96static inline void short_incr_bp(volatile unsigned long *index, int delta)
97{
98 unsigned long new = *index + delta;
99 barrier(); /* Don't optimize these two together */
100 *index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;
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,該函式內容如下:
336irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs)
337{
338 struct timeval tv;
339 int written;
340
341 do_gettimeofday(&tv);
342
343 /* Write a 16 byte record. Assume PAGE_SIZE is a multiple of 16 */
344 written = sprintf((char *)short_head,"%08u.%06u\n",
345 (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
346 BUG_ON(written != 16);
347 short_incr_bp(&short_head, written);
348 wake_up_interruptible(&short_queue); /* awake any reading process */
349 return IRQ_HANDLED;
350}
short_interrupt函式的內容和共享中斷處理函式short_sh_interrupt的後半部分完全一樣,這裡不多解釋,請參考對short_sh_interrupt函式的分析。
如果指定以頂半部/底半部的方式執行中斷處理,在short_init函式中重新註冊了中斷處理函式,如果採用tasklet,則頂半部是short_tl_interrupt,如果採用工作佇列,則頂半部是short_wq_interrupt。這兩個函式列出如下:
413irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs)
414{
415 /* Grab the current time information. */
416 do_gettimeofday((struct timeval *) tv_head);
417 short_incr_tv(&tv_head);
418
419 /* Queue the bh. Don't worry about multiple enqueueing */
420 schedule_work(&short_wq);
421
422 short_wq_count++; /* record that an interrupt arrived */
423 return IRQ_HANDLED;
424}
425
426
427/*
428 * Tasklet top half
429 */
430
431irqreturn_t short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs)
432{
433 do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning */
434 short_incr_tv(&tv_head);
435 tasklet_schedule(&short_tasklet);
436 short_wq_count++; /* record that an interrupt arrived */
437 return IRQ_HANDLED;
438}
在頂半部中,取得當前時間後,呼叫short_incr_tv函式將時間儲存在tv_data陣列中,然後排程tasklet或工作稍後執行:
372static inline void short_incr_tv(volatile struct timeval **tvp)
373{
374 if (*tvp == (tv_data + NR_TIMEVAL - 1))
375 *tvp = tv_data; /* Wrap */
376 else
377 (*tvp)++;
378}
short_incr_tv函式用到的幾個變數定義如下:
357#define NR_TIMEVAL 512 /* length of the array of time values */
358
359struct timeval tv_data[NR_TIMEVAL]; /* too lazy to allocate it */
360volatile struct timeval *tv_head=tv_data;
361volatile struct timeval *tv_tail=tv_data;
工作short_wq的初始化在short_init函式中:
597 INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);
tasklet short_tasklet定義在第91行,如下:
91DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);
由此可見,工作佇列和tasklet的處理函式都是short_do_tasklet,它就是所謂的底半部函式:
382void short_do_tasklet (unsigned long unused)
383{
384 int savecount = short_wq_count, written;
385 short_wq_count = 0; /* we have already been removed from the queue */
386 /*
387 * The bottom half reads the tv array, filled by the top half,
388 * and prints it to the circular text buffer, which is then consumed
389 * by reading processes
390 */
391
392 /* First write the number of interrupts that occurred before this bh */
393 written = sprintf((char *)short_head,"bh after %6i\n",savecount);
394 short_incr_bp(&short_head, written);
395
396 /*
397 * Then, write the time values. Write exactly 16 bytes at a time,
398 * so it aligns with PAGE_SIZE
399 */
400
401 do {
402 written = sprintf((char *)short_head,"%08u.%06u\n",
403 (int)(tv_tail->tv_sec % 100000000),
404 (int)(tv_tail->tv_usec));
405 short_incr_bp(&short_head, written);
406 short_incr_tv(&tv_tail);
407 } while (tv_tail != tv_head);
408
409 wake_up_interruptible(&short_queue); /* awake any reading process */
410}
在底半部函式中,把時間資訊從tv_data陣列中取出來,寫到short_buffer緩衝區中,然後喚醒等待佇列short_queue上的程序。這些程序將從short_buffer中讀取時間資訊。
三、檔案操作函式
分析完了模組初始化函式,我們可以看裝置檔案操作函數了,檔案操作函式集是short_fops:
270struct file_operations short_fops = {
271 .owner = THIS_MODULE,
272 .read = short_read,
273 .write = short_write,
274 .poll = short_poll,
275 .open = short_open,
276 .release = short_release,
277};
先看short_open函式:
114int short_open (struct inode *inode, struct file *filp)
115{
116 extern struct file_operations short_i_fops;
117
118 if (iminor (inode) & 0x80)
119 filp->f_op = &short_i_fops; /* the interrupt-driven node */
120 return 0;
121}
118 - 119行,如果次裝置號的第8位為1,重新設定檔案操作函式集為short_i_fops。理解這樣的設定可以看一下ldd3自帶的short_load指令碼,該指令碼建立的裝置節點/dev/shortint和/dev/shortprint的次裝置號分別為128和129,如果對這兩個節點進行操作,採用short_i_fops,即使用中斷。對其它節點的操作,使用非中斷操作。
328struct file_operations short_i_fops = {
329 .owner = THIS_MODULE,
330 .read = short_i_read,
331 .write = short_i_write,
332 .open = short_open,
333 .release = short_release,
334};
下面看short_read的實現:
190ssize_t short_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
191{
192 return do_short_read(filp->f_dentry->d_inode, filp, buf, count, f_pos);
193}
134ssize_t do_short_read (struct inode *inode, struct file *filp, char __user *buf,
135 size_t count, loff_t *f_pos)
136{
137 int retval = count, minor = iminor (inode);
138 unsigned long port = short_base + (minor&0x0f);
139 void *address = (void *) short_base + (minor&0x0f);
140 int mode = (minor&0x70) >> 4;
141 unsigned char *kbuf = kmalloc(count, GFP_KERNEL), *ptr;
142
143 if (!kbuf)
144 return -ENOMEM;
145 ptr = kbuf;
146
147 if (use_mem)
148 mode = SHORT_MEMORY;
149
150 switch(mode) {
151 case SHORT_STRING:
152 insb(port, ptr, count);
153 rmb();
154 break;
155
156 case SHORT_DEFAULT:
157 while (count--) {
158 *(ptr++) = inb(port);
159 rmb();
160 }
161 break;
162
163 case SHORT_MEMORY:
164 while (count--) {
165 *ptr++ = ioread8(address);
166 rmb();
167 }
168 break;
169 case SHORT_PAUSE:
170 while (count--) {
171 *(ptr++) = inb_p(port);
172 rmb();
173 }
174 break;
175
176 default: /* no more modes defined by now */
177 retval = -EINVAL;
178 break;
179 }
180 if ((retval > 0) && copy_to_user(buf, kbuf, retval))
181 retval = -EFAULT;
182 kfree(kbuf);
183 return retval;
184}
138行,確定要訪問的埠。
139行,確定要訪問的記憶體地址。
注意,對一個裝置節點來說,要麼是採用I/O埠,要麼是採用I/O記憶體,不可能兩個同時用,所以137和138行只有一個起作用,這裡只是為減少程式程式碼而寫在一起。理解這兩句話,需要聯絡模組初始化函式short_init中的如下程式碼:
560 /* Get our needed resources. */
561 if (!use_mem) {
562 if (! request_region(short_base, SHORT_NR_PORTS, "short")) {
563 printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",
564 short_base);
565 return -ENODEV;
566 }
567
568 } else {
569 if (! request_mem_region(short_base, SHORT_NR_PORTS, "short")) {
570 printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n",
571 short_base);
572 return -ENODEV;
573 }
574
575 /* also, ioremap it */
576 short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS);
577 /* Hmm... we should check the return value */
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:
281ssize_t short_i_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
282{
283 int count0;
284 DEFINE_WAIT(wait);
285
286 while (short_head == short_tail) {
287 prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);
288 if (short_head == short_tail)
289 schedule();
290 finish_wait(&short_queue, &wait);
291 if (signal_pending (current)) /* a signal arrived */
292 return -ERESTARTSYS; /* tell the fs layer to handle it */
293 }
294 /* count0 is the number of readable data bytes */
295 count0 = short_head - short_tail;
296 if (count0 < 0) /* wrapped */
297 count0 = short_buffer + PAGE_SIZE - short_tail;
298 if (count0 < count) count = count0;
299
300 if (copy_to_user(buf, (char *)short_tail, count))
301 return -EFAULT;
302 short_incr_bp (&short_tail, count);
303 return count;
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:
306ssize_t short_i_write (struct file *filp, const char __user *buf, size_t count,
307 loff_t *f_pos)
308{
309 int written = 0, odd = *f_pos & 1;
310 unsigned long port = short_base; /* output to the parallel data latch */
311 void *address = (void *) short_base;
312
313 if (use_mem) {
314 while (written < count)
315 iowrite8(0xff * ((++written + odd) & 1), address);
316 } else {
317 while (written < count)
318 outb(0xff * ((++written + odd) & 1), port);
319 }
320
321 *f_pos += count;
322 return written;
323}
313 - 315,使用I/O記憶體,呼叫iowrite8寫資料。
316 - 318,使用I/O埠,呼叫outb寫資料。