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:
- main userspace program will start the kernelspace code (perhaps via
ioctl
) - userspace program blocks and waits for kernelspace code
- kernelspace program asks userspace program for output data, and blocks to wait
- userspace program unblocks, calculates and sends data (
ioctl
?), then blocks again - kernelspace program unblocks and continues
- kernelspace program finishes and notifies userspace
- userspace unblocks and continues to next task
So how do I have the 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.
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 theread()
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.
I would suggest that you move the code that does all the "heavy lifting" to user mode - that is, calculate all the 300 values in one go, and pass those to the kernel.
I'm not even sure you can let an arbitrary piece of code call user-mode from the kernel. I'm sure it's possible to do, because that's what for example "signal" does, but I'm far from convinced you can do it "any way you like" (and almost certainly, there are restrictions regarding, for example, what you can do in that function). It certainly doesn't seem like a great idea, and it would DEFINITELY be quite slow to call back to usermode many times.
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:
- main userspace program will start the kernelspace code (perhaps via
ioctl
) - userspace program blocks and waits for kernelspace code
- kernelspace program asks userspace program for output data, and blocks to wait
- userspace program unblocks, calculates and sends data (
ioctl
?), then blocks again - kernelspace program unblocks and continues
- kernelspace program finishes and notifies userspace
- userspace unblocks and continues to next task
So how do I have the 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.
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 theread()
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.
I would suggest that you move the code that does all the "heavy lifting" to user mode - that is, calculate all the 300 values in one go, and pass those to the kernel.
I'm not even sure you can let an arbitrary piece of code call user-mode from the kernel. I'm sure it's possible to do, because that's what for example "signal" does, but I'm far from convinced you can do it "any way you like" (and almost certainly, there are restrictions regarding, for example, what you can do in that function). It certainly doesn't seem like a great idea, and it would DEFINITELY be quite slow to call back to usermode many times.
0 commentaires:
Enregistrer un commentaire