LDD3 学习笔记
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);