Advanced Issues

This chapter contains instructions for performing operations that CardBuilder cannot automate.

Handling Interrupts

Interrupts can be easily handled with KylixDriver. There are several classes devoted to handling hardware interrupts in KylixDriver Application Library. Exemplars of these classes jointly function to provide you with all necessary abilities to attend to your card’s specific interrupt. The central class is TkdInterrupt and any developer’s treatment of interrupt handling abilities in KylixDriver goes via, in particular, this class. Exemplars of the remaining classes are proxies and are enclosed. But, for now, let us see which interrupts can be in essence. This influences heavily on how the interrupt handler has to be written. ISA and PCI interfaces differently work with interrupts.

ISA and PCI Interrupts

ISA cards use edge-triggered interrupts, that is, those cards step up voltage on one of contacts of IRQ lines in order to acquire the interrupt. This means that ISA devices can not use IRQ lines conjointly, because when the processor receives a signal from one of the lines, it can not determine which particular device has posted this signal. Thus, the first conclusion - ISA devices are not IRQ-sharable. The second conclusion – no special action is needed in order to acknowledge such an interrupt.

PCI cards use level-sensitive interrupts, and this means that different PCI devices can feed different voltages to the same physical IRQ line, but the processor will be able to determine which devices in particular ask for interrupt. However, level-sensitive interrupts are generated as long as the physical interrupt signal is high. If the interrupt signal is not lowered by kernel by the end of handling, the operating system will call the interrupt handler again, causing the PC to hang! To prevent such a situation, the interrupt must be acknowledged by its interrupt handler. The interrupt handler for PCI devices, in addition, must be capable to recognize if native device has generated this particular interrupt or not.

Three Parts of Interrupt Handler

In general case (and many interrupt handlers for sophisticated PCI devices are striking examples) interrupt handler is a code consisting of 3 parts (stages):

Note: 2 and 3 parts can trade places.

Kernel-Mode and User-Mode Interrupt Handlers

Now it is time to understand what is kernel-mode interrupt handler, what is user-mode interrupt handler and how KylixDriver allows you to write either of those handlers and plastically perform any work related to interrupt management. You are yourself capable of being very good at writing kernel-mode interrupt handlers with known deal of easiness. Other than that you will know which problems can be raised at that and how to solve them.

Any kernel-mode interrupt handler is a routine executed exactly at interrupt time, and exactly on the highly privileged system level, in other words, in the kernel mode. This means the code of a kernel-mode interrupt handler must be a verily Linux kernel code, not other kind of code.

Any user-mode interrupt handler is a user application routine executed, in contract, by some previously created system thread which runs an infinite loop waiting for an interrupt to occur. This means any Kylix programming language code can, potentially, be your user-mode interrupt handler.

A careful and empirical reader has already attended about what if the interrupt frequency of a device would be too high, but a user-mode interrupt handler would be slightly slow to be called. And how then to write a kernel-mode interrupt handler if your are not skilled at something other than just writing a Kylix code?

Really, KylixDriver is tested at frequencies of up to 10 KHz on Celeron 2.40 GHz but for stable results it is recommented to utilize the user-mode interrupt handling approach primarily for interrupts those periods are not less than 1-2 milliseconds each. The problem is not in making respective records inside the GDD about each interrupt happened. However, the problem, as you already guessed, is in delays with which the interrupt information is delivered up to the user level, a Kylix application. Linux is a multitasking system. Consequently the interrupt information can be delivered up to the user application as soon as the system activates the thread within which the user-mode interrupt handler runs. Of course, we can influence on the delay time by increasing the priority of the thread and by increasing the priority of the whole Kylix application. But, you see, this is an indirect and, therefore, not that efficient way, especially since other devices and the whole system productivity will be slowed down. Thus, the only reliable way out for frequently interruptible devices is to write a kernel-mode interrupt handler. Fortunately, KylixDriver Application Library’s TkdInterrupt class, already mentioned, has everything for you to do that. You can even combine both kinds of interrupt handlers if it is necessary to distribute the 3 mentioned-above stages of the whole logic of interrupt handler among user-mode and kernel-mode interrupt sub-handlers.

Thus, any kernel mode interrupt handler is running in the kernel, particularly in the GDD as a part of the kernel. But it is important to understand that a kernel-mode code that represents the handler is not obligatory to be positioned inside the GDD module, especially before compilation of this driver. This is not necessary. Chiefly that this code must be ready to be called (from a special kernel framework) before an interrupt is generated.

