LDD3 WEEK2

第七章 时间、延时,和延后工作

jiffies 回绕

定时器也是一种潜在的竞争条件的源

tasklet 大部分用于中断管理,也就是后文第十章的前和后半部。把一个中断处理分成两部分,也可以使用工作队列。

tasklet workqueue 区别:

  • tasklet 代码必须是原子的
  • tasklet 在同一 cpu 上运行
  • 内核可以请求工作队列延后一个明确的间隔。

第八章 分配内存

kmalloc

kmem_cache_create,Slab 缓存(内存排列密集,速度稍微提升)scullc

mempool_create 内存池

get_free_page(get_free_pages) 请求页 scullp

vmalloc,vmalloc 在分配几个页时比其他函数更快, 但是 当获取单个页时有些慢, 因为页表建立的开销 scullv

DEFINE_PER_CPU,per-cpu 每个都使用自己的拷贝

alloc_bootmem,启动时获得大量缓冲,绕开了所有内存管理

第九章 与硬件通信

I/O 端口 request_region

端口分配(部分)

rt@rogerthat ~/kernel> cat /proc/ioports
0000-0000 : PCI Bus 0000:00
  0000-0000 : dma1
  0000-0000 : pic1
  0000-0000 : timer0
  0000-0000 : timer1
  0000-0000 : keyboard
  0000-0000 : keyboard
  0000-0000 : rtc0
  0000-0000 : dma page reg
  0000-0000 : pic2
  0000-0000 : dma2
  0000-0000 : fpu
  0000-0000 : 0000:00:01.1

编译器优化和硬件重编排,可能需要内存屏障

barrier ,mb

一个例子 short ,硬件上没有条件去测试

parport 可以在源码中查看,以支持一系列并口设备,打印机,磁带备份,网络接口

The parport code provides parallel-port support under Linux. This includes the ability to share one port between multiple device drivers

涉及到 I/O 内存 分配和映射(体系依赖)

request_mem_region,再一次涉及到 ioremap。

ioread8,iowrite8,ioread8_rep

映射 ioport_map

short

/*
 * 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.
 */

#include <linux/version.h>      /* LINUX_VERSION_CODE  */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/sched.h>
#include <linux/sched/signal.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
 */
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. */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0))
void short_do_tasklet(unsigned long);
DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);
#else
void short_do_tasklet(struct tasklet_struct *);
DECLARE_TASKLET(short_tasklet, short_do_tasklet);
#endif

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


