《Linux设备驱动程序》(第三版)的shortprint模块程序注释

/*
 * A version of the "short" driver which drives a parallel printer directly,
 * with a lot of simplifying assumptions.
 *
 * 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: shortprint.c,v 1.4 2004/09/26 08:01:04 gregkh Exp $
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.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/slab.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/poll.h>

#include <asm/io.h>
#include <asm/semaphore.h>
#include <asm/atomic.h>

#include "shortprint.h"

#define SHORTP_NR_PORTS 3

//接受默认值或者从命令行接收
/*
 * all of the parameters have no "shortp_" prefix, to save typing when
 * specifying them at load time
 */
static int major = 0; /* dynamic by default */
module_param(major, int, 0);

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

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

/* Microsecond delay around strobe. */
static int delay = 0;
static int shortp_delay;
module_param(delay, int, 0);

MODULE_AUTHOR ("Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");

/*
 * Forwards.
 */
static void shortp_cleanup(void);
static void shortp_timeout(unsigned long unused);

/*
 * Input is managed through a simple circular buffer which, among other things,
 * is allowed to overrun if the reader isn't fast enough.  That makes life simple
 * on the "read" interrupt side, where we don't want to block.
 */
static unsigned long shortp_in_buffer = 0;
static unsigned long volatile shortp_in_head;
static volatile unsigned long shortp_in_tail;
DECLARE_WAIT_QUEUE_HEAD(shortp_in_queue); //初始化输入的等待队列
static struct timeval shortp_tv;  /* When the interrupt happened. */

/*
 * Atomicly increment an index into shortp_in_buffer
 */
static inline void shortp_incr_bp(volatile unsigned long *index, int delta)
{
	unsigned long new = *index + delta;
	barrier ();  /* Don't optimize these two together */
	*index = (new >= (shortp_in_buffer + PAGE_SIZE)) ? shortp_in_buffer : new;
}


/*
 * On the write side we have to be more careful, since we don't want to drop
 * data.  The semaphore is used to serialize write-side access to the buffer;
 * there is only one consumer, so read-side access is unregulated.  The
 * wait queue will be awakened when space becomes available in the buffer.
 */
static unsigned char *shortp_out_buffer = NULL;
static volatile unsigned char *shortp_out_head, *shortp_out_tail;
static struct semaphore shortp_out_sem;
static DECLARE_WAIT_QUEUE_HEAD(shortp_out_queue);//初始化输出的等待队列

/*
 * Feeding the output queue to the device is handled by way of a
 * workqueue.
 */
static void shortp_do_work(void *);
static DECLARE_WORK(shortp_work, shortp_do_work, NULL);   //声明一个工作执行shortp_do_work函数,参数null
static struct workqueue_struct *shortp_workqueue;  //工作队列

/*
 * Available space in the output buffer; should be called with the semaphore
 * held.  Returns contiguous space, so caller need not worry about wraps.
 */
static inline int shortp_out_space(void)
{
	if (shortp_out_head >= shortp_out_tail) {
		int space = PAGE_SIZE - (shortp_out_head - shortp_out_buffer);
		return (shortp_out_tail == shortp_out_buffer) ? space - 1 : space;
	} else
		return (shortp_out_tail - shortp_out_head) - 1;
}

static inline void shortp_incr_out_bp(volatile unsigned char **bp, int incr)
{
	unsigned char *new = (unsigned char *) *bp + incr;
	if (new >= (shortp_out_buffer + PAGE_SIZE))
		new -= PAGE_SIZE;
	*bp = new;
}

/*
 * The output "process" is controlled by a spin lock; decisions on
 * shortp_output_active or manipulation of shortp_out_tail require
 * that this lock be held.
 */
static spinlock_t shortp_out_lock;
volatile static int shortp_output_active;
DECLARE_WAIT_QUEUE_HEAD(shortp_empty_queue); /* waked when queue empties *///初始化释放的等待队列。因为该等待队列只有在释放设备的时候压入程序

