Writing kernel mode drivers for ELKSLike most alternative operating systems, ELKS is missing drivers for many peripherals. Therefore this text describes to write additional drivers. As a proof of concept, the 'hellodev' driver will be implemented now: /* * hello driver for ELKS kernel */ #include <linuxmt/kernel.h> /* printk() */ #include <linuxmt/fs.h> /* file_operations struct */ #include <string.h> #define HELLO_DEVICE_NAME "hellodev" #define HELLO_MAJOR 9 static char message[256]; char *msgptr = message; static int hello_write(struct inode *inode, struct file *file, char* buf, int len) { memcpy_fromfs(message,buf,len); printk("hellodev: hello_write() called\n"); return 0; } static int hello_read(struct inode *inode, struct file *file, char* buf, int len) { memcpy_tofs(buf, message, len); printk("hellodev: hello_read() called\n"); return len; } static int hello_open(struct inode *inode, struct file *file) { printk("hellodev: hello_open() called\n"); return 0; } static void hello_release(struct inode *inode, struct file *file) { printk("hellodev: hello_release() called\n"); return; } static struct file_operations hello_fops = { NULL, /* lseek */ hello_read, /* read */ hello_write, /* write */ NULL, /* readdir */ NULL, /* select */ NULL, /* ioctl */ hello_open, /* open */ hello_release /* release */ #ifdef BLOAT_FS , NULL, /* fsync */ NULL, /* check_media_type */ NULL /* revalidate */ #endif }; void hello_init(void) { printk("hellodev: hello_init() called\n"); /* register device */ if (register_chrdev(HELLO_MAJOR, HELLO_DEVICE_NAME, &hello_fops)) printk("hellodev: unable to register\n"); } This driver just implements the functions hello_init(), hello_open(), hello_read(), hello_write() and hello_release(). When these functions are called it outputs a message using the printk() statement. This is the equivalent for printf() in kernel code. Also the functions memcpy_fromfs() and memcpy_tofs() are used. The kernel will for security reasons not accept pointers from userspace which may be faulty. Therefore you have to use these functions. For single chars there are equivalent functions available called get_user_char() and put_user_char(). You will also not be able to use inportb() and outportb() functions, use the inb() and inb_p() or out() and out_p() functions instead. The „_p“ versions insert a small pause or delay before returning to support slower devices. When the driver is loaded the kernel will call hello_init() and the hellodev driver will execute the register_chrdev() function. This function contains the device name which will appear in the /dev directory and a pointer to the file_operations structure called hello_ops. In there the kernel will find the addresses of the functions defined in the driver. The kernel identifies the driver by its unique the major number. In our demo driver we have included these macros in the code: #define HELLO_DEVICE_NAME "hellodev" #define HELLO_MAJOR 9 Usually in ELKS these are defined in the elks/include/linuxmt/major.h file which the drivers include. However, that file still needs to be changed. Increase the maximum number of char devices to 10: #define MAX_CHRDEV 10 Put the code above as the hellodev.c file into the elks/arch/i86/drivers/char/ directory and add it to the Makefile in this directory: else OBJS = bioscon.o common.o serial.o lp.o xt_key.o init.o dircon.o mem.o \ hellodev.o ntty.o meta.o tcpdev.o pty.o bell.o # clist.o tty.o Also edit the init.c file: void chr_dev_init(void) { #if 1 hello_init(); #endif #ifdef CONFIG_CHAR_DEV_RS rs_init(); Normally, you would define a macro in the elks/arch/i86/defconfig file which can be modified with „menuconfig“ and modify the files elks/arch/i86/drivers/char/config.in plus Documentation/Configure.help. Further, the hello_init() function has to be declared in the elks/include/linuxmt/init.h file: extern void xtk_init(void); extern void hello_init(void); extern void init_console(void); To load the driver simply add it after the tcpdev device in the elkscmd/rootfs_template/dev/MAKEDEV script. In this script there is a macro for the mknod() function called MAKEDEV: # TCPDEV, used by ktcp mknod tcpdev c 8 0 $MKDEV hellodev c 9 0 The letter „c“ stands for character driver while the number 9 is the major number and zero the minor number. To sum it up, you added hellodev.c into the elks/arch/i86/drivers/char directory and modified init.c and the Makefile in there. Also you modified major.h and init.h in the elks/include/linuxmt directory and the file MAKEDEV in the elkscmd/rootfs_template/dev directory. Now you can compile ELKS again and it will include the hellodev driver and load it. If you enter „ls /dev“ you will see a device node called hellodev. To test this driver use this program which you may drvtest.c: #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<fcntl.h> #define BUFFER_LENGTH 256 static char receive[BUFFER_LENGTH]; int main(){ int ret, fd; char stringToSend[BUFFER_LENGTH]; printf("Starting device test code example...\n"); fd = open("/dev/hellodev", O_RDWR); if (fd < 0){ perror("Failed to open the device..."); return errno; } printf("Type in a short string to send to the kernel module:\n"); scanf("%s", stringToSend); // Read in a string printf("Writing message to the device [%s].\n", stringToSend); ret = write(fd, stringToSend, strlen(stringToSend)); if (ret < 0){ perror("Failed to write the message to the device."); return errno; } printf("Now reading the string back from the device...\n"); ret = read(fd, &receive[0], BUFFER_LENGTH); if (ret < 0){ perror("Failed to read the message from the device."); return errno; } printf("The received message is: [%s]\n", receive); return 0; } To compile this program enter „bcc -ansi -o drvtest drvtest.c“ and e.g. copy the executable drvtest to the full3 image file using a loop device. Here
are some additional notes how ELKS drivers work describing the
concepts used above taken from the fs.txt file:
Since the ELKS kernel is almost 64k in size now, currently there is only very limited space available for additional device drivers. User mode device drivers should be considered as an alternative. 4th of March 2017 Georg Potthast
|