Organizing a Kernel-Mode Interrupt Handler

With the aid of TkdInterrupt component’s property named InterruptHandler, you can define practically any kernel mode interrupt handler. The salt is that the sequence of actions defined by you through this property is transformed to a native kernel code and interpreted at interrupt time, in the kernel mode. The interpreter used in interrupt management runs a virtual machine with one 32-bit accumulator and 15 32-bit additional registers. The following actions are implemented:

Table 1.

Action

Description

Read <TkdHWRangeRegisterObject>

read register and save the value in accumulator

Write <TkdHWRangeRegisterObject>

write register with value stored in accumulator

Read <TkdPCINonBridgeObject> <RegisterName>

read PCI configuration register and save the value in accumulator

Write <TkdPCINonBridgeObject> <RegisterName>

write PCI configuration register with value stored in accumulator

Memread8 <u32-or-Rn>

read byte from the shared memory page (shared between kernel space and user space) by offset given via register R1-R15 or explicit value

Memread16 <u32-or-Rn>

read word from the shared memory page (shared between kernel space and user space) by offset given via register R1-R15 or explicit value

Memread32 <u32-or-Rn>

read dword from the shared memory page (shared between kernel space and user space) by offset given via register R1-R15 or explicit value

Memwrite8 <u32-or-Rn>

write byte to the shared memory page (shared between kernel space and user space) by offset given via register R1-R15 or explicit value

Memwrite16 <u32-or-Rn>

write word to the shared memory page (shared between kernel space and user space) by offset given via register R1-R15 or explicit value

Memwrite32 <u32-or-Rn>

write dword to the shared memory page (shared between kernel space and user space) by offset given via register R1-R15 or explicit value

Mempage

store the address of the shared memory page in accumulator

Or <u32-or-Rn>

change the value in accumulator

And <u32-or-Rn>

change the value in accumulator

Xor <u32-or-Rn>

change the value in accumulator

Add <u32-or-Rn>

change the value in accumulator

Sub <u32-or-Rn>

change the value in accumulator

Store <Rn>

store accumulator to register R1-R15

Load <Rn>

load accumulator from register R1-R15

Printk <string>

printk() given string with possible reference to accumulator

Jz <offset>

jump relative if accumulator is 0

Jnz <offset>

jump relative if accumulator is not 0

Jmp <offset>

jump always

SOI

signal about our card’s interrupt generated

Ret

end of interrupt handler

Some comments to those actions.

Components and their properties are referenced by name. For example, accessing command_reg register of a PCI configuration space whose modelling component is kdPCINonBridge1 must be the following: read kdPCINonBridge1 CommandReg, and not read kdPCINonBridge1 command_reg.

The ‘printk’ action receives as argument a string that may itself include at most one additional parameter given via ‘$’ prefix; this is the value stored in accumulator. At interrupt time, the value will be viewable as hexadecimal. The string is enclosed in double-quotes. The string gets a trailing new line automatically appended.

Registers specified by letter ‘R’, followed by a number in the range 0-15. Non-register arguments are specified in decimal (or hex via ’$’ prefix). Whenever a new interrupt handler procedure is loaded, the state of the virtual machine is reset (all registers are cleared).

For jump instructions, the offset is counted from the next instruction. Therefore, "jmp 0" is a no-operation, and "jmp -1" is an infinite loop. If your jump is too far, the interrupt handler procedure will return; this can be used to save instruction to force a return on a condition.

Any line whose first non-blank character is either '#' or '!' is ignored as a comment.

I/O operation that acts out of bound have undefined results. For your convenience, though, operations that read/write in the memory area have the offset checked and invalid bits are masked out (so you can use an ever-increasing counter to implement a circular buffer with no checks on overflow).

SOI instruction’s abbreviation stands for ‘Set Out Interrupt’. This is to signify that interrupt acknowledgement actions, if provided via InterruptAcknowledgement property, must be applied by the GDD.

Options to Write a Succeeding Interrupt Handler

Several examples on how to employ members of TkdInterrupt component to handle interrupts of your card are brought underneath. The following table is of free style description, and it does not pretend to a complete or a very detailed scheme as well, but tries to explain both basic and related issues at the same time.

Table 2.

Your Card Specificity

ISA Card

PCI Card

(Variant 1)

The card is not frequently interruptible.

Its IRQ number is not shared together with other devices.

Some data savings are not required (e.g. to bifurcate subsequent actions).

