Be Driven
  Device Drivers in the Be Os
     
    C Functions : Kernel Export

C Functions - <KernelExport.h>

When Programming device drivers, there are many features provided by the Operating System that have not been very well documented, if at all.

The result, you end up writing fancy little functions that don't work as well as the Os, and might break in the next version. (Functions like call_all_cpus() for instance).

And remember, device drivers are prone to break from one version to the next, so exploit what custom funtionality exists for your current release !

Spawn Kernel Thread

Please Read the chapter on Thread-An Introduction to Kernel Threads.
So, given you need to create a thread in kernel space, the following function allows you to perform this action. It follows the same function prototype as the normal create_thread() routine described in the BeBook Documentation.

Kernel Threads memory access is not quite as restrictive as Interrupt Functions. They may still 'map' into a shared memory area, like normal teams, and may access any area in Kernel space. They may not access user provided memory at ANY point in time. They belong to a seperate team, and should be treated with respect.

It is difficult to predict when a kernel thread has actually terminated, so if you need to do this, create a variable in shared memory that is set to 0 when the thread is on it's way out. As it will no longer be accessing any memory, you need not worry exactly when it is going to die.

thread_id 
spawn_kernel_thread (
    thread_entry function,
    const char *thread_name, 
    long priority,
    void *arg
    ); 

Interrupt Masking Functions

Read the chapter on Locking - Interrupt Masking.

typedef ulong cpu_status;
cpu_status disable_interrupts();
void restore_interrupts(cpu_status status);

Spin Locks

Read the chapter on Locking - Spin Locks.
Be carefull to always use spinlocks with interrupts disabled.

typedef vlong spinlock;
void acquire_spinlock (spinlock *lock);
void release_spinlock (spinlock *lock);

Interrupt Handling Routines

Read the chapter on Interrupts-Registering Interrupt Functions.

 /* ---
    An interrupt handler returns one of the following values:
--- */ #define B_UNHANDLED_INTERRUPT 0 /* pass to next handler */ #define B_HANDLED_INTERRUPT 1 /* don't pass on */ #define B_INVOKE_SCHEDULER 2 /* don't pass on; invoke scheduler */ typedef int32 (*interrupt_handler) (void *data);
/* interrupt handling support for device drivers */
long install_io_interrupt_handler ( long interrupt_number,
interrupt_handler handler,
void *data,
ulong flags
);
long
remove_io_interrupt_handler ( long interrupt_number, interrupt_handler handler, void *data );

Interrupt Masking Functions

Always call these functions before doing DMA operations.
Information acquired from...

Be Newsletter, Issue 89, September 3, 1997
The Dirty Little Secret of lock_memory()
By Robert Herold

These functions are used to change the behaviour of memory that has been allocated by any method.

By Locking memory, you are always making the memory available in RAM, and in a fixed location, so that it does not move. It does not relocated the memory into any particular region in memory. (ie., will not fix 16MB boundry problem with DMA ).

You may also add 2 optional characteristics to this behavior. You normally use both B_READ_DEVICE | B_DMA_IO, together. (safer)

B_READ_DEVICE is used to tell the kernel that the device being read from will be depositing data directory into memory. In the BeOS implementation of virtual memory, the kernel often attempts to be clever and see if a range of memory has been changed since the last time it was read in from the swap file on disk. If it has not changed, there is no need to write it back to disk when the memory needs to be used for something else.

The MMU has a built in mechanism for detecting changes affected by the processor, but this mechanism does not detect DMA transactions from other devices. The B_READ_DEVICE flag tells the kernel that indeed the memory is going to change, so don't try to be clever about it.

The B_DMA_IO flag is an unfortunate crutch made necessary by what some might perceive as a flawed implementation of the bridge between the PCI bus and the main system memory bus in certain motherboard designs from one of our supported platforms.

Normally, when a DMA transaction is started by a device on the PCI bus and travels though the bridge to system memory, the bridge is responsible for notifying any and all devices on the main system bus (e.g. the processor(s)) that the memory is being written, and that those devices should remove any copies of that memory they may have stashed in a local cache for fast access. This mechanism was not implemented in a few designs -- my suspicion is that it was implemented, but did not work reliably, and was disabled.

The work around for this lame behavior is to tell all the processors to NEVER cache locations that may have DMA transactions coming in. This can result in a significant performance hit if those locations are accessed frequently.

The D_DMA_IO tells the kernel to temporarily mark the range uncacheable until the matching lock_memory.

