chapter 9: interrupt handling 3 time frames: 1. human 2. outside world meaning physical *devices* which are slower than the computer 3. computer the physical device world often speaks to the computer via asyncronous events: 1. seek is finished 2. i/o read/write (into computer memory) is finished 3. somebody moved the mouse (a cat?) 4. somebody typed on the keyboard this is outside in. CPU out we call polling, but polling might actually miss something as it is inefficient ... and you can't poll MANY sources of input at once. chapter uses "short" device which can handle interrupts from parallel port short means "short int" as we handle int-errupts overall control of interrupts original pc view: 16 interrupts ... and that was it (IRQs) now have programmable advanced programmable interrupt controller APIC, which can distribute interrupts across multiple CPUs PC has had, cli and sti (clear interrupt and start interrupt) for disable and enable interrupt -- linux tries to avoid use use these calls: unsigned long flags; save_flags(flags); cli(); ... code where interrupts are disabled ... restore_flags(flags); must be called from same function in SMP system, can't protect critical section as above: use: spin_lock_irqsave() 1. locking 2. interrupt control cli rules: 1. if already in an interrupt ... only disables interrupts on the current cpu 2. else works on all cpus preparing the parallel port printer interrupts to tell us that it is ready for the next character one sets bit 4 of port 2 (0x37a) to enable interrupts. installing an interrupt handler kernel acknowledges interrupts ... driver doesn't need to do it. kernel keeps registry of interrupt like registry of i/o ports module must request interrupt channel may have to share an interrupt p. 254: see request_irq() free_irq returns 0, or negative code like -EBUSY (somebody else has it) irq - interrupt requested void (*handler) (int, void *, struct pt_regs *) - function to call flags - bit mask of options dev_name - shown in /proc/interrupts dev_id - used for shared interrupt lines, if no sharing can set to NULL. unique id. use it to point to device structure flags: SA_INTERRUPT - fast handler, executed with interrupts disabled. SA_SHIRQ - can be shared. SA_SAMPLE_RANDOM - interrupts can contribute to shared entropy pool via /dev/random and /dev/urandom ideally request int at device open see request code for short, top of p. 256. the /proc interface see p. 256 /proc/interrupts has counts associated with it, and can help prove that interrupts are working. per cpu count. also shows if drivers have asked for interrupts ... /proc/stat also has info intr N1 N1 is total number of interrupts, followed by single IRQs 0..N /proc/stat therefore can show if an irq has been used even if its driver is unloaded or closed x86 may have <= 224 interrupts in use. p. 257, snapshot of /proc/interrupts taken on an ia-64 system autodetecting the irq number how to determine which irq to use? device might be jumpered, set with firmware, or via something else usually have a default possibly: if we find device at port N, then assume IRQ Y short_irq = 7 this is life with ISA PCI solves the problem by requiring devices to declare what interrupts they are going to use at the bus level. and is much easier to deal with. autodetection may require some probing kernel-assisted probing isa devices may need probing (not PCI) linux has facilities for probing that only works with non-shared interrupts unsigned long probe_irq_on(); - returns bit mask of unassigned interrupts. try to get an interrupt ... driver passes bit mask back to probe_irq_off(unsigned long); and checks return code to learn if any interrupt happened 0 means no interrupts (0 can't happen) interrupts should be off when probe_irq_off() called do-it-yourself probing short driver, probe=2, does do-it-yourself version no kernel assist enable all unused interrupts, and see what happens. we can try a set of irqs that we know might be used by the device e.g., with parallel, we might try: 3, 5, 7, 9. see code p. 261. on p. 262, short_probing() handler used. fast and slow handlers older linux worried about this. fast means handled fast, slow means took a while to handle. with slow we would renable interrupts so we wouldn't miss any. modern kernel is different: SA_INTERRUPT flag means: execute with all other interrupts disabled on the same processor. in general, don't use SA_INTERRUPT internals of interrupt handling on x86 in 2.4: arch/i386/kernel/irq.c arch/i386/kernel/i8259.c include/asm-i386/hw_irq.c lowest level is assembly code MACROs in hw_ireq.h and expanded in i8259.c. each interrupt has function in do_IRQ, in irq.c do_IRQ acks the interrupt, so that interrupt controller can do other things obtains spinlock for the IRQ itself, thus preventing any other CPU from handling this IRQ clears a couple of status bits looks up handler for this IRQ, if none, then goes to out: release spinlock runs tasklets/bottom-halves if handler exists, 1st calls: handle_IRQ_event tests global interrupt lock bit, if bit set, will spin here. calling cli sets this bit, thus blocking handlers if handler is slow, interrupts are reenabled in hw. handler is invoked ... implementing a handler written in C things a handler cannot do: can't xfer data to/from user space no process context cannot block can't lock a semaphore can't allocate memory in a blocking fashion cannot call schedule may have to 1st clear some sort of interrupt bit in a device register may have to signal a wakeup of some sort to TH processes e.g., frame grabber might wakeup readers who wanted data if time needed is long (especially with interrupts off if for some reason that is needed) you should split work into priority, and "slow", and use a tasklet for the 2nd part short_interrupt, p. 265 gets time of day in tv structure writes data into queue wakes up any readers short_incr_bp wraps a circular queue p. 266 has read/write routines ... short_i_read reads from the device and sleeps if no data writing to the device generates inputs using arguments: 3 args to the interrupt handler. irq - which irq, dev_id - which internal structure (you might have > 1 device) regs - registers, snapshot of cpu context before the interrupt. may be useful in debugging. enabling/disabling interrupts what about just disabling one's own interrupt line? disable_irq(int irq); disable_irq_nosync(int irq); enable_irq(int irq); in theory updates irq mask for programmable interrupt controller disable_irq waits for a currently executing interrupt handler to complete disable_irq_nosync does NOT wait tasklets and bottom-half processing what if the task is LONG ... we may block out other interrupts that are more important we can logically split interrupt handler into two-parts high-priority (real interrupt) part that does critical work and schedules remainder in lower-priority (tasklet/BH) that does longer less important part of the work top-half is real handler. let's call this interrupt top-half and interrupt bottom-half ... intr-bottom-half has ALL interrupts enabled ******************************************* typical scenario: ith: deal with device; e.g., xfer data in ibh: do wakeups schedule next i/o action ith: network device (recv) read in pkt ibh: push data up stack to blocked read ibh has same restrictions as ith (cannot access user space memory) tasklets and older BH are 2 mechanisms for doing this tasklets special function scheduled to run in interrujpt context at system determined safe time guaranteed to run on same CPU as function that scheduled them therefore so that the caller function knows that it is not in a race with the tasklet see code. p. 270 and 271. BH mechanism BHs are predefined and there can only be 32 of them. code calls mark_bb(int nr) to schedule a BH. now calls tasklet_schedule to do the work. nr is number of bottom-half. defined in bh code provided by driver that uses it. IMMEDIATE_BH runs tq_immediate task queue thus driver can get its own BH TQUEUE_BH activated at each timer tick if task registered in tq_timer TIMER_BH do_timer runs this BH ... you use it by calling add_timer bb_action() runs BHs at tasklet time. 1. when processes returns from a system call 2. when an interrupt handler exits writing a BH bottom half one doesn't have to worry about multiple queue entries ... queue_task(&short_task, &tq_immediate); mark_bh(IMMEDIATE_BH); interrupt sharing pc hw allows sharing of interrupts, software may not pci allows it ... isa sw simply needs to do it to must have level-triggered interrupts, not edge-triggered for sharing to work installing a shared handler SA_SHIRQ bit must be set in flags arg to request_irq() dev_id must be unique all handlers requesting the same line must set SA_SHIRQ bit when interrupt recv, all handlers are invoked, handler must ignore it if not for self /proc interface /proc/stat not changed with shared handlers /proc/interrupts changes on p. 277, irq 9, power and sound shared irq interrupt-driven i/o delays in interrupt handling require buffered i/o ... and interrupt-driven i/o this is the typical model block drivers have "strategy" routine (with linux this is called a request routine) plus request queue network devices have xmit/recv queues processes that write (TH) fill the output queue processes that read (TH) read from the input queue driver interrupt-side writes incoming data into the input queue driver write function may be "started" by top-half, but interrupt handler will async drain output queue race conditions TH/BH needs to share queues ... linked lists ... naturally we have race conditions interrupt-recv code wants to put a queue item in, TH wants to take it out. interrupt-side "interrupts" TH methodologies include: 1. use circular queue and avoid shared variables 2. use spinlocks for mutex 3. use lock variables that are auto incremented or decremented in general, we cannot do a P on a semaphore (although a V is possible as long as it doesn't block) volatile int might work here ... but code is not as good. using circular buffers consumer/producer concurrent access avoided if there is one consumer and one producer spinlocks try and acquire shared variable spin if you cannot get mutex give up variable type spinlock_t init: spinlock_t my)lock = SPIN_LOCK_UNLOCKED; macros for access: spin_lock() acquire or spin upon return, we own it spin_lock_irqsave( ... flags) acqure lock AND disable interrupts on this CPU putting current interrupt state in flags. spin_lock_irq() does not save interrupt state spin_lock_bh() ... plus prevent BH execution see unlock primitives p. 281 spin_is_locked() - query lock state without changing it spin_trylock() - try and get lock, but fail otherwise (don't spin) spin_unlock_wait() - wait until lock is free but do not take it example: driver is in read method. obtains lock with spin_lock() interrupt-side interrupts tries to use spin_lock() to get variable deadlock ... because interrupt handler has higher priority, and TH will never run to give it up therefore TH should use irqsave routine to block interrupts reader/writer access we may have multiple readers, 1 write situation therefore readers can get in without BLOCKING each other see primitives p. 283 --> on uniprocessor systems, spinlocks do nothing <--- using lock variables 1. bit operations see p. 284 2. atomic integer operations defines atomic_t datatype may not hold more than 24 bits going to sleep without races while condition interrupt_sleep_on what happens if condition changes between test and sleep? see code. p. 287, wait_event_interruptible() is a macro that does this code for you.