/*
 * When output is active, the timer is too, in case we miss interrupts.	 Hold
 * shortp_out_lock if you mess with the timer.
 */
static struct timer_list shortp_timer;
#define TIMEOUT 5*HZ  /* Wait a long time */


/*
 * Open the device.
 */
static int shortp_open(struct inode *inode, struct file *filp)
{
	return 0;
}


static int shortp_release(struct inode *inode, struct file *filp)
{
	/* Wait for any pending output to complete */
	wait_event_interruptible(shortp_empty_queue, shortp_output_active==0);

	return 0;
}



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



/*
 * The read routine, which doesn't return data from the device; instead, it
 * returns timing information just like the "short" device.
 */
static ssize_t shortp_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	int count0;
	DEFINE_WAIT(wait);//建立等待队列入口

	while (shortp_in_head == shortp_in_tail) {
		prepare_to_wait(&shortp_in_queue, &wait, TASK_INTERRUPTIBLE);//等待队列头添加入队列
		if (shortp_in_head == shortp_in_tail)
			schedule();//让出cpu
		finish_wait(&shortp_in_queue, &wait);  //休眠结束,清理函数
		if (signal_pending (current))  /* a signal arrived */
			return -ERESTARTSYS; /* tell the fs layer to handle it */
	}

	/* count0 is the number of readable data bytes */
	count0 = shortp_in_head - shortp_in_tail;
	if (count0 < 0) /* wrapped */
	//若尾指针在头指针前面,说明写入的信息写完了整个缓存区从头开始写了
	//则打印出头指针到缓存末的字符个数
		count0 = shortp_in_buffer + PAGE_SIZE - shortp_in_tail; 
	if (count0 < count)
		count = count0;

	if (copy_to_user(buf, (char *)shortp_in_tail, count))
		return -EFAULT;
	shortp_incr_bp(&shortp_in_tail, count);//将索引向前移动count个
	return count;
}


/*
 * Wait for the printer to be ready; this can sleep.
 等待打印机可以工作
 */
static void shortp_wait(void)
{
	if ((inb(shortp_base + SP_STATUS) & SP_SR_BUSY) == 0) {
		printk(KERN_INFO "shortprint: waiting for printer busy\n");
		printk(KERN_INFO "Status is 0x%x\n", inb(shortp_base + SP_STATUS));
		while ((inb(shortp_base + SP_STATUS) & SP_SR_BUSY) == 0) {
			set_current_state(TASK_INTERRUPTIBLE);
			schedule_timeout(10*HZ); 
		}
	}
}


/*
 * Write the next character from the buffer.  There should *be* a next
 * character...	 The spinlock should be held when this routine is called.
 */
static void shortp_do_write(void)
{
	unsigned char cr = inb(shortp_base + SP_CONTROL);

	/* Something happened; reset the timer */
	mod_timer(&shortp_timer, jiffies + TIMEOUT);

	/* Strobe a byte out to the device */
	outb_p(*shortp_out_tail, shortp_base+SP_DATA);
	shortp_incr_out_bp(&shortp_out_tail, 1);
	if (shortp_delay)
		udelay(shortp_delay);
	outb_p(cr | SP_CR_STROBE, shortp_base+SP_CONTROL);
	if (shortp_delay)
		udelay(shortp_delay);
	outb_p(cr & ~SP_CR_STROBE, shortp_base+SP_CONTROL);
}


/*
 * Start output; call under lock.
 */
static void shortp_start_output(void)
{
	if (shortp_output_active) /* Should never happen */
		return;

	/* Set up our 'missed interrupt' timer */
	shortp_output_active = 1;
	shortp_timer.expires = jiffies + TIMEOUT;
	add_timer(&shortp_timer);

	/*  And get the process going. */
	queue_work(shortp_workqueue, &shortp_work);
}


/*
 * Write to the device.
 */