/*
 * 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)
{
	extern struct file_operations short_i_fops;

	if (iminor (inode) & 0x80)
		filp->f_op = &short_i_fops; /* the interrupt-driven node */
	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;
    
	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(file_dentry(filp)->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;

	if (!kbuf)
		return -ENOMEM;
	if (copy_from_user(kbuf, buf, count))
		return -EFAULT;
	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:
		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(file_dentry(filp)->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,
};

/* then,  the interrupt-related device */

ssize_t short_i_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	int count0;
	DEFINE_WAIT(wait);

	while (short_head == short_tail) {
		prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);
		if (short_head == short_tail)
			schedule();
		finish_wait(&short_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 = short_head - short_tail;
	if (count0 < 0) /* wrapped */
		count0 = short_buffer + PAGE_SIZE - short_tail;
	if (count0 < count) count = count0;

	if (copy_to_user(buf, (char *)short_tail, count))
		return -EFAULT;
	short_incr_bp (&short_tail, count);
	return count;
}

ssize_t short_i_write (struct file *filp, const char __user *buf, size_t count,
		loff_t *f_pos)
{
	int written = 0, odd = *f_pos & 1;
	unsigned long port = short_base; /* output to the parallel data latch */
	void *address = (void *) short_base;

	if (use_mem) {
		while (written < count)
			iowrite8(0xff * ((++written + odd) & 1), address);
	} else {
		while (written < count)
			outb(0xff * ((++written + odd) & 1), port);
	}

	*f_pos += count;
	return written;
}




struct file_operations short_i_fops = {
	.owner	 = THIS_MODULE,
	.read	 = short_i_read,
	.write	 = short_i_write,
	.open	 = short_open,
	.release = short_release,
};

irqreturn_t short_interrupt(int irq, void *dev_id)
{
	struct timespec64 tv;
	int written;

	ktime_get_real_ts64(&tv);

	    /* Write a 16 byte record. Assume PAGE_SIZE is a multiple of 16 */
	written = sprintf((char *)short_head,"%08u.%06lu\n",
			(int)(tv.tv_sec % 100000000), (int)(tv.tv_nsec) /  NSEC_PER_USEC);
	BUG_ON(written != 16);
	short_incr_bp(&short_head, written);
	wake_up_interruptible(&short_queue); /* awake any reading process */
	return IRQ_HANDLED;
}

/*
 * The following two functions are equivalent to the previous one,
 * but split in top and bottom half. First, a few needed variables
 */

#define NR_TIMEVAL 512 /* length of the array of time values */

struct timespec64 tv_data[NR_TIMEVAL]; /* too lazy to allocate it */
volatile struct timespec64 *tv_head=tv_data;
volatile struct timespec64 *tv_tail=tv_data;

static struct work_struct short_wq;


int short_wq_count = 0;

/*
 * Increment a circular buffer pointer in a way that nobody sees
 * an intermediate value.
 */
static inline void short_incr_tv(volatile struct timespec64 **tvp)
{
	if (*tvp == (tv_data + NR_TIMEVAL - 1))
		*tvp = tv_data;	 /* Wrap */
	else
		(*tvp)++;
}


#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0))
void short_do_tasklet (unsigned long unused)
#else
void short_do_tasklet (struct tasklet_struct * unused)
#endif
{
	int savecount = short_wq_count, written;
	short_wq_count = 0; /* we have already been removed from the queue */
	/*
	 * The bottom half reads the tv array, filled by the top half,
	 * and prints it to the circular text buffer, which is then consumed
	 * by reading processes
	 */

	/* First write the number of interrupts that occurred before this bh */
	written = sprintf((char *)short_head,"bh after %6i\n",savecount);
	short_incr_bp(&short_head, written);

	/*
	 * Then, write the time values. Write exactly 16 bytes at a time,
	 * so it aligns with PAGE_SIZE
	 */

	do {
		written = sprintf((char *)short_head,"%08u.%06lu\n",
				(int)(tv_tail->tv_sec % 100000000),
				(int)(tv_tail->tv_nsec) /  NSEC_PER_USEC);
		short_incr_bp(&short_head, written);
		short_incr_tv(&tv_tail);
	} while (tv_tail != tv_head);

	wake_up_interruptible(&short_queue); /* awake any reading process */
}


irqreturn_t short_wq_interrupt(int irq, void *dev_id)
{
	/* Grab the current time information. */
	ktime_get_real_ts64((struct timespec64 *) tv_head);
	short_incr_tv(&tv_head);

	/* Queue the bh. Don't worry about multiple enqueueing */
	schedule_work(&short_wq);

	short_wq_count++; /* record that an interrupt arrived */
	return IRQ_HANDLED;
}


/*
 * Tasklet top half
 */

irqreturn_t short_tl_interrupt(int irq, void *dev_id)
{
	ktime_get_real_ts64((struct timespec64 *) tv_head); /* cast to stop 'volatile' warning */
	short_incr_tv(&tv_head);
	tasklet_schedule(&short_tasklet);
	short_wq_count++; /* record that an interrupt arrived */
	return IRQ_HANDLED;
}




irqreturn_t short_sh_interrupt(int irq, void *dev_id)
{
	int value, written;
	struct timespec64 tv;

	/* If it wasn't short, return immediately */
	value = inb(short_base);
	if (!(value & 0x80))
		return IRQ_NONE;
	
	/* clear the interrupting bit */
	outb(value & 0x7F, short_base);

	/* the rest is unchanged */

	ktime_get_real_ts64(&tv);
	written = sprintf((char *)short_head,"%08u.%06lu\n",
			(int)(tv.tv_sec % 100000000), (int)(tv.tv_nsec) / NSEC_PER_USEC);
	short_incr_bp(&short_head, written);
	wake_up_interruptible(&short_queue); /* awake any reading process */
	return IRQ_HANDLED;
}

void short_kernelprobe(void)
{
	int count = 0;
	do {
		unsigned long mask;

		mask = probe_irq_on();
		outb_p(0x10,short_base+2); /* enable reporting */
		outb_p(0x00,short_base);   /* clear the bit */
		outb_p(0xFF,short_base);   /* set the bit: interrupt! */
		outb_p(0x00,short_base+2); /* disable reporting */
		udelay(5);  /* give it some time */
		short_irq = probe_irq_off(mask);

		if (short_irq == 0) { /* none of them? */
			printk(KERN_INFO "short: no irq reported by probe\n");
			short_irq = -1;
		}
		/*
		 * if more than one line has been activated, the result is
		 * negative. We should service the interrupt (no need for lpt port)
		 * and loop over again. Loop at most five times, then give up
		 */
	} while (short_irq < 0 && count++ < 5);
	if (short_irq < 0)
		printk("short: probe failed %i times, giving up\n", count);
}

irqreturn_t short_probing(int irq, void *dev_id)
{
	if (short_irq == 0) short_irq = irq;	/* found */
	if (short_irq != irq) short_irq = -irq; /* ambiguous */
	return IRQ_HANDLED;
}

void short_selfprobe(void)
{
	int trials[] = {3, 5, 7, 9, 0};
	int tried[]  = {0, 0, 0, 0, 0};
	int i, count = 0;

	/*
	 * install the probing handler for all possible lines. Remember
	 * the result (0 for success, or -EBUSY) in order to only free
	 * what has been acquired
      */
	for (i = 0; trials[i]; i++)
		tried[i] = request_irq(trials[i], short_probing,
				       0, "short probe", NULL);

	do {
		short_irq = 0; /* none got, yet */
		outb_p(0x10,short_base+2); /* enable */
		outb_p(0x00,short_base);
		outb_p(0xFF,short_base); /* toggle the bit */
		outb_p(0x00,short_base+2); /* disable */
		udelay(5);  /* give it some time */

		/* the value has been set by the handler */
		if (short_irq == 0) { /* none of them? */
			printk(KERN_INFO "short: no irq reported by probe\n");
		}
		/*
		 * If more than one line has been activated, the result is
		 * negative. We should service the interrupt (but the lpt port
		 * doesn't need it) and loop over again. Do it at most 5 times
		 */
	} while (short_irq <=0 && count++ < 5);

	/* end of loop, uninstall the handler */
	for (i = 0; trials[i]; i++)
		if (tried[i] == 0)
			free_irq(trials[i], NULL);

	if (short_irq < 0)
		printk("short: probe failed %i times, giving up\n", count);
}



/* 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. */
	if (!use_mem) {
		if (! request_region(short_base, SHORT_NR_PORTS, "short")) {
			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, "short")) {
			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 */
	}
	/* Here we register our device - should not fail thereafter */
	result = register_chrdev(major, "short", &short_fops);
	if (result < 0) {
		printk(KERN_INFO "short: can't get major number\n");
		if (!use_mem) {
			release_region(short_base, SHORT_NR_PORTS);
		} else {
			release_mem_region(short_base, SHORT_NR_PORTS);
		}
		return result;
	}
	if (major == 0) major = result; /* dynamic */

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

	/*
	 * Fill the workqueue structure, used for the bottom half handler.
	 * The cast is there to prevent warnings about the type of the
	 * (unused) argument.
	 */
	/* this line is in short_init() */
	INIT_WORK(&short_wq, (void (*)(struct work_struct *)) short_do_tasklet);

	/*
	 * Now we deal with the interrupt: either kernel-based
	 * autodetection, DIY detection or default number
	 */

	if (short_irq < 0 && probe == 1)
		short_kernelprobe();

	if (short_irq < 0 && probe == 2)
		short_selfprobe();

	if (short_irq < 0) /* not yet specified: force the default on */
		switch(short_base) {
		    case 0x378: short_irq = 7; break;
		    case 0x278: short_irq = 2; break;
		    case 0x3bc: short_irq = 5; break;
		}

	/*
	 * If shared has been specified, installed the shared handler
	 * instead of the normal one. Do it first, before a -EBUSY will
	 * force short_irq to -1.
	 */
	if (short_irq >= 0 && share > 0) {
		result = request_irq(short_irq, short_sh_interrupt,
				     IRQF_SHARED,"short",
				short_sh_interrupt);
		if (result) {
			printk(KERN_INFO "short: can't get assigned irq %i\n", short_irq);
			short_irq = -1;
		}
		else { /* actually enable it -- assume this *is* a parallel port */
			outb(0x10, short_base+2);
		}
		return 0; /* the rest of the function only installs handlers */
	}

	if (short_irq >= 0) {
		result = request_irq(short_irq, short_interrupt,
				     0, "short", NULL);
		if (result) {
			printk(KERN_INFO "short: can't get assigned irq %i\n",
					short_irq);
			short_irq = -1;
		}
		else { /* actually enable it -- assume this *is* a parallel port */
			outb(0x10,short_base+2);
		}
	}

	/*
	 * Ok, now change the interrupt handler if using top/bottom halves
	 * has been requested
	 */
	if (short_irq >= 0 && (wq + tasklet) > 0) {
		free_irq(short_irq,NULL);
		result = request_irq(short_irq,
				tasklet ? short_tl_interrupt :
				short_wq_interrupt,
				0, "short-bh", NULL);
		if (result) {
			printk(KERN_INFO "short-bh: can't get assigned irq %i\n",
					short_irq);
			short_irq = -1;
		}
	}

	return 0;
}

