I'm programming a simple Linux character device driver to output data to a piece of hardware via I/O ports. I have a function which performs floating point operations to calculate the correct output for the hardware; unfortunately this means I need to keep this function in userspace since the Linux kernel doesn't handle floating point operations very nicely.
Here's a pseudo representation of the setup (note that this code doesn't do anything specific, it just shows the relative layout of my code):
Userspace function:
char calculate_output(char x){
double y = 2.5*x;
double z = sqrt(y);
char output = 0xA3;
if(z > 35.67){
output = 0xC0;
}
return output;
}
Kernelspace code:
unsigned i;
for(i = 0; i < 300; i++){
if(inb(INPUT_PORT) & NEED_DATA){
char seed = inb(SEED_PORT);
char output = calculate_output(seed);
outb(output, OUTPUT_PORT);
}
/* do some random stuff here */
}
I thought about using ioctl
to pass in the data from the userspace function, but I'm not sure how to handle the fact that the function call is in a loop and more code executes before the next call to calculate_output
occurs.
The way I envision this working is:
ioctl
)ioctl
?), then blocks againSo how do I have the communication between kernelspace and userspace, and also have blocking so that I don't have the userspace continually polling a device file to see if it needs to send data?
A caveat: while fixed point arithmetic would work quite well in my example code, it is not an option in the real code; I require the large range that floating point provides and -- even if not -- I'm afraid rewriting the code to use fixed point arithmetic would obfuscate the algorithm for future maintainers.
You cannot call a kernel function from user space, you need to go through one of the existing mechanisms. You'll need to write at least some glue code in the kernel, to provide a way to trigger the execution of the kernel code, and pass parameters and results around.
Kernel space is strictly reserved for running a privileged operating system kernel, kernel extensions, and most device drivers. In contrast, user space is the memory area where application software and some drivers execute.
Usermode Helper API is for creating a user mode process from kernel space. Data structure that is used for the API is struct subprocess_info . /linux/include/kmod.
I think the simplest solution would be to create a character device in your kernel driver, with your own file operations for a virtual file. Then userspace can open this device O_RDWR
. You have to implement two main file operations:
read
-- this is how the kernel passes data back up to userspace. This function is run in the context of the userspace thread calling the read()
system call, and in your case it should block until the kernel has another seed value that it needs to know the output for.
write
-- this is how userspace passes data into the kernel. In your case, the kernel would just take the response to the previous read and pass it onto the hardware.
Then you end up with a simple loop in userspace:
while (1) {
read(fd, buf, sizeof buf);
calculate_output(buf, output);
write(fd, output, sizeof output);
}
and no loop at all in the kernel -- everything runs in the context of the userspace process that is driving things, and the kernel driver is just responsible for moving the data to/from the hardware.
Depending on what your "do some random stuff here" on the kernel side is, it might not be possible to do it quite so simply. If you really need the kernel loop, then you need to create a kernel thread to run that loop, and then have some variables along the lines of input_data
, input_ready
, output_data
and output_ready
, along with a couple of waitqueues and whatever locking you need.
When the kernel thread reads data, you put the data in input_ready
and set the input_ready
flag and signal the input waitqueue, and then do wait_event(<output_ready is set>)
. The read
file operation would do a wait_event(<input_ready is set>)
and return the data to userspace when it becomes ready. Similarly the write
file operation would put the data it gets from userspace into output_data
and set output_ready
and signal the output waitqueue.
Another (uglier, less portable) way is to use something like ioperm
, iopl
or /dev/port
to do everything completely in userspace, including the low-level hardware access.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With