יום שלישי

short - טיפול ב-IO

עדיין בעבודה----
נציג device driver שמבצע קריאה וכתיבה ל-IO. ה-IO שעליו נדגיםהוא הפורט המקבילי שנמצא ב-pc ויושב במרחב כתובות ה-IO. מומלץ מאד לקרוא שני פוסטים מקדימים שדנים ב- memory barrier ובגישה ל-IO, היות שהנושאים שהוסברו שם נמצאים בתוכנית אותה נציג. 
בפוסט הזה אציג את short. זוהי דוגמא שמובאת בספר -Linux Device Driver. השם short vut ר"ת של: Simple Hardware Operations and Raw Tests

במקור הדוגמא כוללת גם טיפול ב-Interrupt, אבל לשם הפשטות הוצאתי מהדוגמא את החלקים שטיפלו ב-Interrupts. נדון בחלקים אלה בפוסטים הבאים.
רקע על ה-Parralel Ports ב-PC:
ב-PC יש 3 פורטים מקביליים:
LPT1 בכתובת 0x378 - דו כיווני (input ו-output) ומחובר לפינים 2-9 בקונקטור המקבילי של ה-pc.
LPT2 בכתובת 0x278 - קריאה בלדבד (input), מחובר לפינים 10,11,12,13,15 בקונקטור המקבילי. פין 11 מחובר עם לוגיקה הפוכה - שער NOT.
LPT3 בכתובת 0x3bc - כתיבה בלבד (output), ומחובר לפינים 1,14,16,17, כאשר מלבד הקו שמחובר לפין 16, כל 3 הקוים האחרים מהופכים עם NOT.\

בדוגמא נכתוב ונקרא לLPT1 בלבד, בכתובת 0x378.

אציג תחילה את כל התוכנית כפי שהיא, לאחר שסיננתי ממנה את החלקים שקשורים לפסיקות.
לאחר מכן אתייחס לכל אחת מהפונקציות.
אסקור את תהליך בניית המודול, ו"אשחק" איתו קצת.
לסיום אתן קישור להורדת הקבצים, מוכנים לקומפילציה.

אז הנה הקובץ short.c. הוא כולל את כל הפונקציות של המודול. אחריו ההסברים.


/*
 * short.c -- Simple Hardware Operations and Raw Tests
 * short.c -- also a brief example of interrupt handling ("short int")
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 * $Id: short.c,v 1.16 2004/10/29 16:45:40 corbet Exp $
 */

/*
 * FIXME: this driver is not safe with concurrent readers or
 * writers.
 */

// RONEN: REPALCE sa_interrupt WITH IRQF_DISABLED
// INIT_WORK remove 3rd argument
// SA_SHIRQ changed to IRQF_SHARED

//#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/delay.h> /* udelay */
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/poll.h>
#include <linux/wait.h>

#include <asm/io.h>

#define SHORT_NR_PORTS 8 /* use 8 ports by default */

/*
 * all of the parameters have no "short_" prefix, to save typing when
 * specifying them at load time
 */


#define DEVICE_NAME "short"

static int major = 0; /* dynamic by default */
module_param(major, int, 0);

static int use_mem = 0; /* default is I/O-mapped */
module_param(use_mem, int, 0);

/* default is the first printer port on PC's. "short_base" is there too
   because it's what we want to use in the code */
static unsigned long base = 0x378;
unsigned long short_base = 0;
module_param(base, long, 0);

/* The interrupt line is undefined by default. "short_irq" is as above */
static int irq = -1;
volatile int short_irq = -1;
module_param(irq, int, 0);

static int probe = 0; /* select at load time how to probe irq line */
module_param(probe, int, 0);

static int wq = 0; /* select at load time whether a workqueue is used */
module_param(wq, int, 0);

static int tasklet = 0; /* select whether a tasklet is used */
module_param(tasklet, int, 0);

static int share = 0; /* select at load time whether install a shared irq */
module_param(share, int, 0);

MODULE_AUTHOR ("Alessandro Rubini");
MODULE_LICENSE("Dual BSD/GPL");


unsigned long short_buffer = 0;
unsigned long volatile short_head;
volatile unsigned long short_tail;
DECLARE_WAIT_QUEUE_HEAD(short_queue);