void short_cleanup(void)
{
	if (short_irq >= 0) {
		outb(0x0, short_base + 2);   /* disable the interrupt */
		if (!share) free_irq(short_irq, NULL);
		else free_irq(short_irq, short_sh_interrupt);
	}
	/* Make sure we don't leave work queue/tasklet functions running */
	if (tasklet)
		tasklet_disable(&short_tasklet);
	else
		flush_scheduled_work();
	unregister_chrdev(major, "short");
	if (use_mem) {
		iounmap((void __iomem *)short_base);
		//release_mem_region(short_base, SHORT_NR_PORTS);
		release_mem_region(base, SHORT_NR_PORTS);
	} else {
		release_region(short_base,SHORT_NR_PORTS);
	}
	if (short_buffer) free_page(short_buffer);
}

module_init(short_init);
module_exit(short_cleanup);

第十章 中断

安装中断处理

request_irq,free_irq,其中有几个 flag,有关禁止中断,中断共享,

中断报告

rt@rogerthat ~/kernel> cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7       
  0:          5          0          0          0          0          0          0          0   IO-APIC   2-edge      timer
  1:          0          0          0          0          0          0          0          9   IO-APIC   1-edge      i8042
  6:          0          3          0          0          0          0          0          0   IO-APIC   6-edge      floppy
  8:          1          0          0          0          0          0          0          0   IO-APIC   8-edge      rtc0
  9:          0          0          0          0          0          0          0          0   IO-APIC   9-fasteoi   acpi
 10:          0          0        109       3285          0          0          0          0   IO-APIC  10-fasteoi   virtio0
 11:          0          0          0          0          0         32          0          0   IO-APIC  11-fasteoi   uhci_hcd:usb1
 12:          0          0          0          0          0          0         15          0   IO-APIC  12-edge      i8042
 14:          0          0          0          0          0          0          0          0   IO-APIC  14-edge      ata_piix
 15:          0          0          0          0       6477          0          0          0   IO-APIC  15-edge      ata_piix
 24:          0          0          0          0          0          0          0          0   PCI-MSI 540672-edge      virtio2-config
 25:          0          0          0          0          0          0          0          0   PCI-MSI 540673-edge      virtio2-control
 26:          0          0          0          0          0          0          0          0   PCI-MSI 540674-edge      virtio2-event
 27:       3790          0          0          0          0          0          0          0   PCI-MSI 540675-edge      virtio2-request
 28:          0       1682          0          0          0          0          0          0   PCI-MSI 540676-edge      virtio2-request
 29:          0          0       3794          0          0          0          0          0   PCI-MSI 540677-edge      virtio2-request
 30:          0          0          0       3853          0          0          0          0   PCI-MSI 540678-edge      virtio2-request
 31:          0          0          0          0       2456          0          0          0   PCI-MSI 540679-edge      virtio2-request
 32:          0          0          0          0          0       2673          0          0   PCI-MSI 540680-edge      virtio2-request
 33:          0          0          0          0          0          0      23500          0   PCI-MSI 540681-edge      virtio2-request
 34:          0          0          0          0          0          0          0       2433   PCI-MSI 540682-edge      virtio2-request
 35:          0          0          0          0          0          0          0          0   PCI-MSI 557056-edge      virtio3-config
 36:          0          0          0          0          0          0          0          0   PCI-MSI 557057-edge      virtio3-control
 37:          0          0          0          0          0          0          0          0   PCI-MSI 557058-edge      virtio3-event
 38:       5186          0          0          0          0          0          0          0   PCI-MSI 557059-edge      virtio3-request
 39:          0         37          0          0          0          0          0          0   PCI-MSI 557060-edge      virtio3-request
 40:          0          0         90          0          0          0          0          0   PCI-MSI 557061-edge      virtio3-request
 41:          0          0          0        554          0          0          0          0   PCI-MSI 557062-edge      virtio3-request
 42:          0          0          0          0        227          0          0          0   PCI-MSI 557063-edge      virtio3-request
 43:          0          0          0          0          0       8204          0          0   PCI-MSI 557064-edge      virtio3-request
 44:          0          0          0          0          0          0      18380          0   PCI-MSI 557065-edge      virtio3-request
 45:          0          0          0          0          0          0          0         37   PCI-MSI 557066-edge      virtio3-request
 46:          0          0          0          0          0          0          0          0   PCI-MSI 294912-edge      virtio1-config
 47:          0        140          0          0          0          0          0      23144   PCI-MSI 294913-edge      virtio1-input.0
 48:          0          0        100      17195          0          0          0          0   PCI-MSI 294914-edge      virtio1-output.0
