作者:劉昊昱
博客: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模塊初始化函數:
- 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}
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}
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}
並口允許用戶選擇的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}
註銷共享中斷處理程序同樣使用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_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}
- 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}
- 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;
- 597 INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);
- 91DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);
- 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}
三、文件操作函數
分析完了模塊初始化函數,我們可以看設備文件操作函數了,文件操作函數集是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};
- 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}
- 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};
- 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}
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 }
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}
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}
316 - 318,使用I/O端口,調用outb寫數據。