/* Set up our tasklet if we're doing that. */
// ronen void short_do_tasklet(unsigned long);
// ronen DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);


/*
 * The devices with low minor numbers write/read burst of data to/from
 * specific I/O ports (by default the parallel ones).
 *
 * The device with 128 as minor number returns ascii strings telling
 * when interrupts have been received. Writing to the device toggles
 * 00/FF on the parallel data lines. If there is a loopback wire, this
 * generates interrupts.
 */

int short_open (struct inode *inode, struct file *filp)
{
#ifdef RONEN
extern struct file_operations short_i_fops;

if (iminor (inode) & 0x80)
filp->f_op = &short_i_fops; /* the interrupt-driven node */
#endif
return 0;
}


int short_release (struct inode *inode, struct file *filp)
{
return 0;
}


/* first, the port-oriented device */

enum short_modes {SHORT_DEFAULT=0, SHORT_PAUSE, SHORT_STRING, SHORT_MEMORY};

ssize_t do_short_read (struct inode *inode, struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
int retval = count, minor = iminor (inode);
unsigned long port = short_base + (minor&0x0f);
void *address = (void *) short_base + (minor&0x0f);
int mode = (minor&0x70) >> 4;
unsigned char *kbuf = kmalloc(count, GFP_KERNEL), *ptr;
printk(KERN_INFO "short: do_short_read  count = %d minor=%d mode = %d\n",count,minor,mode);
    
if (!kbuf)
return -ENOMEM;
ptr = kbuf;

if (use_mem)
mode = SHORT_MEMORY;
switch(mode) {
   case SHORT_STRING:
insb(port, ptr, count);
rmb();
break;

   case SHORT_DEFAULT:
while (count--) {
*(ptr++) = inb(port);
rmb();
}
break;

   case SHORT_MEMORY:
while (count--) {
*ptr++ = ioread8(address);
rmb();
}
break;
   case SHORT_PAUSE:
while (count--) {
*(ptr++) = inb_p(port);
rmb();
}
break;

   default: /* no more modes defined by now */
retval = -EINVAL;
break;
}
if ((retval > 0) && copy_to_user(buf, kbuf, retval))
retval = -EFAULT;
kfree(kbuf);
return retval;
}


/*
 * Version-specific methods for the fops structure.  FIXME don't need anymore.
 */
ssize_t short_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
return do_short_read(filp->f_dentry->d_inode, filp, buf, count, f_pos);
}



ssize_t do_short_write (struct inode *inode, struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
int retval = count, minor = iminor(inode);
unsigned long port = short_base + (minor&0x0f);
void *address = (void *) short_base + (minor&0x0f);
int mode = (minor&0x70) >> 4;
unsigned char *kbuf = kmalloc(count , GFP_KERNEL), *ptr;
printk(KERN_INFO "short: do_short_write  count = %d minor=%d mode = %d \n",count,minor,mode);
if (!kbuf)
return -ENOMEM;
if (copy_from_user(kbuf, buf, count))
return -EFAULT;
printk(KERN_INFO "short: *kbuf = %d \n",*kbuf);
ptr = kbuf;

if (use_mem)
mode = SHORT_MEMORY;

switch(mode) {
case SHORT_PAUSE:
while (count--) {
outb_p(*(ptr++), port);
wmb();
}
break;

case SHORT_STRING:
outsb(port, ptr, count);
wmb();
break;

case SHORT_DEFAULT:
printk(KERN_INFO "short: do_short_write  SHORT_DEFAULT \n");
while (count--) {
outb(*(ptr++), port);
wmb();
}
break;

case SHORT_MEMORY:
while (count--) {
iowrite8(*ptr++, address);
wmb();
}
break;

default: /* no more modes defined by now */
retval = -EINVAL;
break;
}
kfree(kbuf);
return retval;
}


ssize_t short_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
return do_short_write(filp->f_dentry->d_inode, filp, buf, count, f_pos);
}




unsigned int short_poll(struct file *filp, poll_table *wait)
{
return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
}






struct file_operations short_fops = {
.owner = THIS_MODULE,
.read = short_read,
.write = short_write,
.poll = short_poll,
.open = short_open,
.release = short_release,
};


