Difference between revisions of "linux/kernel/module development"
Line 220: | Line 220: | ||
function unregister_chrdev(devMajor:LongInt; devName:PChar):LongInt; cdecl; external; | function unregister_chrdev(devMajor:LongInt; devName:PChar):LongInt; cdecl; external; | ||
end. | end. | ||
+ | </code> | ||
+ | |||
+ | <code> | ||
+ | unit kernel_module; | ||
+ | {$TYPEINFO OFF} | ||
+ | interface | ||
+ | |||
+ | implementation | ||
+ | const | ||
+ | PROCFS_MAX_SIZE = 1024; | ||
+ | PROCFS_NAME = 'kpmod'; | ||
+ | BUF_LEN = 256; | ||
+ | var | ||
+ | {Major number assigned to our device driver} | ||
+ | devMajor:LongInt; | ||
+ | fops:file_operations; | ||
+ | i, j, k:LongInt; | ||
+ | {Is device open? Used to prevent multiple access to device} | ||
+ | devIsOpen:Boolean = False; | ||
+ | {pattern:Array[0..1, 0..7, 0..7]of LongInt = ( | ||
+ | (($A,$9,$5,$6,$A,$9,$5,$6), ($A,$8,$9,$1,$5,$4,$6,$2)), | ||
+ | (($6,$5,$9,$A,$6,$5,$9,$A), ($2,$6,$4,$5,$1,$9,$8,$A)));} | ||
+ | {This structure hold information about the /proc file} | ||
+ | Our_Proc_File:Pproc_dir_entry; | ||
+ | {The buffer used to store character for this module} | ||
+ | //procfs_buffer:Array[0..PROCFS_MAX_SIZE - 1]of Char; | ||
+ | {The size of the buffer} | ||
+ | procfs_buffer_size:Cardinal = 0; | ||
+ | {The msg the device will give when asked} | ||
+ | msg:Array[0..BUF_LEN-1]of Char; | ||
+ | msg_Ptr:PChar; | ||
+ | |||
+ | {* This function is called then the /proc file is read | ||
+ | * | ||
+ | * Arguments | ||
+ | * ========= | ||
+ | * 1. The buffer where the data is to be inserted, if | ||
+ | * you decide to use it. | ||
+ | * 2. A pointer to a pointer to characters. This is | ||
+ | * useful if you don't want to use the buffer | ||
+ | * allocated by the kernel. | ||
+ | * 3. The current position in the file | ||
+ | * 4. The size of the buffer in the first argument. | ||
+ | * 5. Write a "1" here to indicate EOF. | ||
+ | * 6. A pointer to data (useful in case one common | ||
+ | * read for multiple /proc/... entries) | ||
+ | * | ||
+ | * Usage and Return Value | ||
+ | * ====================== | ||
+ | * A return value of zero means you have no further | ||
+ | * information at this time (end of file). A negative | ||
+ | * return value is an error condition. | ||
+ | * | ||
+ | * For More Information | ||
+ | * ==================== | ||
+ | * The way I discovered what to do with this function | ||
+ | * wasn't by reading documentation, but by reading the | ||
+ | * code which used it. I just looked to see what uses | ||
+ | * the get_info field of proc_dir_entry struct (I used a | ||
+ | * combination of find and grep, if you're interested), | ||
+ | * and I saw that it is used in <kernel source | ||
+ | * directory>/fs/proc/array.c. | ||
+ | * | ||
+ | * If something is unknown about the kernel, this is | ||
+ | * usually the way to go. In Linux we have the great | ||
+ | * advantage of having the kernel source code for | ||
+ | * free - use it. | ||
+ | *} | ||
+ | function procfile_read(buffer:PChar; buffer_location:PPChar; offset:off_t; buffer_length:LongInt; eof:PLongInt; data:Pointer):LongInt; cdecl; | ||
+ | begin | ||
+ | printk(KERN_INFO + 'procfile_read (/proc/%s) called'#10, PROCFS_NAME); | ||
+ | {* | ||
+ | * We give all of our information in one go, so if the | ||
+ | * user asks us if we have more information the | ||
+ | * answer should always be no. | ||
+ | * | ||
+ | * This is important because the standard read | ||
+ | * function from the library would continue to issue | ||
+ | * the read system call until the kernel replies | ||
+ | * that it has no more information, or until its | ||
+ | * buffer is filled. | ||
+ | *} | ||
+ | if offset > 0 then begin | ||
+ | {we have finished to read, return 0} | ||
+ | procfile_read := 0; | ||
+ | end else begin | ||
+ | {fill the buffer, return the buffer size} | ||
+ | //move('HelloWorld!'#10#0, buffer, 13); | ||
+ | //memcpy(buffer, procfs_buffer, procfs_buffer_size); | ||
+ | buffer[0] := 'H'; | ||
+ | buffer[1] := 'e'; | ||
+ | buffer[2] := 'l'; | ||
+ | buffer[3] := 'l'; | ||
+ | buffer[4] := 'o'; | ||
+ | buffer[5] := #10; | ||
+ | buffer[6] := #0; | ||
+ | procfile_read := 6; | ||
+ | end; | ||
+ | end; | ||
+ | {This function is called with the /proc file is written} | ||
+ | function procfile_write(_file:Pointer{struct file *}; buffer:PChar; count:Cardinal; data:Pointer):LongInt; cdecl; | ||
+ | begin | ||
+ | printk(KERN_INFO + 'procfile_write (/proc/%s) called'#10, PROCFS_NAME); | ||
+ | {get buffer size} | ||
+ | procfs_buffer_size := count; | ||
+ | if procfs_buffer_size > PROCFS_MAX_SIZE then | ||
+ | procfs_buffer_size := PROCFS_MAX_SIZE; | ||
+ | {write data to the buffer} | ||
+ | //if copy_from_user(@procfs_buffer, buffer, procfs_buffer_size) <> 0 then | ||
+ | // procfile_write := -EFAULT | ||
+ | //else | ||
+ | procfile_write := procfs_buffer_size; | ||
+ | end; | ||
+ | {Called when a process tries to open the device file, like "cat /dev/kpmod"} | ||
+ | function device_open(inode:Pointer{struct inode *}; filp:Pointer{struct file *}):LongInt; cdecl; | ||
+ | begin | ||
+ | //static int counter = 0; | ||
+ | if devIsOpen then | ||
+ | Exit(-EBUSY); | ||
+ | printk('Ouverture en mode Lecture/Ecriture ...'#10); | ||
+ | //sprintf(msg, "I already told you %d times Hello world!'#10, counter += 1); | ||
+ | //msg_Ptr = msg; | ||
+ | //try_module_get(THIS_MODULE); | ||
+ | devIsOpen := True; | ||
+ | device_open := 0; | ||
+ | end; | ||
+ | {Called when a process, which already opened the dev file, attempts to read from it.} | ||
+ | function device_read(filp:Pointer{struct file *}; buffer:PChar; len:size_t; offset:Ploff_t):ssize_t; cdecl; | ||
+ | var | ||
+ | bytes_read:LongInt; | ||
+ | begin | ||
+ | {Number of bytes actually written to the buffer} | ||
+ | bytes_read := 0; | ||
+ | printk('Writing %d data into buffer from offset %%lld'#10, len{, offset^}); | ||
+ | {Actually put the data into the buffer} | ||
+ | //while(len > 0) and (msg_Ptr^ <> #0)do begin | ||
+ | {The buffer is in the user data segment, not the kernel | ||
+ | segment so "*" assignment won't work. We have to use | ||
+ | put_user which copies data from the kernel data segment to | ||
+ | the user data segment.} | ||
+ | //put_user(msg_Ptr^, buffer += 1); | ||
+ | // msg_Ptr += 1; | ||
+ | // len -= 1; | ||
+ | // bytes_read += 1; | ||
+ | //end; | ||
+ | {If we're at the end of the message, return 0 signifying end of file} | ||
+ | {Most read functions return the number of bytes put into the buffer} | ||
+ | device_read := bytes_read; | ||
+ | end; | ||
+ | {Called when a process writes to dev file: "echo 'hi' > /dev/hello"} | ||
+ | function device_write(filp:Pointer{struct file *}; buff:Pchar; len:size_t; offset:Ploff_t):ssize_t; cdecl; | ||
+ | function step:LongInt; | ||
+ | begin | ||
+ | if k < 8 then begin | ||
+ | //printk('%d'#10,pattern[i][j][k]); | ||
+ | //outb(pattern[i][j][k],LPT_BASE); | ||
+ | k += 1; | ||
+ | end else begin | ||
+ | k := 0; | ||
+ | //outb(pattern[i][j][k],LPT_BASE); | ||
+ | //printk('%d'#10,pattern[i][j][k]); | ||
+ | k += 1; | ||
+ | end; | ||
+ | step := 0; | ||
+ | end; | ||
+ | var | ||
+ | data:Char; | ||
+ | cmd:Char; | ||
+ | begin | ||
+ | printk(KERN_ALERT + 'Sorry, write operation on "%s" isn''t supported yet.'#10, '/dev/kpmod'); | ||
+ | device_write := -EINVAL; | ||
+ | { | ||
+ | //get_user(data,buffer); | ||
+ | cmd := data; | ||
+ | case cmd of | ||
+ | 'H':begin | ||
+ | printk('Reportez-vous au fichier README'#10); | ||
+ | end; | ||
+ | 'h':begin | ||
+ | printk('Initialisation en mode demi pas'#10); | ||
+ | j=0; | ||
+ | end; | ||
+ | 'f':begin | ||
+ | printk('Initialisation en mode pas entier'#10); | ||
+ | j=1; | ||
+ | end; | ||
+ | 'F':begin | ||
+ | i=0; | ||
+ | step(); | ||
+ | end; | ||
+ | 'R':begin | ||
+ | i=1; | ||
+ | step(); | ||
+ | end; | ||
+ | else | ||
+ | printk('Reportez-vous au fichier README, les commandes sont H, h, F, f, et R'#10); | ||
+ | end; | ||
+ | device_write := 1;} | ||
+ | end; | ||
+ | {Called when a process closes the device file.} | ||
+ | function device_release(inode:Pointer{struct inode *}; filp:Pointer{struct file *}):LongInt; cdecl; | ||
+ | begin | ||
+ | printk('Fermeture ...'#10); | ||
+ | {Decrement the usage count, or else once you opened the file, you'll never get get rid of the module.} | ||
+ | //module_put(THIS_MODULE); | ||
+ | {We're now ready for our next caller} | ||
+ | devIsOpen := False; | ||
+ | device_release := 0; | ||
+ | end; | ||
+ | {This function is called when the module is loaded} | ||
+ | function init_module: Integer; cdecl; export; | ||
+ | begin | ||
+ | with fops do begin | ||
+ | open := @device_open; | ||
+ | read := @device_read; | ||
+ | write := @device_write; | ||
+ | release := @device_release; | ||
+ | end; | ||
+ | devMajor := register_chrdev(0, DEVICE_NAME, @fops); | ||
+ | if devMajor < 0 then begin | ||
+ | printk(KERN_ALERT + 'Registering char device failed with %d'#10, devMajor); | ||
+ | Exit(devMajor); | ||
+ | end; | ||
+ | printk(KERN_INFO + 'I was assigned major number %d. To talk to'#10, devMajor); | ||
+ | printk(KERN_INFO + 'the driver, create a dev file with'#10); | ||
+ | printk(KERN_INFO + '"mknod /dev/%s c %d 0".'#10, DEVICE_NAME, devMajor); | ||
+ | printk(KERN_INFO + 'Try various minor numbers. Try to cat and echo to'#10); | ||
+ | printk(KERN_INFO + 'the device file.'#10); | ||
+ | printk(KERN_INFO + 'Remove the device file and module when done.'#10); | ||
+ | printk(KERN_INFO + '@fops = $%X'#10, LongInt(@fops)); | ||
+ | {create the /proc file} | ||
+ | Our_Proc_File := create_proc_entry(PROCFS_NAME, 0644, Nil); | ||
+ | if Our_Proc_File = Nil then begin | ||
+ | remove_proc_entry(PROCFS_NAME, @proc_root); | ||
+ | printk(KERN_ALERT + 'Could not initialize /proc/%s'#10, PROCFS_NAME); | ||
+ | Exit(-ENOMEM); | ||
+ | end; | ||
+ | printk(KERN_INFO + 'Our_Proc_File = 0x%X'#10, PtrUInt(Our_Proc_File)); | ||
+ | with Our_Proc_File^ do begin | ||
+ | read_proc := @procfile_read; | ||
+ | write_proc := @procfile_write; | ||
+ | //owner := THIS_MODULE; | ||
+ | //mode := S_IFREG OR S_IRUGO; | ||
+ | uid := 0; | ||
+ | gid := 0; | ||
+ | size := 37; | ||
+ | end; | ||
+ | printk(KERN_INFO + '/proc/%s created'#10, PROCFS_NAME); | ||
+ | {everything is ok} | ||
+ | init_module := 0; | ||
+ | end; | ||
+ | |||
+ | {This function is called when the module is unloaded} | ||
+ | procedure cleanup_module; cdecl; export; | ||
+ | var | ||
+ | ret:LongInt; | ||
+ | begin | ||
+ | remove_proc_entry(PROCFS_NAME, @proc_root); | ||
+ | printk(KERN_INFO + '/proc/%s removed'#10, PROCFS_NAME); | ||
+ | ret := unregister_chrdev(devMajor, DEVICE_NAME); | ||
+ | if ret < 0 then | ||
+ | printk(KERN_ALERT + 'Error in unregister_chrdev: %d'#10, ret); | ||
+ | end; | ||
+ | |||
+ | end. | ||
+ | </code> | ||
+ | obj-m := kernel_pmodule.o | ||
+ | kernel_pmodule-objs := kernel_module_info.o kernel_module.o system.o | ||
+ | KDIR := /lib/modules/$(shell uname -r)/build | ||
+ | PWD := $(shell pwd) | ||
+ | |||
+ | default:kernel_module.o | ||
+ | $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules | ||
+ | kernel_module.o: kernel_module.pas system.pas | ||
+ | fpc kernel_module | ||
+ | clean: | ||
+ | ${RM} *.ko *.mod.c *.o *.ppu Module.symvers .*.ko.cmd .*.o.cmd | ||
+ | ${RM} -r .tmp_versions | ||
+ | <code> | ||
+ | |||
</code> | </code> |
Revision as of 11:20, 5 October 2007
overview
The purpose of this page is to give basic material to write Linux kernel modules using FPC. As Linux distributions may differ, most actions on installing packages are described for Debian distribution, on with author of this article is working. However, it may be very simple to translate this for other Linux distributions.
Requirements
1) First of all, you need to have FPC installed
apt-get install fp-compiler
Please notice that this will install fp-compiler and fp-units-rtl. The author of this page is using 2.2.0, but 2.0.4 is also working. However other versions were not tested an my not work, especially previous releases. 2) you need to have kernel headers packages installed
apt-get install linux-headers-$(uname -r)
3) Then you need make utility
apt-get install make
Please note that installing linux-headers-* will install automatically the required version of GCC and binutils.
Kernel RTL
It is obvious that when programming Linux kernel modules, one can not use the standard FPC RTL. As nos standard kernel RTL is provided, you have to write your own one. This is not very easy. A good start point could be the following
unit system;
{$TYPEINFO OFF}
interface
{Paѕcal common type aliases}
type
{Basic embedded types}
u8 = Byte;
u16 = Word;
u32 = LongWord;
u64 = QWord;
s8 = ShortInt;
s16 = SmallInt;
s32 = LongInt;
s64 = Int64;
{Integer types}
DWord = LongWord;
Cardinal = LongWord;
Integer = SmallInt;
UInt64 = QWord;
{$ifdef CPU64}
SizeInt = Int64;
SizeUInt = QWord;
PtrInt = Int64;
PtrUInt = QWord;
ValSInt = int64;
ValUInt = qword;
{$endif CPU64}
{$ifdef CPU32}
SizeInt = Longint;
SizeUInt = DWord;
PtrInt = Longint;
PtrUInt = DWord;
ValSInt = Longint;
ValUInt = Cardinal;
{$endif CPU32}
{Zero - terminated strings }
PChar = ^Char;
PPChar = ^PChar;
{Pointers}
PSmallInt = ^Smallint;
PShortInt = ^Shortint;
PInteger = ^Integer;
PByte = ^Byte;
PWord = ^word;
PDWord = ^DWord;
PLongWord = ^LongWord;
PLongint = ^Longint;
PCardinal = ^Cardinal;
PQWord = ^QWord;
PInt64 = ^Int64;
PPtrInt = ^PtrInt;
PPtrUInt = ^PtrUInt;
PSizeInt = ^SizeInt;
PPointer = ^Pointer;
PPPointer = ^PPointer;
PBoolean = ^Boolean;
PWordBool = ^WordBool;
PLongBool = ^LongBool;
{Other types}
HRESULT = type Longint;
TDateTime = type Double;
TError = type Longint;
const
KERN_INFO='KERNEL:INFO:';
KERN_ALERT='KERNEL:ALERT:';
DEVICE_NAME='kpmod';
EPERM = 1;{Operation not permitted}
ENOENT = 2;{No such file or directory}
ESRCH = 3;{No such process}
EINTR = 4;{Interrupted system call}
EIO = 5;{I/O error}
ENXIO = 6;{No such device or address}
E2BIG = 7;{Argument list too long}
ENOEXEC = 8;{Exec format error}
EBADF = 9;{Bad file number}
ECHILD = 10;{No child processes}
EAGAIN = 11;{Try again}
ENOMEM = 12;{Out of memory}
EACCES = 13;{Permission denied}
EFAULT = 14;{Bad address}
ENOTBLK = 15;{Block device required}
EBUSY = 16;{Device or resource busy}
EEXIST = 17;{File exists}
EXDEV = 18;{Cross-device link}
ENODEV = 19;{No such device}
ENOTDIR = 20;{Not a directory}
EISDIR = 21;{Is a directory}
EINVAL = 22;{Invalid argument}
ENFILE = 23;{File table overflow}
EMFILE = 24;{Too many open files}
ENOTTY = 25;{Not a typewriter}
ETXTBSY = 26;{Text file busy}
EFBIG = 27;{File too large}
ENOSPC = 28;{No space left on device}
ESPIPE = 29;{Illegal seek}
EROFS = 30;{Read-only file system}
EMLINK = 31;{Too many links}
EPIPE = 32;{Broken pipe}
EDOM = 33;{Math argument out of domain of func}
ERANGE = 34;{Math result not representable}
type
{Kernel types}
mode_t = Word;
nlink_t = DWord;
uid_t = Word;
fl_owner_t = ^files_struct;
gid_t = Word;
off_t = LongInt;
loff_t = Int64;
Ploff_t = ^loff_t;
size_t = DWord;
ssize_t = LongInt;
{$PACKRECORDS C}
atomic_t = record
counter:LongInt;
end;
files_struct = record
{read mostly part}
count:atomic_t;
//struct fdtable *fdt;
//struct fdtable fdtab;
{written part on a separate cache line in SMP}
//spinlock_t file_lock ____cacheline_aligned_in_smp;
//int next_fd;
//struct embedded_fd_set close_on_exec_init;
//struct embedded_fd_set open_fds_init;
//struct file * fd_array[NR_OPEN_DEFAULT];
end;
filldir_t = function(arg1:Pointer; arg2:PChar; arg3:LongInt; arg4:loff_t; arg5:u64; arg6:DWord):LongInt; cdecl;
read_actor_t = function(arg1:Pointer{read_descriptor_t *}; arg2:Pointer{struct page *}; arg3:DWord; arg4:DWord):LongInt; cdecl;
Pfile_operations = ^file_operations;
file_operations = record
owner:Pointer;{struct module *}
llseek:function(filp:Pointer{struct file *}; offset:loff_t; whence:LongInt):loff_t; cdecl;
read:function(filp:Pointer{struct file *}; buf:PChar; count:size_t; offset:Ploff_t):ssize_t; cdecl;
write:function(filp:Pointer{struct file *}; buf:PChar; count:size_t; offset:Ploff_t):ssize_t; cdecl;
aio_read:function(kiocb:Pointer{struct kiocb *}; iovec:Pointer{const struct iovec *}; count:DWord; offset:loff_t):ssize_t; cdecl;
aio_write:function(kiocb:Pointer{struct kiocb *}; iovec:Pointer{const struct iovec *}; count:DWord; offset:loff_t):ssize_t; cdecl;
readdir:function(filp:Pointer{struct file *}; buf:Pointer; filldir:filldir_t):LongInt; cdecl;
poll:function(filp:Pointer{struct file *}; poll_table:Pointer{struct poll_table_struct *}):DWord; cdecl;
ioctl:function(inode:Pointer{struct inode *}; filp:Pointer{struct file *}; arg1:DWord; arg2:DWord):LongInt; cdecl;
unlocked_ioctl:function(filp:Pointer{struct file *}; arg1:DWord; arg2:DWord):LongInt; cdecl;
compat_ioctl:function(filp:Pointer{struct file *}; arg1:DWord; arg2:DWord):LongInt; cdecl;
mmap:function(filp:Pointer{struct file *}; vm_area_struct:Pointer{ struct vm_area_struct *}):LongInt; cdecl;
open:function(inode:Pointer{struct inode *}; filp:Pointer{struct file *}):LongInt; cdecl;
flush:function(filp:Pointer{struct file *}; id:fl_owner_t):LongInt; cdecl;
release:function(inode:Pointer{struct inode *}; filp:Pointer{struct file *}):LongInt; cdecl;
fsync:function(filp:Pointer{struct file *}; arg1:Pointer{ struct dentry *}; datasync:LongInt):LongInt; cdecl;
aio_fsync:function(kiocb:Pointer{struct kiocb *}; datasync:LongInt):LongInt; cdecl;
fasync:function(arg1:LongInt; filp:Pointer{struct file *}; arg2:LongInt):LongInt; cdecl;
lock:function(filp:Pointer{struct file *}; arg1:LongInt; arg2:Pointer{ struct file_lock *}):LongInt; cdecl;
sendfile:function(filp:Pointer{struct file *}; arg1:Pointer{ loff_t *}; arg2:size_t; arg3:read_actor_t; arg4:Pointer{ void *}):ssize_t; cdecl;
sendpage:function(filp:Pointer{struct file *}; arg1:Pointer{ struct page *}; arg2:LongInt; arg3:size_t; arg4:Pointer{ loff_t *}; arg5:LongInt):ssize_t; cdecl;
get_unmapped_area:function(filp:Pointer{struct file *}; arg1:DWord; arg2:DWord; arg3:DWord; arg4:DWord):DWord; cdecl;
check_flags:function(falgs:LongInt):LongInt; cdecl;
dir_notify:function(filp:Pointer{struct file *}; arg:DWord):LongInt; cdecl;
flock:function(filp:Pointer{struct file *}; arg1:LongInt; arg2:Pointer{ struct file_lock *}):LongInt; cdecl;
splice_write:function(arg1:Pointer{struct pipe_inode_info *}; filp:Pointer{struct file *}; arg2:Pointer{ loff_t *}; arg3:size_t; arg4:DWord):ssize_t; cdecl;
splice_read:function(filp:Pointer{struct file *}; arg1:Pointer{ loff_t *}; arg2:Pointer{ struct pipe_inode_info *}; arg3:size_t; arg4:DWord):ssize_t; cdecl;
end;
Pproc_dir_entry = ^proc_dir_entry;
proc_dir_entry = record
low_ino:Cardinal;
namelen:Integer;
name:PChar;
mode:mode_t;
nlink:nlink_t;
uid:uid_t;
gid:gid_t;
size:loff_t;
proc_iops:Pointer;{const struct inode_operations *}
proc_fops:Pfile_operations;{const struct file_operations *}
get_info:Pointer;{get_info_t *}
owner:Pointer;{struct module *}
next, parent, subdir:Pproc_dir_entry;
data:Pointer;
read_proc:Pointer;{read_proc_t *}
write_proc:Pointer;{write_proc_t *}
count:atomic_t;{use count}
deleted:LongInt;{delete flag}
_set:Pointer;
end;
var
proc_root:proc_dir_entry; cvar; external;
//function copy_from_user(_to:Pointer; _from:Pointer; count:Cardinal):Cardinal; cdecl;
function create_proc_entry(name:PChar; mode:mode_t; parent:Pproc_dir_entry):Pproc_dir_entry; cdecl;
procedure remove_proc_entry(name:PChar; parent:Pproc_dir_entry); cdecl;
procedure printk(fmt:PChar); cdecl;
procedure printk(fmt:PChar; param:LongInt); cdecl;
procedure printk(fmt:PChar; param1:PChar); cdecl;
procedure printk(fmt:PChar; param1:PChar; param2:LongInt); cdecl;
function register_chrdev(devMajor:LongInt; devName:PChar; fops:Pfile_operations):LongInt; cdecl;
function unregister_chrdev(devMajor:LongInt; devName:PChar):LongInt; cdecl;
implementation
{$LINK kernel_module_info}
//function copy_from_user(_to:Pointer; _from:Pointer; count:Cardinal):Cardinal; cdecl; external;
function create_proc_entry(name:PChar; mode:mode_t; parent:Pproc_dir_entry):Pproc_dir_entry; cdecl; external;
procedure remove_proc_entry(name:PChar; parent:Pproc_dir_entry); cdecl; external;
procedure printk(fmt:PChar); cdecl; external;
procedure printk(fmt:PChar; param:LongInt); cdecl; external;
procedure printk(fmt:PChar; param1:PChar); cdecl; external;
procedure printk(fmt:PChar; param1:PChar; param2:LongInt); cdecl; external;
function register_chrdev(devMajor:LongInt; devName:PChar; fops:Pfile_operations):LongInt; cdecl; external;
function unregister_chrdev(devMajor:LongInt; devName:PChar):LongInt; cdecl; external;
end.
unit kernel_module;
{$TYPEINFO OFF}
interface
implementation
const
PROCFS_MAX_SIZE = 1024;
PROCFS_NAME = 'kpmod';
BUF_LEN = 256;
var
{Major number assigned to our device driver}
devMajor:LongInt;
fops:file_operations;
i, j, k:LongInt;
{Is device open? Used to prevent multiple access to device}
devIsOpen:Boolean = False;
{pattern:Array[0..1, 0..7, 0..7]of LongInt = (
(($A,$9,$5,$6,$A,$9,$5,$6), ($A,$8,$9,$1,$5,$4,$6,$2)),
(($6,$5,$9,$A,$6,$5,$9,$A), ($2,$6,$4,$5,$1,$9,$8,$A)));}
{This structure hold information about the /proc file}
Our_Proc_File:Pproc_dir_entry;
{The buffer used to store character for this module}
//procfs_buffer:Array[0..PROCFS_MAX_SIZE - 1]of Char;
{The size of the buffer}
procfs_buffer_size:Cardinal = 0;
{The msg the device will give when asked}
msg:Array[0..BUF_LEN-1]of Char;
msg_Ptr:PChar;
{* This function is called then the /proc file is read
*
* Arguments
* =========
* 1. The buffer where the data is to be inserted, if
* you decide to use it.
* 2. A pointer to a pointer to characters. This is
* useful if you don't want to use the buffer
* allocated by the kernel.
* 3. The current position in the file
* 4. The size of the buffer in the first argument.
* 5. Write a "1" here to indicate EOF.
* 6. A pointer to data (useful in case one common
* read for multiple /proc/... entries)
*
* Usage and Return Value
* ======================
* A return value of zero means you have no further
* information at this time (end of file). A negative
* return value is an error condition.
*
* For More Information
* ====================
* The way I discovered what to do with this function
* wasn't by reading documentation, but by reading the
* code which used it. I just looked to see what uses
* the get_info field of proc_dir_entry struct (I used a
* combination of find and grep, if you're interested),
* and I saw that it is used in <kernel source
* directory>/fs/proc/array.c.
*
* If something is unknown about the kernel, this is
* usually the way to go. In Linux we have the great
* advantage of having the kernel source code for
* free - use it.
*}
function procfile_read(buffer:PChar; buffer_location:PPChar; offset:off_t; buffer_length:LongInt; eof:PLongInt; data:Pointer):LongInt; cdecl;
begin
printk(KERN_INFO + 'procfile_read (/proc/%s) called'#10, PROCFS_NAME);
{*
* We give all of our information in one go, so if the
* user asks us if we have more information the
* answer should always be no.
*
* This is important because the standard read
* function from the library would continue to issue
* the read system call until the kernel replies
* that it has no more information, or until its
* buffer is filled.
*}
if offset > 0 then begin
{we have finished to read, return 0}
procfile_read := 0;
end else begin
{fill the buffer, return the buffer size}
//move('HelloWorld!'#10#0, buffer, 13);
//memcpy(buffer, procfs_buffer, procfs_buffer_size);
buffer[0] := 'H';
buffer[1] := 'e';
buffer[2] := 'l';
buffer[3] := 'l';
buffer[4] := 'o';
buffer[5] := #10;
buffer[6] := #0;
procfile_read := 6;
end;
end;
{This function is called with the /proc file is written}
function procfile_write(_file:Pointer{struct file *}; buffer:PChar; count:Cardinal; data:Pointer):LongInt; cdecl;
begin
printk(KERN_INFO + 'procfile_write (/proc/%s) called'#10, PROCFS_NAME);
{get buffer size}
procfs_buffer_size := count;
if procfs_buffer_size > PROCFS_MAX_SIZE then
procfs_buffer_size := PROCFS_MAX_SIZE;
{write data to the buffer}
//if copy_from_user(@procfs_buffer, buffer, procfs_buffer_size) <> 0 then
// procfile_write := -EFAULT
//else
procfile_write := procfs_buffer_size;
end;
{Called when a process tries to open the device file, like "cat /dev/kpmod"}
function device_open(inode:Pointer{struct inode *}; filp:Pointer{struct file *}):LongInt; cdecl;
begin
//static int counter = 0;
if devIsOpen then
Exit(-EBUSY);
printk('Ouverture en mode Lecture/Ecriture ...'#10);
//sprintf(msg, "I already told you %d times Hello world!'#10, counter += 1);
//msg_Ptr = msg;
//try_module_get(THIS_MODULE);
devIsOpen := True;
device_open := 0;
end;
{Called when a process, which already opened the dev file, attempts to read from it.}
function device_read(filp:Pointer{struct file *}; buffer:PChar; len:size_t; offset:Ploff_t):ssize_t; cdecl;
var
bytes_read:LongInt;
begin
{Number of bytes actually written to the buffer}
bytes_read := 0;
printk('Writing %d data into buffer from offset %%lld'#10, len{, offset^});
{Actually put the data into the buffer}
//while(len > 0) and (msg_Ptr^ <> #0)do begin
{The buffer is in the user data segment, not the kernel
segment so "*" assignment won't work. We have to use
put_user which copies data from the kernel data segment to
the user data segment.}
//put_user(msg_Ptr^, buffer += 1);
// msg_Ptr += 1;
// len -= 1;
// bytes_read += 1;
//end;
{If we're at the end of the message, return 0 signifying end of file}
{Most read functions return the number of bytes put into the buffer}
device_read := bytes_read;
end;
{Called when a process writes to dev file: "echo 'hi' > /dev/hello"}
function device_write(filp:Pointer{struct file *}; buff:Pchar; len:size_t; offset:Ploff_t):ssize_t; cdecl;
function step:LongInt;
begin
if k < 8 then begin
//printk('%d'#10,pattern[i][j][k]);
//outb(pattern[i][j][k],LPT_BASE);
k += 1;
end else begin
k := 0;
//outb(pattern[i][j][k],LPT_BASE);
//printk('%d'#10,pattern[i][j][k]);
k += 1;
end;
step := 0;
end;
var
data:Char;
cmd:Char;
begin
printk(KERN_ALERT + 'Sorry, write operation on "%s" isnt supported yet.'#10, '/dev/kpmod');
device_write := -EINVAL;
{
//get_user(data,buffer);
cmd := data;
case cmd of
'H':begin
printk('Reportez-vous au fichier README'#10);
end;
'h':begin
printk('Initialisation en mode demi pas'#10);
j=0;
end;
'f':begin
printk('Initialisation en mode pas entier'#10);
j=1;
end;
'F':begin
i=0;
step();
end;
'R':begin
i=1;
step();
end;
else
printk('Reportez-vous au fichier README, les commandes sont H, h, F, f, et R'#10);
end;
device_write := 1;}
end;
{Called when a process closes the device file.}
function device_release(inode:Pointer{struct inode *}; filp:Pointer{struct file *}):LongInt; cdecl;
begin
printk('Fermeture ...'#10);
{Decrement the usage count, or else once you opened the file, you'll never get get rid of the module.}
//module_put(THIS_MODULE);
{We're now ready for our next caller}
devIsOpen := False;
device_release := 0;
end;
{This function is called when the module is loaded}
function init_module: Integer; cdecl; export;
begin
with fops do begin
open := @device_open;
read := @device_read;
write := @device_write;
release := @device_release;
end;
devMajor := register_chrdev(0, DEVICE_NAME, @fops);
if devMajor < 0 then begin
printk(KERN_ALERT + 'Registering char device failed with %d'#10, devMajor);
Exit(devMajor);
end;
printk(KERN_INFO + 'I was assigned major number %d. To talk to'#10, devMajor);
printk(KERN_INFO + 'the driver, create a dev file with'#10);
printk(KERN_INFO + '"mknod /dev/%s c %d 0".'#10, DEVICE_NAME, devMajor);
printk(KERN_INFO + 'Try various minor numbers. Try to cat and echo to'#10);
printk(KERN_INFO + 'the device file.'#10);
printk(KERN_INFO + 'Remove the device file and module when done.'#10);
printk(KERN_INFO + '@fops = $%X'#10, LongInt(@fops));
{create the /proc file}
Our_Proc_File := create_proc_entry(PROCFS_NAME, 0644, Nil);
if Our_Proc_File = Nil then begin
remove_proc_entry(PROCFS_NAME, @proc_root);
printk(KERN_ALERT + 'Could not initialize /proc/%s'#10, PROCFS_NAME);
Exit(-ENOMEM);
end;
printk(KERN_INFO + 'Our_Proc_File = 0x%X'#10, PtrUInt(Our_Proc_File));
with Our_Proc_File^ do begin
read_proc := @procfile_read;
write_proc := @procfile_write;
//owner := THIS_MODULE;
//mode := S_IFREG OR S_IRUGO;
uid := 0;
gid := 0;
size := 37;
end;
printk(KERN_INFO + '/proc/%s created'#10, PROCFS_NAME);
{everything is ok}
init_module := 0;
end;
{This function is called when the module is unloaded}
procedure cleanup_module; cdecl; export;
var
ret:LongInt;
begin
remove_proc_entry(PROCFS_NAME, @proc_root);
printk(KERN_INFO + '/proc/%s removed'#10, PROCFS_NAME);
ret := unregister_chrdev(devMajor, DEVICE_NAME);
if ret < 0 then
printk(KERN_ALERT + 'Error in unregister_chrdev: %d'#10, ret);
end;
end.
obj-m := kernel_pmodule.o kernel_pmodule-objs := kernel_module_info.o kernel_module.o system.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default:kernel_module.o $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules kernel_module.o: kernel_module.pas system.pas fpc kernel_module clean: ${RM} *.ko *.mod.c *.o *.ppu Module.symvers .*.ko.cmd .*.o.cmd ${RM} -r .tmp_versions