This turns out to be a very complex operation. Data in that range needs to be flushed from all caches. The data structures used by the cache to determine an address's cacheability need to be updated to reflect the new non-cacheable status -- but if a part of the range shares a cache line with anything that gets used in updating the data structures, it will end up back in the cache!

/* ---
   virtual memory buffer functions
   --- */
#define B_DMA_IO 0x00000001 #define B_READ_DEVICE 0x00000002
typedef struct { void *address; /* address in physical memory */ ulong size; /* size of block */ } physical_entry;
long lock_memory ( void *buf, /* -> virtual buffer to lock (make resident) */ ulong num_bytes, /* size of virtual buffer */ ulong flags ); long unlock_memory ( void *buf, /* -> virtual buffer to unlock */ ulong num_bytes, /* size of virtual buffer */ ulong flags );

Obtaining a Memory Map

Once you have your memory all locked away neat and tight, you will need to find the physical addresses where it now resides.

The get_memory_map() functions provides this ability. Actually, you can use this to quench your curiosity on any memory accessible in your Virtual-Address-Space, but I would suggest locking it first so It stays in one place!
Remember, if you do not create the memory using an Area, with B_CONTIGUOUS characteristics, you will find the memory scattered across the Physical-Address-Space!

long get_memory_map (
    const void *address,    /* -> virtual buffer to translate */
    ulong size,             /* size of virtual buffer */
    physical_entry *table,  /* -> caller supplied table */
    long num_entries        /* # entries in table */
    );

So to use it you will want to create an array of physical_entry elements. You would normally pass in the location of the first byte in your allocated memory, and the size of the memory allocated to get all the page locations.

physical_entry area_phys_addr[100]; // start + 99 segments
get_memory_map( base, base_size, area_phys_addr, 99 );

Map Physical Memory into Virtual Address Space

-- Info Taken from the printed Be Advanced Topics

This function allows you to map the memory in physical-address-space starting at physical_address and extending numBytes into your team's address space. The kernel creates an area named area_name mapped into the memory address mapped_address and returns its area_id to the caller.

numBytes must be a multiple of B_PAGE_SIZE (4096).

flags must be either B_ANY_KERNEL_ADDRESS or B_ANY_KERNEL_BLOCK_ADDRESS. If spec is B_ANY_KERNEL_ADDRESS, the memory will begin at an arbitrary location in the kernel address space. If spec is B_ANY_KERNEL_BLOCK_ADDRESS, memory will be alligned on a multiple of B_PAGE_SIZE (4096).

protection is a bitmask consisting of the fields B_READ_AREA and B_WRITE_AREA, as discussed in create_area().

the error codes are the same as those for create_area().

/* -----
   address specifications for mapping physical memory
   ----- */

#define B_ANY_KERNEL_BLOCK_ADDRESS ((B_ANY_KERNEL_ADDRESS)+1)


/* -----
   MTR attributes for mapping physical memory (Intel Architecture only)
   ----- */
#define B_MTR_UC 0x10000000
#define B_MTR_WC 0x20000000
#define B_MTR_WT 0x30000000
#define B_MTR_WP 0x40000000
#define B_MTR_WB 0x50000000
#define B_MTR_MASK 0xf0000000
/* ----- call to map physical memory - typically used for memory-mapped i/o ----- */ area_id map_physical_memory ( const char *area_name, void *physical_address, size_t numBytes, uint32 flags, uint32 protection, void **mapped_address );

Platform Stuff

Want to get details about your platform?

typedef enum platform_types {
    B_BEBOX_PLATFORM = 0,
    B_MAC_PLATFORM,
    B_AT_CLONE_PLATFORM 
    // + other silly ones..
} platform_type;
extern _IMPEXP_KERNEL platform_type platform();
#if __POWERPC__ extern _IMPEXP_KERNEL long motherboard_version (void); extern _IMPEXP_KERNEL long io_card_version (void); #endif

Kernel Debugging

/* ---
primitive kernel debugging facilities. Debug output is on...
bebox: serial port 4
mac: modem port
pc: com1

...at 19.2 kbaud, no parity, 8 bit, 1 stop bit.
--- */