Set InterruptType property to itEdgeTriggered.

Set InterruptShared property to False.

Set UseInterruptHandler property to False.

Set UseInterruptAcknowledgement property to False.

Code that performs subsequent actions can be provided as OnHwInterrupt event handler.

Set InterruptType property to itLevelSensitive.

Set InterruptShared property to False.

Provide InterruptAcknowledgement property with necessary acknowledgement actions.

Set UseInterruptAcknowledgement property to True.

Set UseInterruptHandler property to False.

Code that performs subsequent actions can be provided as OnHwInterrupt event handler.

(Variant 2)

The card is not frequently interruptible.

Its IRQ number is not shared together with other devices.

Some data savings are required (e.g. to bifurcate subsequent actions).

See Variant 1.

Additionally, if some data must be delivered to user, provide InterruptHandler property with code saving necessary data to the shared memory page.

Set UseInterruptHandler property to True.

All data saved are later retrieved through SharedMemory property.

Set InterruptType property to itLevelSensitive.

Set InterruptShared property to False.

Provide InterruptAcknowledgement property with necessary acknowledgement actions.

Set UseInterruptAcknowledgement property to True.

Provide InterruptHandler property with code saving necessary data to the shared memory page (this automatically goes before acknowledgement).

Set UseInterruptHandler property to True.

Code that performs subsequent actions can be provided as OnHwInterrupt event handler; in it (and out it) all data saved are retrieved through SharedMemory property.

(Variant 3)

The card is not frequently interruptible.

Its IRQ number is not shared together with other devices.

Acknowledgement requires some data savings (e.g. to bifurcate subsequent actions) (PCI only). (alternative to the above, if acknowledgement itself is data producing)

No acknowledgement can be for ISA’s edge-triggered interrupts.

The exclusion from the above (Variant 2) is that interrupt acknowledgement itself is too complicated.

Set UseInterruptAcknowledgement property to False and provide InterruptHandler property with a combined code performing interrupt acknowledgement with simultaneous data savings into the shared memory page.

(Variant 4)

The card is not frequently interruptible.

Its IRQ number is shared together with other devices.

The same IRQ number can not be used together with other devices for ISA. However you can observe how interrupts are generated by ISA (not PCI) device already involved, attaching to its handler and perform any actions interesting to you.

Set InterruptType property to itEdgeTriggered.

Set InterruptShared property to True.

For statistics, OnHwInterrupt event handler assigned can advert to such properties as InterruptsDirected, InterruptLastTime, InterruptPreviousTime and other.

Look at the description of InterruptRTSynchronized property as well.

The exclusions from Variant 1 is that:

InterruptShared property must be set to True. This also leads to providing InterruptHandler property with code determining whether it is your card asks for interrupt or not. If it is your card’s interrupt detected, use SOI action in InterruptHandler property to signify that. In this case interrupt acknowledgement actions specified in InterruptAcknowledgement property will be involved, otherwise not.

(Variant 5)

The card is frequently interruptible.

Its IRQ number is shared together with other devices.

Code that performs subsequent actions does not exist

Similar to Variant 4

Similar to Variant 4

(Variant 6)

The card is frequently interruptible.

Its IRQ number is shared together with other devices.

Code that performs subsequent actions exists

See description of PCI card variant

InterruptHandler property is mainly involved.

The best solution is to totally provide InterruptHandler property with all the stages of interrupt handling code.

In this case set UseInterruptAcknowledgement property to False.

Alternatively, you can provide InterruptHandler property with 1st and 3rd stages of the whole interrupt handling code and use InterruptAcknowledgement property for 2nd stage that will be executed at the end of kernel-mode interrupt handler. If so, do not forget to provide InterruptAcknowledgement property with necessary acknowledgement actions and set UseInterruptAcknowledgement property to True.

Surely, the table does not contain all possible examples and the whole interrupt handling code can be even more complicated than the table could explain. For example, a user code can deliver data into the shared memory page in order for the kernel-mode interrupt handler to use it. Probably this is an exclusive situation. A good tone that kernel-mode interrupt handlers of frequently interruptible devices to organize (via the shared memory page) circular buffers containing data read by OnHwInterrupt event handlers or other parts of a user application. The simple idea of circular buffers is so good that the GDD ‘s inter-spatial interrupt-bearing pipe between the user and the kernel spaces based on the circular buffer technique is very stable working during all the development time of this product.