NMI:          0          0          0          0          0          0          0          0   Non-maskable interrupts
LOC:     413405     372106     639970     393123     571071     523965     709446     831403   Local timer interrupts
SPU:          0          0          0          0          0          0          0          0   Spurious interrupts
PMI:          0          0          0          0          0          0          0          0   Performance monitoring interrupts
IWI:          0          0          0          0          0          0          0          0   IRQ work interrupts
RTR:          0          0          0          0          0          0          0          0   APIC ICR read retries
RES:       1067        976        882       1101        924        820        916        823   Rescheduling interrupts
CAL:      66881      61648      44649      58271      56575      55592      48640      40939   Function call interrupts
TLB:       2065       2197       1533       2068       2028       2277       1999       1688   TLB shootdowns
TRM:          0          0          0          0          0          0          0          0   Thermal event interrupts
THR:          0          0          0          0          0          0          0          0   Threshold APIC interrupts
DFR:          0          0          0          0          0          0          0          0   Deferred Error APIC interrupts
MCE:          0          0          0          0          0          0          0          0   Machine check exceptions
MCP:         21         21         21         21         21         21         21         21   Machine check polls
HYP:          1          1          1          1          1          1          1          1   Hypervisor callback interrupts
ERR:          0
MIS:          0
PIN:          0          0          0          0          0          0          0          0   Posted-interrupt notification event
NPI:          0          0          0          0          0          0          0          0   Nested posted-interrupt event
PIW:          0          0          0          0          0          0          0          0   Posted-interrupt wakeup event
rt@rogerthat ~/kernel> cat /proc/stat 
cpu  6517 39 6599 5246969 2435 0 405 96 0 0
cpu0 617 37 666 656883 113 0 279 11 0 0
cpu1 745 0 593 656906 131 0 78 7 0 0
cpu2 925 0 964 655316 409 0 12 13 0 0
cpu3 657 0 612 656545 549 0 4 10 0 0
cpu4 809 0 850 655639 343 0 4 13 0 0
cpu5 805 0 826 655850 453 0 2 13 0 0
cpu6 961 0 971 655188 234 0 3 12 0 0
cpu7 993 0 1113 654639 200 0 19 13 0 0
intr 5176490 5 9 0 0 0 0 3 0 1 0 3495 32 15 0 0 6677 0 0 0 0 0 0 0 0 0 0 0 3790 1682 3794 3859 2456 2803 23571 2434 0 0 0 5186 37 90 554 227 8204 18380 37 0 24193 18070 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 9424165
btime 1714920166
processes 5149
procs_running 1
procs_blocked 0
softirq 989106 1 213115 5 58801 4340 0 140 307128 8 405568

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/module.h>
#include <linux/moduleparam.h>