/* Finally, init and cleanup */

int short_init(void)
{
int result;

/*
* first, sort out the base/short_base ambiguity: we'd better
* use short_base in the code, for clarity, but allow setting
* just "base" at load time. Same for "irq".
*/
short_base = base;
short_irq = irq;

/* Get our needed resources. */
#ifdef RONEN
if (!use_mem) {
if (! request_region(short_base, SHORT_NR_PORTS, DEVICE_NAME)) {
printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",
short_base);
return -ENODEV;
}

} else {
if (! request_mem_region(short_base, SHORT_NR_PORTS, DEVICE_NAME)) {
printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n",
short_base);
return -ENODEV;
}

/* also, ioremap it */
short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS);
/* Hmm... we should check the return value */
}
#endif
/* Here we register our device - should not fail thereafter */
result = register_chrdev(major, DEVICE_NAME, &short_fops);
if (result < 0) {
printk(KERN_INFO "short: can't get major number\n");
release_region(short_base,SHORT_NR_PORTS);  /* FIXME - use-mem case? */
return result;
}
else
{
printk(KERN_INFO "ronen - major number is %d\n", result);

}
if (major == 0) major = result; /* dynamic */

short_buffer = __get_free_pages(GFP_KERNEL,0); /* never fails */  /* FIXME */
short_head = short_tail = short_buffer;
return 0;
}

void short_cleanup(void)
{
if (use_mem) {
release_mem_region(short_base, SHORT_NR_PORTS);
} else {
release_region(short_base,SHORT_NR_PORTS);
}
}

module_init(short_init);
module_exit(short_cleanup);



ה-driver כולל 7 פונקציות (ראה למעלה- מודגשות בצבע שונה): 5 הפונקציות המסומנות ב-file_operations structure (ראה בצהוב למטה), בנוסף לshort_init ו-short_release.

הנה ה-fops:


struct file_operations short_fops = {
.owner  = THIS_MODULE,
.read  = short_read,
.write  = short_write,
.poll  = short_poll,
.open  = short_open,
.release = short_release,
};


אם נבחן את התוכנית נראה שבפונקציות short_open ו-short_release לא מתבצע כלום, כך שנשאר לדון רק ב-5 פונקציות:
short_init
short_cleanup
short_read
short_write
short_poll

short_init


הנה הפונקציה:



  1. int short_init(void)
  2. {
  3. int result;

  4. /*
  5.  * first, sort out the base/short_base ambiguity: we'd better
  6.  * use short_base in the code, for clarity, but allow setting
  7.  * just "base" at load time. Same for "irq".
  8.  */
  9. short_base = base;
  10. short_irq = irq;

  11. /* Get our needed resources. */
  12. #ifdef RONEN
  13. if (!use_mem) {
  14. if (! request_region(short_base, SHORT_NR_PORTS, DEVICE_NAME)) {
  15. printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",
  16. short_base);
  17. return -ENODEV;
  18. }

  19. } else {
  20. if (! request_mem_region(short_base, SHORT_NR_PORTS, DEVICE_NAME)) {
  21. printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n",
  22. short_base);
  23. return -ENODEV;
  24. }

  25. /* also, ioremap it */
  26. short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS);
  27. /* Hmm... we should check the return value */
  28. }
  29. #endif
  30. /* Here we register our device - should not fail thereafter */
  31. result = register_chrdev(major, DEVICE_NAME, &short_fops);
  32. if (result < 0) {
  33. printk(KERN_INFO "short: can't get major number\n");
  34. release_region(short_base,SHORT_NR_PORTS);  /* FIXME - use-mem case? */
  35. return result;
  36. }
  37. else
  38. {
  39. printk(KERN_INFO "ronen - major number is %d\n", result);

  40. }
  41. if (major == 0) major = result; /* dynamic */

  42. return 0;
  43. }