static ssize_t shortp_write(struct file *filp, const char __user *buf, size_t count,
		loff_t *f_pos)
{
	int space, written = 0;
	unsigned long flags;
	/*
	 * Take and hold the semaphore for the entire duration of the operation.  The
	 * consumer side ignores it, and it will keep other data from interleaving
	 * with ours.
	 */
	if (down_interruptible(&shortp_out_sem))
		return -ERESTARTSYS;
	/*
	 * Out with the data.
	 */
	while (written < count) {
		/* Hang out until some buffer space is available. */
		space = shortp_out_space();
		if (space <= 0) {
			if (wait_event_interruptible(shortp_out_queue,
					    (space = shortp_out_space()) > 0))
			//若没有空间,添加到等待队列等待有空间的条件成立且wake被唤醒
				goto out;
		}

		/* Move data into the buffer. */
		if ((space + written) > count)
			space = count - written;
		if (copy_from_user((char *) shortp_out_head, buf, space)) {
			up(&shortp_out_sem);
			return -EFAULT;
		}
		shortp_incr_out_bp(&shortp_out_head, space);
		buf += space;
		written += space;

		/* If no output is active, make it active. */
		//若没有输出设备激活,激活并设置定时器。
		spin_lock_irqsave(&shortp_out_lock, flags);
		if (! shortp_output_active)
			shortp_start_output();
		spin_unlock_irqrestore(&shortp_out_lock, flags);
	}

out:
	*f_pos += written;
	up(&shortp_out_sem);
	return written;
}


/*
 * The bottom-half handler.
 */


static void shortp_do_work(void *unused)
{
	int written;
	unsigned long flags;

	/* Wait until the device is ready */
	shortp_wait();
	
	spin_lock_irqsave(&shortp_out_lock, flags);

	/* Have we written everything? */
	if (shortp_out_head == shortp_out_tail) { /* empty */
		shortp_output_active = 0;
		wake_up_interruptible(&shortp_empty_queue);
		del_timer(&shortp_timer);  
	}
	/* Nope, write another byte */
	else
		shortp_do_write();

	/* If somebody's waiting, maybe wake them up. */
	if (((PAGE_SIZE + shortp_out_tail - shortp_out_head) % PAGE_SIZE) > SP_MIN_SPACE) {
		wake_up_interruptible(&shortp_out_queue);
	}
	spin_unlock_irqrestore(&shortp_out_lock, flags);

	/* Handle the "read" side operation */
	written = sprintf((char *)shortp_in_head, "%08u.%06u\n",
			(int)(shortp_tv.tv_sec % 100000000),
			(int)(shortp_tv.tv_usec));
	shortp_incr_bp(&shortp_in_head, written);
	wake_up_interruptible(&shortp_in_queue); /* awake any reading process */
}


/*
 * The top-half interrupt handler.
 */
static irqreturn_t shortp_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	if (! shortp_output_active) 
		return IRQ_NONE;

	/* Remember the time, and farm off the rest to the workqueue function */ 
	do_gettimeofday(&shortp_tv);
	queue_work(shortp_workqueue, &shortp_work);//把工作shortp_work添加到工作队列里
	return IRQ_HANDLED;
}

/*
 * Interrupt timeouts.	Just because we got a timeout doesn't mean that
 * things have gone wrong, however; printers can spend an awful long time
 * just thinking about things.
 */
static void shortp_timeout(unsigned long unused)
{
	unsigned long flags;
	unsigned char status;
   
	if (! shortp_output_active)
		return;
	spin_lock_irqsave(&shortp_out_lock, flags);
	status = inb(shortp_base + SP_STATUS);

	/* If the printer is still busy we just reset the timer */
	if ((status & SP_SR_BUSY) == 0 || (status & SP_SR_ACK)) {
		shortp_timer.expires = jiffies + TIMEOUT;
		add_timer(&shortp_timer);
		spin_unlock_irqrestore(&shortp_out_lock, flags);
		return;
	}

	/* Otherwise we must have dropped an interrupt. */
	spin_unlock_irqrestore(&shortp_out_lock, flags);
	shortp_interrupt(shortp_irq, NULL, NULL);
}
    