#include <linux/sched.h>
#include <linux/sched/signal.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 <linux/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(struct timer_list *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 timespec64 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 mutex shortp_out_mutex;
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(struct work_struct *work);
static DECLARE_WORK(shortp_work, shortp_do_work);
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();
		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);
	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 mutex for the entire duration of the operation.  The
	 * consumer side ignores it, and it will keep other data from interleaving
	 * with ours.
	 */
	if (mutex_lock_interruptible(&shortp_out_mutex))
		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))
				goto out;
		}

		/* Move data into the buffer. */
		if ((space + written) > count)
			space = count - written;
		if (copy_from_user((char *) shortp_out_head, buf, space)) {
			mutex_unlock(&shortp_out_mutex);
			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;
	mutex_unlock(&shortp_out_mutex);
	return written;
}


/*
 * The bottom-half handler.
 */


static void shortp_do_work(struct work_struct *work)
{
	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.%09u\n",
			(int)(shortp_tv.tv_sec % 100000000),
			(int)(shortp_tv.tv_nsec));
	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)
{
	if (! shortp_output_active) 
		return IRQ_NONE;

	/* Remember the time, and farm off the rest to the workqueue function */ 
	ktime_get_real_ts64(&shortp_tv);
	queue_work(shortp_workqueue, &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(struct timer_list *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);
}
    




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. */
	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;
	mutex_init(&shortp_out_mutex);
    
	/* And the output info */
	shortp_output_active = 0;
	spin_lock_init(&shortp_out_lock);
	timer_setup(&shortp_timer, shortp_timeout, 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. */
	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);