Finally, additional information regarding settings of TkdInterrupt component’s properties and its OnHwInterrupt event handler invocation is brought below:

Table 3.

(Use)InterruptHandler

(Use)InterruptAcknowledgement

Shared

LevelSensitive

Comments

OnHwInterrupt Event Handler

-

-

-

-

OK

Always fires

-

-

-

X

Either InterruptHandler or InterruptAcknowledgement must be involved in

Always fires

-

-

X

-

Allowed

Always fires

-

-

X

X

InterruptHandler must be involved in

Fires if SOI action was applied

-

X

-

-

Can not be

-

-

X

-

X

OK

Always fires

-

X

X

-

Can not be

-

-

X

X

X

InterruptHandler must be involved in

Fires if SOI action was applied

X

-

-

-

OK

Always fires

X

-

-

X

OK

Always fires

X

-

X

-

OK

Fires if SOI action was applied

X

-

X

X

OK

Fires if SOI action was applied

X

X

-

-

Can not be

-

X

X

-

X

OK

Always fires

X

X

X

-

Can not be

-

X

X

X

X

OK

Fires if SOI action was applied

Debugging Technique

Debugging possibilities of the toolkit are not that sophisticated as you might suspect. The Application Library’s code execution can be traced in no way; just there is no imaginable benefit from it. All the debugging help for an end-user in this stage of development was put in place on the GDD side, at least because the most part of the overall execution is there. However, kernel code can not be easily executed under a debugger, nor can it be easily traced, because it is a set of functionalities not related to a specific process. Especially since you have no whole source code of the GDD. Thus, for now, there was implemented the most common technique – debugging by printing. If you turn on the printing, you can observe which calls are done under different trying circumstances and which errors and good passings take place. It is because all the GDD is penetrated with tons of kernel-mode messages which can be logged.

How Debug Messages Get Logged

Concerning the GDD, you have 3 modes of functioning:

    1. Writing debug messages into a system circular buffer. This is allowed for root privileges only. Open extern_defs.c source file located in module directory. Uncomment const int printk_type = PRINTK_TYPE_DEBUG and comment-out the remaining strings with the same constant. Save the file and rebuild the GDD (see FAQ). After that open a console/terminal, stay there the root user, and, finally, execute this command: cat /proc/kmsg at the console prompt. All debug messages will be delivered onto this console/terminal right from a system circular buffer.
    2. Writing debug messages into the console/terminal of an application. This is allowed for any user. Especially if your application is windowed, this feature allows you to monitor all the debug messages on a console/terminal from which your application was executed. Open extern_defs.c source file located in module directory. Uncomment const int printk_type = PRINTK_TYPE_APPLICATION_TTY and comment-out the remaining strings with the same constant. Save the file and rebuild the GDD (see FAQ). All debug messages will be delivered where mentioned above.
    3. No debug. A massive use of printing debug messages can slow down the system noticeably, because every line that is printed causes a disk operation. However, you do not want to slow down your system in the ultimate variant of your user-mode – kernel-mode driver pair. The possibility is to turn the debugging off at all. Open extern_defs.c source file located in module directory. Uncomment const int printk_type = PRINTK_TYPE_NONE and comment-out the remaining strings with the same constant. Save the file and rebuild the GDD (see FAQ). All debug messages and commands will be removed from the GDD’s body.

 

Performing DMA

There are two ways to perform DMA: Contiguous Buffer DMA and Scatter/Gather DMA. Scatter/Gather DMA is much more efficient than contiguous DMA. This method allows the PCI device to copy memory blocks from different addresses. This means that the transfer can be done directly to/from the user's buffer that is contiguous in virtual memory, but fragmented in the physical memory. If your PCI device does not support Scatter/Gather, you will need to allocate a physically contiguous memory block, perform the DMA transfer to there, and then copy the data to your own buffer.

The programming of DMA is specific for different PCI devices. Normally, you need to program your PCI device with the Local address (on your PCI device), the Host address (the physical memory address on your PC) and the transfer count (size of block to transfer), and then set the register that initiates the transfer.

Scatter/Gather DMA scheme

The following is an outline of a DMA transfer routine for PCI devices that support Scatter/Gather DMA.

Of course, you have to implement the following three routines that are extremely dependent on your particular device:

Contiguous DMA scheme (read sequence)

The following is a read sequence from the card to the motherboard's memory.

Contiguous DMA scheme (write sequence)

The following is a write sequence from the motherboard's memory to the card.