/*
* 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"#defineSHORTP_NR_PORTS3//接受默认值或者从命令行接收/*
* all of the parameters have no "shortp_" prefix, to save typing when
* specifying them at load time
*/staticint 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 */staticunsignedlong base =0x378;unsignedlong shortp_base =0;module_param(base,long,0);/* The interrupt line is undefined by default. "shortp_irq" is as above */staticint irq =-1;staticint shortp_irq =-1;module_param(irq,int,0);/* Microsecond delay around strobe. */staticint delay =0;staticint shortp_delay;module_param(delay,int,0);MODULE_AUTHOR("Jonathan Corbet");MODULE_LICENSE("Dual BSD/GPL");/*
* Forwards.
*/staticvoidshortp_cleanup(void);staticvoidshortp_timeout(unsignedlong 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.
*/staticunsignedlong shortp_in_buffer =0;staticunsignedlongvolatile shortp_in_head;staticvolatileunsignedlong shortp_in_tail;DECLARE_WAIT_QUEUE_HEAD(shortp_in_queue);//初始化输入的等待队列staticstructtimeval shortp_tv;/* When the interrupt happened. *//*
* Atomicly increment an index into shortp_in_buffer
*/staticinlinevoidshortp_incr_bp(volatileunsignedlong*index,int delta){unsignedlong 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.
*/staticunsignedchar*shortp_out_buffer =NULL;staticvolatileunsignedchar*shortp_out_head,*shortp_out_tail;staticstructsemaphore shortp_out_sem;staticDECLARE_WAIT_QUEUE_HEAD(shortp_out_queue);//初始化输出的等待队列/*
* Feeding the output queue to the device is handled by way of a
* workqueue.
*/staticvoidshortp_do_work(void*);staticDECLARE_WORK(shortp_work, shortp_do_work,NULL);//声明一个工作执行shortp_do_work函数,参数nullstaticstructworkqueue_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.
*/staticinlineintshortp_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;}elsereturn(shortp_out_tail - shortp_out_head)-1;}staticinlinevoidshortp_incr_out_bp(volatileunsignedchar**bp,int incr){unsignedchar*new =(unsignedchar*)*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.
*/staticspinlock_t shortp_out_lock;volatilestaticint 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.
*/staticstructtimer_list shortp_timer;#defineTIMEOUT5*HZ /* Wait a long time *//*
* Open the device.
*/staticintshortp_open(structinode*inode,structfile*filp){return0;}staticintshortp_release(structinode*inode,structfile*filp){/* Wait for any pending output to complete */wait_event_interruptible(shortp_empty_queue, shortp_output_active==0);return0;}staticunsignedintshortp_poll(structfile*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.
*/staticssize_tshortp_read(structfile*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();//让出cpufinish_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.
等待打印机可以工作
*/staticvoidshortp_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.
*/staticvoidshortp_do_write(void){unsignedchar 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.
*/staticvoidshortp_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.
*/staticssize_tshortp_write(structfile*filp,constchar __user *buf,size_t count,loff_t*f_pos){int space, written =0;unsignedlong 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.
*/staticvoidshortp_do_work(void*unused){int written;unsignedlong 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 */elseshortp_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.
*/staticirqreturn_tshortp_interrupt(int irq,void*dev_id,structpt_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.
*/staticvoidshortp_timeout(unsignedlong unused){unsignedlong flags;unsignedchar 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);}staticstructfile_operations shortp_fops ={.read = shortp_read,.write = shortp_write,.open = shortp_open,.release = shortp_release,.poll = shortp_poll,.owner = THIS_MODULE
};/*
* Module initialization
*/staticintshortp_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 =(unsignedchar*)__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){case0x378: shortp_irq =7;break;case0x278: shortp_irq =2;break;case0x3bc: 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);return0;}staticvoidshortp_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((unsignedlong) shortp_out_buffer);}module_init(shortp_init);module_exit(shortp_cleanup);