החלק הראשון - מודגש בצהוב - מבצע את התהליך של שריון איזור הזיכרון. הוא מותאם גם ל-MMIO וגם ל-PMIO, בהתאם לערך של use_mem. כאן use_mem ערכו 0, והגישה היא ל=PMIO. ראה סבר לכל זה בפוסט הקדמה ל-sure.
גילוי נאות: לי היתה בעיה עם הפונקציה request_region, היות שה-parallel port driver כבר תפס את הכתובות האלה. בינתיים קטע הקוד הזה מנוטרל עם ifdef. אחזור לעניין הזה בהמשך. בכל אופן, כמו שהוסבר בפוסט "הקדמה ל-sure", ה-request_region אחראי על רישום הקצאת הכתובות, כך שהוא ימנע הקצאת אותה כתובת פעמיים, אבל נחתן לעקוף אותו ולגשת למרחב הכתובות בכל אופן, וכך אני עושה בינתיים.
החלק השני - מודגש בירוק - רגיסטרציה של ה-device. ראינו שימוש ב-API הזה בפוסט Device Driver - Read  Write/. מומלץ בכל אופן להשתמש ב-API חדש יותר - alloc_chrdev_region -כפי שהודגם בפוסט על scull.


register_chrdev מקבלת את ה-major number כפרמטר הראשון שלה. אם המספר הוא 0, המערכת תבחר major nunmber בעצמה, ותחזיר אותו כ-result. במקרה שלנו major אותחל ל-0, ובשורה 46 התוצאה תשמר בתוך major.




short_cleanup



void short_cleanup(void)
{

if (use_mem) {
release_mem_region(short_base, SHORT_NR_PORTS);
} else {
release_region(short_base,SHORT_NR_PORTS);
}
}

פשוט שחרור הזיכרון שהוכנס לרגיסטרציה ב-init/



read


הפונקציה  read מפעילה את do_short_read.

/*
 * Version-specific methods for the fops structure.  FIXME don't need anymore.
 */
ssize_t short_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
return do_short_read(filp->f_dentry->d_inode, filp, buf, count, f_pos);
}








  1. ssize_t do_short_read (struct inode *inode, struct file *filp, char __user *buf,
  2. size_t count, loff_t *f_pos)
  3. {
  4. int retval = count, minor = iminor (inode);
  5. unsigned long port = short_base + (minor&0x0f);
  6. void *address = (void *) short_base + (minor&0x0f);
  7. int mode = (minor&0x70) >> 4;
  8. unsigned char *kbuf = kmalloc(count, GFP_KERNEL), *ptr;
  9. printk(KERN_INFO "short: do_short_read  count = %d minor=%d mode = %d\n",count,minor,mode);
  10.     
  11. if (!kbuf)
  12. return -ENOMEM;
  13. ptr = kbuf;

  14. ssize_t short_write(struct file *filp, const char __user *buf, size_t count,
  15.   loff_t *f_pos)
  16. {
  17.  return do_short_write(filp->f_dentry->d_inode, filp, buf, count, f_pos);
  18. }

  19. if (use_mem)
  20. mode = SHORT_MEMORY;
  21. switch(mode) {
  22.     case SHORT_STRING:
  23. insb(port, ptr, count);
  24. rmb();
  25. break;

  26.     case SHORT_DEFAULT:
  27. while (count--) {
  28. *(ptr++) = inb(port);
  29. rmb();
  30. }
  31. break;

  32.     case SHORT_MEMORY:
  33. while (count--) {
  34. *ptr++ = ioread8(address);
  35. rmb();
  36. }
  37. break;
  38.     case SHORT_PAUSE:
  39. while (count--) {
  40. *(ptr++) = inb_p(port);
  41. rmb();
  42. }
  43. break;

  44.     default: /* no more modes defined by now */
  45. retval = -EINVAL;
  46. break;
  47. }
  48. if ((retval > 0) && copy_to_user(buf, kbuf, retval))
  49. retval = -EFAULT;
  50. kfree(kbuf);
  51. return retval;
  52. }



