Reputation: 308
Concise version at the end.
Intro:
I am making a char driver (type of scull. emulates the driver by allocating a memory area in the kernel and treating it as a device). From : https://www.youtube.com/playlist?list=PL16941B715F5507C5 -videos no. 7 and 8 from this.
The code for driver till now is working fine.
Current Code:
struct fake_device
{
char data[100];
struct semaphore sem;
} virtual_device;
As I said, above code creates a fake char device named virtual_device.
ssize_t device_write(struct file* filp, const char* bufSourceData,size_t bufCount, loff_t* curOffset)
{
printk(KERN_INFO "soliduscode: writing to device");
printk (KERN_INFO "%s",bufSourceData);
ret = copy_from_user(virtual_device.data, bufSourceData, bufCount);
return ret;
}
I use the above function to write to the fake device
Question:
As you see, device_write() reads the data from the node it's attached to (file: /dev/solidusmodule in my case) and writes exactly the same data to the device.
Now I am supposed to reverse (make palindrome) of this string and this conversion should be performed in the module code (this code) and not the user application.
Solutions I've tried:
The problem is device_write() has syntax
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
It takes a constant character user space pointer. Changing the syntax isn't permitted. I can't change the values pointed by bufSourceData. So I tried
ssize_t device_write(struct file* filp, const char* bufSourceData,size_t bufCount, loff_t* curOffset)
{
char temptry2[100]; //user app has the same size for writing to /dev/solidusdevice
int i, len, ascii_null = 0;
len = strlen(bufSourceData);
for(i=0;i<len;i++)
{
temptry2[i] = bufSourceData[len-i-1];
}
temptry2[i] = ascii_null;
for(i=len + 1; i<100;i++)
{
temptry2[i] = '0';
}
const char* palindrome_pointer = temptry2;
ret = copy_from_user(virtual_device.data, palindrome_pointer, bufCount);
return ret;
}
But this doesn't write the string to virtual_device.data. value of ret isn't 0. copy_from_user() fails.
From Linux Device Drivers by Jonathan Corbett, I found that copy_from_user(void *to, const void __user *mypointer, unsigned long count) only works for user space pointers in the source. So I tried
const char __user *palindrome_pointer = temptry2; //tried to make it a user space pointer so that I can pass it to copy_from_user( ,const void __user *mypointer, )
even tried (don't laugh):
const void __user *palindrome_pointer = temptry2; //changed from char to void
Still doesn't work.
TL;DR (Concise Question): How to I create a constant character type user space pointer (const char __user *my_pointer) to hold a string so that I can pass it as a source to copy_from_user(destination,source,size) ?
Please tell me if you know the solution. Is it even possible to do such operations in the module code or should I keep it in the user application itself?
Thanks
EDIT: If you find any link on this site or other that answers my question partially or fully, please do tell. I am just a beginner in device driver programming.
Upvotes: 1
Views: 722
Reputation: 3183
Don't have experience with Linux kernel module programming at all, so I might be speaking total nonsense. However, linked tutorials and quick googling gives a pretty clear picture of what is happening.
Your device_write()
function is part of kernel module, it's run in kernel mode, so all allocated buffers within it are expected to be in kernel space as well. The second parameter bufSourceData
is passed from user space. The main purpose of copy_from_user()
method is to safely treat external pointers: validate, that memory is accessible and is located in user space as expected and only then copy. So kernel won't panic or corrupt things on incorrect input. Check this answer for more details.
Your current version does this:
copy_from_user()
to copy data from kernel space buffer to kernel space buffer, which it restricts to do.What you should probably do:
copy_from_user()
to fill it with user space data (note, you need to prevent possible buffer overflow)virtual_device.data
buffer as usual.Apparently, clarifications are needed.
But what I wanted to do in clear terms is: take data from user space, manipulate it and pass that on to the kernel space
Looks like you imagine your routine device_write()
to be somehow in between user space and kernel space, but there is no such layer. You are already in kernel space, the very first line of code in this function is executed in kernel mode. You are not passing anything to kernel, you're a part of the kernel (well, term "kernel module" is quite self-descriptive). You need to handle data from user space properly, while working in kernel mode, and then pass it into device.
In step 3, you mentioned states that I should write to 'virtual_device.data' as usual. <...> Now, if I follow what you say (ie. adding a statement 'virtual_device.data = *palindrome'), then this will work only for this program as it a simulation (I am using a fake char device). But for it to work practically, I need to use 'copy_from_user()' as usual with an appropriate user space pointer (containing the palindrome).
Method copy_from_user()
is kind of a tricky and safe variant of memcpy()
, nothing more. It has nothing to do with devices (virtual or not) or anything else. Function is really straight forward: copy from one buffer to another. If you can do this with device, then you can write data with memcpy()
to device or any other standard way of handling buffer data. By the way, your suggestion virtual_device.data = *palindrome
is neither valid C code, nor the good concept of passing data from one array to another. It looks like you are trying to modify pointer instead of copying data between arrays. This can end up in a very bad way.
Ok, so what should your solution look like in pseudo code:
ssize_t device_write(struct file* filp, const char* bufSourceData,size_t bufCount, loff_t* curOffset)
{
//kernel space buffer
char palindrome_data[SOME_SIZE];
ssize_t palindrome_size = min(bufCount, SOME_SIZE);
//fill it with user data via safe function
//instead of acessing user space data directly
copy_from_user(palindrome_data, bufSourceData, palindrome_size));
<check if copy_from_user hasn't failed>
//do whatever you like
make_palindrome(palindrome_data, palindrome_size);
//write to device
memcpy(virtual_device.data, palindrome_data, palindrome_size);
return palindrome_size;
}
Upvotes: 1