extern void dprintf (const char *format, ...); /* just like printf */
extern bool set_dprintf_enabled( bool new_state ); /* returns old state */
extern void panic(const char *format, ...);
extern void kernel_debugger (const char *message); /* enter kernel debugger */
extern void kprintf (const char *fmt, ...); /* only for debugger cmds */
extern ulong parse_expression (char *str); /* util for debugger cmds */
/* special return codes for kernel debugger */
#define B_KDEBUG_CONT 2 #define B_KDEBUG_QUIT 3
extern int add_debugger_command (char *name, /* add a cmd to debugger */
int (*func)(int argc, char **argv),
char *help);
extern int remove_debugger_command (char *name, /* remove a cmd from debugger */
int (*func)(int argc, char **argv));
extern int load_driver_symbols( const char *driver_name );

Spin

"Executes a delay lasting at least the specified number of micro-seconds. It could last longer, due to rounding errors, interrupts, and context switches."
-- Info Taken from the printed Be Advanced Topics

When doing low-level I/O to a board, there are times when you have to pace how fast you access the poor thing. A Task swap may be to long to wait, or even not possible (if in an interrupt), so you need a processor independent method for waiting some small period of time..

So, if you have to wait only a really tiny period, then possibly you may want to block interrupts to guarantee your time frame.. Of course doing this with anything larger than the smallest delay is going to cripple system performance..

If you use it, execute your 2 or 3 commands that must occur near each other, and then force a task swap using sleep(1); (if you arn't an interrupt).

Think more like a spinlock with this type of operation.

void spin (bigtime_t num_microseconds);

Register Periodic Daemon in the Kernel

"Adds or removes daemons from the kernel. A kernel daemon function is executed approximately once every freq/10 seconds. (10ths of a second). The kernel calls func with the arguments arg and an iteration value that increase by freq on successive calls to the daemon."

-- Info Taken from the printed Be Advanced Topics

I have no idea why you would want the interation value passed into you?
Ohh well.

int register_kernel_daemon( 
    void (*func)(void *, int), void *arg, int freq);

int unregister_kernel_daemon( 
    void (*func)(void *, int), void *arg); 

Signals Pending

Returns a bitmask of the current pending signals for the current thread. thr should always be NULL; passing other values at this point is undocumented, and thus is not much use to you.

Returns 0 if no signals are pending, and other if there are.

You may want to use this function when???

extern int has_signals_pending(struct thread_rec *);

Call All Cpus

Ha Ha Ha Ha <evil laugh>
Now, at a guess, this is Dmitriy Budko's doing ;-)

Be Newsletter, Volume II, Issue 21; May 27, 1998
Windows 95 Experience on BeOS -- Or How to Hack on BeOS
By Dmitriy Budko

"spawn a real-time priority kernel thread per CPU, then block and simultaneously release them. Use spinlocks to synchronize threads with interrupts disabled ... , because occasionally... the registers have to be synchronously set up on all CPUs in the system. All interrupts must be disabled. To do that you have to designate a thread per CPU, which the BeOS does not support (i.e., CPU affinity for threads)."

At a guess, the call will come back to you, passes in the cookie, and your CPU number. I can not tell you if this thread will always remain on one processor. I would make a guess that your are in Kernel space.

If you are going to use this function, write to Be for instructions on exactly how to use it.

void call_all_cpus(void (*f)(void*, int), void* cookie);

Disable DMA operations on the IDE bus

This is a question function.

Will return 1 if DMA is disabled. I do not know exactly the ramifications of this operation.

bool ide_dma_disabled(void);

Are User Add-Ons Disabled

Now, I really dont know what this is about. It could be possible that when you boot up in safe mode, and the user selects disable user addons, that you have to check this flag, and not load your driver if this is the case.. This could be very usefull when you are doing in-house development, and just happen to have code that crashes things all the time!

bool disable_user_addons(void);

Missing Functions that you might want to use.

Looking through R4.0 kernel, the following symbols are STILL publically available, and you may choose to use them at your own peril.

Of course, Be will tell you that you should go through either the ISA bus or PCI bus modules, and they are probably right, but here they are for them who have serious suicidal tendencies. This is probably still here for compatability with the old 3.0 drivers..

Be carefull 4.5 may truly dispose of these functions...

extern _IMPEXP_KERNEL uint8  read_io_8( int port );
extern _IMPEXP_KERNEL void write_io_8( int port, uint8 value );
extern _IMPEXP_KERNEL uint16 read_io_16( int port );
extern _IMPEXP_KERNEL void write_io_16( int port, uint16 value );
extern _IMPEXP_KERNEL uint32 read_io_32( int port );
extern _IMPEXP_KERNEL void write_io_32( int port, uint32 value );

The Communal Be Documentation Site
1999 - bedriven.miffy.org