static struct file_operations shortp_fops = {
	.read =	   shortp_read,
	.write =   shortp_write,
	.open =	   shortp_open,
	.release = shortp_release,
	.poll =	   shortp_poll,
	.owner	 = THIS_MODULE
};




/*
 * Module initialization
 */

static int shortp_init(void)
{
	int result;

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

	/* Get our needed resources. */
	//分配IO端口
	if (! request_region(shortp_base, SHORTP_NR_PORTS, "shortprint")) {
		printk(KERN_INFO "shortprint: can't get I/O port address 0x%lx\n",
				shortp_base);
		return -ENODEV;
	}	

	/* Register the device */
	//注册设备
	result = register_chrdev(major, "shortprint", &shortp_fops);
	if (result < 0) {
		printk(KERN_INFO "shortp: can't get major number\n");
		release_region(shortp_base, SHORTP_NR_PORTS);
		return result;
	}
	if (major == 0)
		major = result; /* dynamic */

	//开辟输入输出的内存
	/* Initialize the input buffer. */
	shortp_in_buffer = __get_free_pages(GFP_KERNEL, 0); /* never fails */
	shortp_in_head = shortp_in_tail = shortp_in_buffer;

	/* And the output buffer. */
	shortp_out_buffer = (unsigned char *) __get_free_pages(GFP_KERNEL, 0);
	shortp_out_head = shortp_out_tail = shortp_out_buffer;
	sema_init(&shortp_out_sem, 1);
    
	/* And the output info */
	shortp_output_active = 0;
	spin_lock_init(&shortp_out_lock);
	init_timer(&shortp_timer);
	shortp_timer.function = shortp_timeout;//设置定时器函数
	shortp_timer.data = 0;
    
	/* Set up our workqueue. */
	//创建单线程工作的工作队列
	shortp_workqueue = create_singlethread_workqueue("shortprint");

	/* If no IRQ was explicitly requested, pick a default */
	if (shortp_irq < 0)
		switch(shortp_base) {
		    case 0x378: shortp_irq = 7; break;
		    case 0x278: shortp_irq = 2; break;
		    case 0x3bc: shortp_irq = 5; break;
		}

	/* Request the IRQ */
	result = request_irq(shortp_irq, shortp_interrupt, 0, "shortprint", NULL);
	if (result) {
		printk(KERN_INFO "shortprint: can't get assigned irq %i\n",
				shortp_irq);
		shortp_irq = -1;
		shortp_cleanup ();
		return result;
	}

	/* Initialize the control register, turning on interrupts. */
	//直接IO输出,启动中断,初始化控制控制寄存器
	outb(SP_CR_IRQ | SP_CR_SELECT | SP_CR_INIT, shortp_base + SP_CONTROL);

	return 0;
}

static void shortp_cleanup(void)
{
	/* Return the IRQ if we have one */
	if (shortp_irq >= 0) {
		outb(0x0, shortp_base + SP_CONTROL);   /* disable the interrupt */
		free_irq(shortp_irq, NULL);
	}

	/* All done with the device */
	unregister_chrdev(major, "shortprint");
	release_region(shortp_base,SHORTP_NR_PORTS);

	/* Don't leave any timers floating around.  Note that any active output
	   is effectively stopped by turning off the interrupt */
	if (shortp_output_active)
		del_timer_sync (&shortp_timer);
	flush_workqueue(shortp_workqueue);
	destroy_workqueue(shortp_workqueue);

	if (shortp_in_buffer)
		free_page(shortp_in_buffer);
	if (shortp_out_buffer)
		free_page((unsigned long) shortp_out_buffer);
}

module_init(shortp_init);
module_exit(shortp_cleanup);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值