התיחסות ל-do_short_read:
הפונקציה קוראת מתוך הכתובת short_base - ראו שורה 6. אצלנו  short_base=0x378, כלומר parralel port 0.
שורה 18: הסתעפות לפונקצית הקריאה, על פי ה-minor number. אנחנו ניצור device עם minor number=0, כך שהוא יסתעף ל-case SHORT_DEFAULT, הקטע בצהוב למעלה. זה הקטע שמתאים לקריאת byte יחיד בממשק PMIO.
שורה 26 קוראת מתוך ה-PORT וכותבת את התוצאה לתוך kbuf - זהו buffer שהוקצה - ראו שורה 8.
שורה 27 rmb - read memory barrier, מנטרלת פעולת אופטימיזציה שעלולה להעשות כאן, היות שהקריאה נעשית תמיד מאותה כתובת. הסבר על rmb - בפוסט המבוא ל-short.
שורה 48 - העתקה של kbuf ל-buf שנמצא ב-user space בעזרת הפונקציה copy_to_user.
שורה 50 - שחרור kbuf.
שורה 51 - read מחזיר את כמות הקריאות שבצע.


write:
הפונקציה short_write מפעילה את do_short_write, אותה נסביר בהמשך.

ssize_t short_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
return do_short_write(filp->f_dentry->d_inode, filp, buf, count, f_pos);
}









  1. ssize_t do_short_write (struct inode *inode, struct file *filp, const char __user *buf,
  2. size_t count, loff_t *f_pos)
  3. {
  4. int retval = count, minor = iminor(inode);
  5. unsigned long port = short_base + (minor&0x0f);
  6. void *address = (void *) short_base + (minor&0x0f);
  7. int mode = (minor&0x70) >> 4;
  8. unsigned char *kbuf = kmalloc(count , GFP_KERNEL), *ptr;
  9. printk(KERN_INFO "short: do_short_write  count = %d minor=%d mode = %d \n",count,minor,mode);
  10. if (!kbuf)
  11. return -ENOMEM;
  12. if (copy_from_user(kbuf, buf, count))
  13. return -EFAULT;
  14. printk(KERN_INFO "short: *kbuf = %d \n",*kbuf);
  15. ptr = kbuf;

  16. if (use_mem)
  17. mode = SHORT_MEMORY;

  18. switch(mode) {
  19. case SHORT_PAUSE:
  20. while (count--) {
  21. outb_p(*(ptr++), port);
  22. wmb();
  23. }
  24. break;


  25. case SHORT_STRING:
  26. outsb(port, ptr, count);
  27. wmb();
  28. break;

  29. case SHORT_DEFAULT:
  30. printk(KERN_INFO "short: do_short_write  SHORT_DEFAULT \n");
  31. while (count--) {
  32. outb(*(ptr++), port);
  33. wmb();
  34. }
  35. break;

  36. case SHORT_MEMORY:
  37. while (count--) {
  38. iowrite8(*ptr++, address);
  39. wmb();
  40. }
  41. break;

  42. default: /* no more modes defined by now */
  43. retval = -EINVAL;
  44. break;
  45. }
  46. kfree(kbuf);
  47. return retval;
  48. }






do_short_write דומה מאד ל-do_short_read.
בקטע הראשון של הפונקציה - בירוק - שולף את הערכים של minor_number, כתובת ה-port וכו, אלוקציה של זכרון אליו נעתי'ק את תוצאת הקריאה מה-port.
בקטע הורוד מועתק התוכן לכתיבה מעזור ה-user space ע"י copy_from_user.
בחלק הבא (בז ?) מסתעפים עפ"י mode שנגזר מה-minor number. אנחנו נרוץ עם minor = 0, כך שנסתעף לקטע הצהוב.
שורה 37: כתיבה עם outb.
שור 38: write barrier
הסברים על אלה בפוסט הקדמה ל-short.
שורות 53-53: שחרור הזיכרון.

הפונקציה האחרונה היא poll:

unsigned int short_poll(struct file *filp, poll_table *wait)
{
return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
}




היא מופעלת ע"י poll מה-user space, שממתינה למוכנות ה-driver לקריאה או כתיבה. במערכת הזו אין צורך בהמתנה בכל אופן, כך שהפונקציה מחזירה מיד את הדגלים המודיעים על מוכנותה:

 POLLIN - יש data מוכנה לקריאה.
POLLRDNORM - יש data מוכנה לקריאה.
POLLOUT - אפשר לכתוב בלי להתקע (no block).
POLLWRNORM - אפשר לכתוב בלי להתקע (no block).




זו היתה התוכנית short - ממנה הוצא הטיפול בפסיקות. פוסט נוסף יטפל ב-short עם הפסיקות.

אין תגובות:

הוסף רשומת תגובה