Related
I have an assignment, where I have to create a proc_entry which can be written to(by user) and read from(by kernel).
The motive is that the kernel code should be able to read the value in the proc_entry, and use it later as a threshold for number of files opened by a process. If a process has opened more files that this threshold, it will be penalized in the scheduler. As user can also change value inside this proc_entry, thus, kernel will use this threshold dynamically.
Most of the codes I have seen online, tell how to create a module that will create such an entry, and another module that given the path of this entry, will read the string present.
The code for the module to create I/O proc_entry is- (Using the below module, I am able to create a proc_entry "/proc/my_proc_entry_write", to which, I can write using- "echo 200 > /proc/my_proc_entry_write", and read this value via "cat /proc/my_proc_entry_write")-
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <asm/types.h>
#define DATA_SIZE 3000000 // We can keep 1024 bytes of data with us.
#define MY_PROC_ENTRY "my_proc_entry_write"
#define PROC_FULL_PATH "/proc/my_proc_entry_write"
struct proc_dir_entry *proc;
int len;
char *msg = NULL;
/*
* Function to write to the proc. Here we free get the new value from buffer,
* count from the buffer and then overwrite the data in our file.
*
* Note that - you can have any other implementation as well for this, all you have to
* ensure that you comply with the expectations of the write() system calls
* like filling in the buffer, and returning the numbers of character written.
*/
static ssize_t my_proc_write(struct file *filp, const char __user * buffer, size_t count, loff_t *pos) // buffer, of length count, should be copied to kernel
{
int i;
char *data = PDE_DATA(file_inode(filp)); // gives data pointer of file
if (count > DATA_SIZE) {
return -EFAULT;
}
printk(KERN_INFO "Printing the data passed. Count is %lu", (size_t) count);
for (i=0; i < count; i++) {
printk(KERN_INFO "Index: %d . Character: %c Ascii: %d", i, buffer[i], buffer[i]);
}
printk(KERN_INFO "Writing to proc");
if (copy_from_user(data, buffer, count)) {
return -EFAULT;
}
data[count-1] = '\0';
printk(KERN_INFO "msg has been set to %s", msg); // Due to kmalloc, msg points to this. So, when we write, msg is changed.
printk(KERN_INFO "Message is: ");
for (i=0; i < count; i++) {
printk(KERN_INFO "\n Index: %d . Character: %c", i, msg[i]);
}
*pos = (int) count; // length written to be copied to pos at end
len = count-1; // len is length of string. count is len+1, to accomodate the \0.
return count;
}
/*
* Function to read the proc entry, here we copy the data from our proc entry
* to the buffer passed.
*/
ssize_t my_proc_read(struct file *filp,char *buf, size_t count, loff_t *offp ) // copy to buf,which is in userspace
{
char* f_path; int i=0; char f_arr[128]; char* f_path_2;
while(i<128)
{
f_arr[i]=0;
i++;
}
f_path=dentry_path_raw(filp->f_path.dentry,f_arr,128);
printk(KERN_ERR"f_path: %s\n",f_path);
i=0;
while(i<128 && f_arr[i]==0)
{
i++;
}
if(i!=128)
{
f_path_2=&f_arr[i];
printk(KERN_ERR"f_path_2: %s\n",f_path_2);
}
int err;
char *data = PDE_DATA(file_inode(filp));
if ((int) (*offp) > len) {
return 0;
}
printk(KERN_INFO "Reading the proc entry, len of the file is %d", len);
if(!(data)) {
printk(KERN_INFO "NULL DATA");
return 0;
}
if (count == 0) {
printk(KERN_INFO "Read of size zero, doing nothing.");
return count;
} else {
printk(KERN_INFO "Read of size %d", (int) count);
}
count = len + 1; // +1 to read the \0 ; thus we store the previous written length in global variable len
err = copy_to_user(buf, data, count); // +1 for \0
printk(KERN_INFO "Read data : %s", buf);
*offp = count;
if (err) {
printk(KERN_INFO "Error in copying data.");
} else {
printk(KERN_INFO "Successfully copied data.");
}
return count;
}
/*
* The file_operations structure. This is the glue layer which associates the
* proc entry to the read and write operations.
*/
struct file_operations proc_fops = {
.read = my_proc_read,
.write = my_proc_write,
};
/*
* This function will create the proc entry. This function will allocate some
* data where the data will be written incase of a write to the proc entry. The
* same memory will be used to serve the reads. * Initially the function fills
* the data with DATA which has "100".
* The important function to see here is the proc_create_data, this function
* will take the proc entry name and create it with the given permissions
* (0666). We also need to pass the file_operations structure which has the
* function pointers to the functions which needs to be called when read or
* write is called on the file.
The last argument has the pointer to the data
* associated with the file. (So, by "char *data = PDE_DATA(file_inode(filp));", the 'data' is actually 'msg')
*/
int create_new_proc_entry(void) {
int i;
char *DATA = "100";
len = strlen(DATA);
msg = kmalloc((size_t) DATA_SIZE, GFP_KERNEL); // +1 for \0
if (msg != NULL) {
printk(KERN_INFO "Allocated memory for msg");
} else {
return -1;
}
strncpy(msg, DATA, len+1);
for (i=0; i < len +1 ; i++) {
printk(KERN_INFO "%c", msg[i]);
if (msg[i] == '\0') {
printk(KERN_INFO "YES");
}
}
proc = proc_create_data(MY_PROC_ENTRY, 0666, NULL, &proc_fops, msg);
if (proc) {
return 0;
}
return -1;
}
/* The init function of the module. Does nothing other than calling the
* create_new_proc_entry. */
int proc_init (void) {
if (create_new_proc_entry()) {
return -1;
}
return 0;
}
/* Function to remove the proc entry. Call this when the module unloads. */
void proc_cleanup(void) {
remove_proc_entry(MY_PROC_ENTRY, NULL);
}
MODULE_LICENSE("GPL");
module_init(proc_init);
module_exit(proc_cleanup);
Makefile for the above module is(assuming the code of module is written in file proc_write_read.c):-
MYPROC=proc_write_read
obj-m += $(MYPROC).o
export KROOT=/lib/modules/$(shell uname -r)/build
#export KROOT=/lib/modules/$(uname)3.2.0-23-generic/build
allofit: modules
modules: clean
#$(MAKE) -C $(KROOT) M=$(PWD) modules
modules_install:
#$(MAKE) -C $(KROOT) M=$(PWD) modules_install
kernel_clean:
#$(MAKE) -C $(KROOT) M=$(PWD) clean
clean: kernel_clean
rm -rf Module.symvers modules.order
insert: modules
dmesg -c
insmod proc_write_read.ko
remove: clean
rmmod proc_write_read
The module that reads this proc_entry, like a file is-
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <asm/types.h>
int proc_init(void)
{
struct file *f; ssize_t ret = -EBADF;int i;
char* f_path;
i=0;
f=NULL;
char buf[128]; char f_arr[128];
mm_segment_t fs;
i=0;
while(i<128)
{
buf[i] = 0;
f_arr[i]=0;
i++;
}
// To see in /var/log/messages that the module is operating
//printk(KERN_INFO "read_file- my module is loaded\n");
f = filp_open("/proc/my_proc_entry_write", O_RDONLY, 0);
if (IS_ERR(f)) {
printk(KERN_ERR "Error in filp_open: %p ; %d\n", f,PTR_ERR(f));
return 100000;
}
f_path=dentry_path_raw(f->f_path.dentry,f_arr,128);
printk(KERN_ERR"f_path_in_proc_entry_read: %s\n",f_path);
printk(KERN_ERR"kernel Below1!!!\n");
// Get current segment descriptor
fs = get_fs();
printk(KERN_ERR"kernel Below2!!!\n");
// Set segment descriptor associated to kernel space
set_fs(get_ds());
printk(KERN_ERR"kernel Below3!!!\n");
// Read the file
if (f!=NULL) {
if ((f->f_op)!=NULL && (f->f_op->read)!=NULL){
ret=f->f_op->read(f, buf, 128, &f->f_pos);
printk(KERN_ERR"kernel Below4!!!\n");
}
else
return 100000;
}
else
return 100000;
// Restore segment descriptor
set_fs(fs);
// See what we read from file
printk(KERN_ERR "kernel Read my_proc_entry_write buf:%s\n",buf);
int val=0;
sscanf(buf, "%d", &val);
printk(KERN_ERR"kernel val: %d\n",val);
filp_close(f,NULL);
return val;
}
void proc_cleanup(void)
{
printk(KERN_INFO "My module is unloaded\n");
}
module_init(proc_init);
module_exit(proc_cleanup);
Makefile for the above module(name=read_file_in_kernel) is-
obj-m += read_file_in_kernel.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
My question is- Both these modules work fine as userspace modules, via insmod, but if I copy-paste this code to the source code of kernel 4.19.200 (not exact copy-paste, just the main parts as functions), and use the 2nd module,i.e, read_file_in_kernel's code to access the proc value, I am getting an error, i.e, kernel is not able to boot.
On running gdb through the kernel, I found that the code in read_file_in_kernel went through the below code, thrice (i.e, when update_curr function in linux kernel called this function 3 times),i.e, f is being returned as an error every time-
if (IS_ERR(f)) {
printk(KERN_ERR "Error in filp_open: %p ; %d\n", f,PTR_ERR(f));
return 100000;
}
In the 4th call to read_file_in_kernel, the kernel froze on -
f = filp_open("/proc/my_proc_entry_write", O_RDONLY, 0);
Same case with gdb.
I don't know what I am doing wrong here. Is it that /proc/my_proc_entry_write is not being created during bootup when it is read, and so, filp_open is not able to open that proc_entry to be read.
I even tried removing the first module from kernel entirely, running it separately from user-space to create a proc_entry(my_proc_entry_write) beforehand, that will be loaded by default every time the kernel boots up. But still, same error is coming.
What correction should I make to this?
If this is not the way to create a dynamic proc_entry that can be written to by user and ready by kernel, what is?
I'm trying to understand some example code my lecturer gave me.
It is a method of transferring data from user space into kernel space via a /proc file. This is the only code he gave me and I feel like I'm missing the user space program and I don't think he's explained what's going on very well.
He's trying to demonstrate locking files via semaphores, and also transferring the data I believe. The things I'm struggling to understand are:
what is the "reference count" (He mentions it in the comments for procfs_open and procfs_close)
Why would he use a proc file? It appears to do nothing.
What do module_put and try_module_get do? I can't find any remotely good explanations online.
How would I trigger the kernelWrite function from userspace? So that I know how I can actually transfer the data, not just receive it.
Here is the code:
#define BUFFERLENGTH 256
#define INCREASE_COUNTER 'I'
#define SHOW_COUNTER 'S'
#define PROC_ENTRY_FILENAME "kernelWrite"
DECLARE_RWSEM(counter_sem); /* semaphore to protect counter access */
static struct proc_dir_entry *Our_Proc_File;
int counter1 = 0;
int counter2 = 0;
/* displays the kernel table - for simplicity via printk */
void show_table (void) {
int tmp1;
int tmp2;
down_read (&counter_sem); /* lock for reading */
tmp1 = counter1;
tmp2 = counter2;
up_read (&counter_sem); /* unlock reading */
printk (KERN_INFO "kernelWrite:The counters are %d, %d\n", tmp1, tmp2);
}
void increase_counter (void) {
down_write (&counter_sem); /* lock for writing */
counter1++;
counter2++;
up_write (&counter_sem);
}
/* This function reads in data from the user into the kernel */
ssize_t kernelWrite (struct file *file, const char __user *buffer, size_t count, loff_t *offset) {
char command;
printk (KERN_INFO "kernelWrite entered\n");
if (get_user (command, buffer)) {
return -EFAULT;
}
switch (command) {
case INCREASE_COUNTER:
increase_counter ();
break;
case SHOW_COUNTER:
show_table ();
break;
default:
printk (KERN_INFO "kernelWrite: Illegal command \n");
}
return count;
}
/*
* The file is opened - we don't really care about
* that, but it does mean we need to increment the
* module's reference count.
*/
int procfs_open(struct inode *inode, struct file *file)
{
printk (KERN_INFO "kernelWrite opened\n");
try_module_get(THIS_MODULE);
return 0;
}
/*
* The file is closed - again, interesting only because
* of the reference count.
*/
int procfs_close(struct inode *inode, struct file *file)
{
printk (KERN_INFO "kernelWrite closed\n");
module_put(THIS_MODULE);
return 0; /* success */
}
const struct file_operations File_Ops_4_Our_Proc_File = {
.owner = THIS_MODULE,
.write = kernelWrite,
.open = procfs_open,
.release = procfs_close,
};
int init_module(void)
{
/* create the /proc file */
Our_Proc_File = proc_create_data (PROC_ENTRY_FILENAME, 0644, NULL, &File_Ops_4_Our_Proc_File, NULL);
/* check if the /proc file was created successfuly */
if (Our_Proc_File == NULL){
printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
PROC_ENTRY_FILENAME);
return -ENOMEM;
}
printk(KERN_INFO "/proc/%s created\n", PROC_ENTRY_FILENAME);
return 0; /* success */
}
void cleanup_module(void)
{
remove_proc_entry(PROC_ENTRY_FILENAME, NULL);
printk(KERN_INFO "/proc/%s removed\n", PROC_ENTRY_FILENAME);
printk(KERN_INFO "kernelWrite:Proc module unloaded.\n");
}
Several questions, several answers:
1 Module reference count and try_module_get and module_put:
Each kernel module has usage count, in particular if it is referenced by any other module or used any other way. In this case, when doing opening file it will prevent module from being removed, and after closing the file, it will remove reference. Why it should not be used is explained here
2 Proc file:
Uses File_Ops_4_Our_Proc_File structure. You need to perform action in user space to trigger appropriate action on proc file (namely open, close and write).
3 Triggering actions.
For example (from bash):
echo 'I' > /proc/kernelWrite
Which writes character 'I' to proc file, triggering File_Ops_4_Our_Proc_File.write, effectively calling kernelWrite.
I'm writing a simple character device driver in Linux. But I've encountered some issues. I've added timer but it's incorrect. I need to 1) execute timer one; 2) execute timer n times each 30 seconds apart and stop; 3) execute timer continuously until user sends a STOP command to the driver. And also the user process have to get back the data (snap shot) from device through command IO interface at the end of every timer interrupt completion. I can use jiffies, tasklets, interrupts but I have no idea how to implement them in this driver.
Then about the sysinfo(). I've added the code of sysinfo() to the user application but it has to be in the kernel application code and then send back the sysinfo() to the user application process and show in the terminal.
Here are my kernel and user application codes respectively.
Kernel:
/**
* #file diasdd.c
* #date 16 May 2017
* #version 0.1
* #brief This module maps to /dev/diasdd and
* comes with a helper C program that can be run in Linux user space to communicate with
* this the LKM.
*/
#include <linux/init.h> // Macros used to mark up functions e.g. __init __exit
#include <linux/module.h> // Core header for loading LKMs into the kernel
#include <linux/device.h> // Header to support the kernel Driver Model
#include <linux/kernel.h> // Contains types, macros, functions for the kernel
#include <linux/fs.h> // Header for the Linux file system support
#include <asm/uaccess.h> // Required for the copy to user function
#include <linux/timer.h>
#define DEVICE_NAME "diasdd" ///< The device will appear at /dev/diasdd using this value
#define CLASS_NAME "dd" ///< The device class -- this is a character device driver
MODULE_LICENSE("GPL"); ///< The license type -- this affects available functionality
MODULE_DESCRIPTION("A simple Linux char driver"); ///< The description -- see modinfo
MODULE_VERSION("0.1"); ///< A version number to inform users
static int majorNumber; ///< Stores the device number -- determined automatically
static char message[256] = {0}; ///< Memory for the string that is passed from user space
static short size_of_message; ///< Used to remember the size of the string stored
static int numberOpens = 0; ///< Counts the number of times the device is opened
static struct class* diasddClass = NULL; ///< The device-driver class struct pointer
static struct device* diasddDevice = NULL; ///< The device-driver device struct pointer
// The prototype functions for the character driver -- must come before the struct definition
static int dev_open(struct inode *, struct file *);
static int dev_release(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
static ssize_t dev_write(struct file *, const char *, size_t, loff_t *);
static struct timer_list my_timer;
void my_timer_callback( unsigned long data )
{
printk( "DiasDD: my_timer_callback called (%ld).\n", jiffies );
}
/** #brief Devices are represented as file structure in the kernel. The file_operations structure from
* /linux/fs.h lists the callback functions that we wish to associated with our file operations
* using a C99 syntax structure. char devices usually implement open, read, write and release calls
*/
static struct file_operations fops =
{
.open = dev_open,
.read = dev_read,
.write = dev_write,
.release = dev_release,
};
/** #brief The LKM initialization function
* The static keyword restricts the visibility of the function to within this C file. The __init
* macro means that for a built-in driver (not a LKM) the function is only used at initialization
* time and that it can be discarded and its memory freed up after that point.
* #return returns 0 if successful
*/
static int __init diasdd_init(void){
// Timer starts here
int ret;
printk("DiasDD: Timer module installing\n");
// my_timer.function, my_timer.data
setup_timer( &my_timer, my_timer_callback, 0 );
printk( "DiasDD: Starting timer to fire in 300ms (%ld)\n", jiffies );
ret = mod_timer( &my_timer, jiffies + msecs_to_jiffies(300) );
if (ret) printk("Error in mod_timer\n");
printk(KERN_INFO "DiasDD: Initializing the DiasDD LKM\n");
// Try to dynamically allocate a major number for the device -- more difficult but worth it
majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
if (majorNumber<0){
printk(KERN_ALERT "DiasDD failed to register a major number\n");
return majorNumber;
printk(KERN_INFO "DiasDD: registered correctly with major number %d\n", majorNumber);
}
// Register the device class
diasddClass = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(diasddClass)){ // Check for error and clean up if there is
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to register device class\n");
return PTR_ERR(diasddClass); // Correct way to return an error on a pointer
}
printk(KERN_INFO "DiasDD: device class registered correctly\n");
// Register the device driver
diasddDevice = device_create(diasddClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(diasddDevice)){ // Clean up if there is an error
class_destroy(diasddClass); // Repeated code but the alternative is goto statements
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to create the device\n");
return PTR_ERR(diasddDevice);
}
printk(KERN_INFO "DiasDD: device class created correctly\n"); // Made it! device was initialized
return 0;
}
/** #brief The LKM cleanup function
* Similar to the initialization function, it is static. The __exit macro notifies that if this
* code is used for a built-in driver (not a LKM) that this function is not required.
*/
static void __exit diasdd_exit(void){
// Timer ends here
int ret;
ret = del_timer( &my_timer );
if (ret) printk("DiasDD: The timer is still in use...\n");
printk("DiasDD: Timer module uninstalling\n");
device_destroy(diasddClass, MKDEV(majorNumber, 0)); // remove the device
class_unregister(diasddClass); // unregister the device class
class_destroy(diasddClass); // remove the device class
unregister_chrdev(majorNumber, DEVICE_NAME); // unregister the major number
printk(KERN_INFO "DiasDD: Goodbye from the LKM!\n");
}
/** #brief The device open function that is called each time the device is opened
* This will only increment the numberOpens counter in this case.
* #param inodep A pointer to an inode object (defined in linux/fs.h)
* #param filep A pointer to a file object (defined in linux/fs.h)
*/
static int dev_open(struct inode *inodep, struct file *filep){
numberOpens++;
printk(KERN_INFO "DiasDD: Device has been opened %d time(s)\n", numberOpens);
return 0;
}
/** #brief This function is called whenever device is being read from user space i.e. data is
* being sent from the device to the user. In this case it uses the copy_to_user() function to
* send the buffer string to the user and captures any errors.
* #param filep A pointer to a file object (defined in linux/fs.h)
* #param buffer The pointer to the buffer to which this function writes the data
* #param len The length of the b
* #param offset The offset if required
*/
static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset){
int error_count = 0;
// copy_to_user has the format ( * to, *from, size) and returns 0 on success
error_count = copy_to_user(buffer, message, size_of_message);
if (error_count==0){ // if true then have success
printk(KERN_INFO "DiasDD: Sent %d characters to the user\n", size_of_message);
return (size_of_message=0); // clear the position to the start and return 0
}
else {
printk(KERN_INFO "DiasDD: Failed to send %d characters to the user\n", error_count);
return -EFAULT; // Failed -- return a bad address message (i.e. -14)
}
}
/** #brief This function is called whenever the device is being written to from user space i.e.
* data is sent to the device from the user. The data is copied to the message[] array in this
* LKM using the sprintf() function along with the length of the string.
* #param filep A pointer to a file object
* #param buffer The buffer to that contains the string to write to the device
* #param len The length of the array of data that is being passed in the const char buffer
* #param offset The offset if required
*/
static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset){
sprintf(message, "%s(%zu letters)", buffer, len); // appending received string with its length
size_of_message = strlen(message); // store the length of the stored message
printk(KERN_INFO "DiasDD: Received %zu characters from the user\n", len);
printk(KERN_INFO "DiasDD: Received message: %s\n", buffer);
return len;
}
/** #brief The device release function that is called whenever the device is closed/released by
* the user space program
* #param inodep A pointer to an inode object (defined in linux/fs.h)
* #param filep A pointer to a file object (defined in linux/fs.h)
*/
static int dev_release(struct inode *inodep, struct file *filep){
printk(KERN_INFO "DiasDD: Device successfully closed\n");
return 0;
}
/** #brief A module must use the module_init() module_exit() macros from linux/init.h, which
* identify the initialization function at insertion time and the cleanup function (as
* listed above)
*/
module_init(diasdd_init);
module_exit(diasdd_exit);
User Process:
/**
* #file testdiasdd.c
* #date 16 May 2017
* #version 0.1
* #brief A Linux user space program that communicates with the diasdd.c LKM. It passes a
* string to the LKM and reads the response from the LKM. For this example to work the device
* must be called /dev/diasdd.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/sysinfo.h> // sysinfo
#include <unistd.h> // sysconf
#define BUFFER_LENGTH 256 ///< The buffer length (crude but fine)
static char receive[BUFFER_LENGTH]; ///< The receive buffer from the LKM
int main(){
int ret, fd;
char stringToSend[BUFFER_LENGTH];
printf("Starting device test code example...\n");
fd = open("/dev/diasdd", O_RDWR); // Open the device with read/write access
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("%[^\n]%*c", stringToSend); // Read in a string (with spaces)
printf("Writing message to the device [%s].\n", stringToSend);
ret = write(fd, stringToSend, strlen(stringToSend)); // Send the string to the LKM
if (ret < 0){
perror("Failed to write the message to the device.");
return errno;
}
printf("Press ENTER to read back from the device...\n");
getchar();
printf("Reading from the device...\n");
ret = read(fd, receive, BUFFER_LENGTH); // Read the response from the LKM
if (ret < 0){
perror("Failed to read the message from the device.");
return errno;
}
printf("The received message is: [%s]\n", receive);
printf("End of the program\n");
struct sysinfo info;
if (sysinfo(&info) != 0)
printf("\n");
printf("Current system info:\n");
printf("Uptime: %ld:%ld:%ld\n", info.uptime/3600, info.uptime%3600/60, info.uptime%60);
printf("Total RAM: %ld MB\n", info.totalram/1024/1024);
printf("Free RAM: %ld MB\n", (info.totalram-info.freeram)/1024/1024);
printf("Shared RAM: %ld MB\n", info.sharedram/1024/1024);
printf("Memory used by buffers: %ld MB\n", info.bufferram/1024/1024);
printf("Free swap space size: %ld bytes\n", info.freeswap);
printf("Process count: %d\n", info.procs);
return 0;
}
This is my Makefile:
obj-m := diasdd.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
$(CC) testdiasdd.c -o test
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm test
I developed a kernel module which allocates some kernel memory and remaps it to physical registers of an FPGA when user software opens the device, it also writes in a hardware register which triggers an interruption which is finally used by the probing functions of the kernel to detect the IRQ number at module init, which is 61 in my case. (I got to this point reading the excellent LDD3 book from O'Reilly, but since I'm a newbie in the kernel world I have some trouble getting my driver working well)
Thereby, I am accessing hardware registers from the kernel itself and from the user space using a small soft that I named "regedit". To access the registers from the kernel I used ioremap and I wrote the mmap function to allow regedit to access the registers from the user space using remap_pfn_range.
My first problem is that I suppose there is a better way than separately calling ioremap and remap_pfn_range to do the same thing, but I don't know how to allocate memory, remap it, access it from the kernel and by the same time provide it to the user space.
My second problem is that when I install the module, I see that my driver is able to read and write the registers using ioremap because I successfully detect the IRQ number by triggering an interrupt (by writing the register at offset 0), and when opening the device, my irq handler routine is called and successfully acknowledges the interrupt by writing 0 in the register. But, because there is a but, when I try to read the same register using my soft regedit, I get a bus error.
My guess is that only three registers are physically wired (offsets 0, 4 and 8) and maybe when I think I'm reading a single 32 bits register, the kernel is in fact reading a larger buffer (PAGE_SIZE aligned I presume) and accesses a forbidden area. (To prove that the problem is from my driver I used /dev/mem in my regedit soft, and it's working fine)
I am using a linux kernel 3.12 on a Xilinx Zynq ZC702 board using a processor ARM Cortex A9.
Here is the code of my driver :
Driver Header File
#ifndef DRIVER_H_
#define DRIVER_H_
/* --------------------------------------------------------------
* External References
* ------------------------------------------------------------*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/moduleparam.h>
/* --------------------------------------------------------------
* Application Includes
* ------------------------------------------------------------*/
/* --------------------------------------------------------------
* Constants Definition
* ------------------------------------------------------------*/
#define MODULE_NAME "mydriver"
#define DEFAULT_MAJOR_NUMBER 0 // If zero, major number will be automatically allocated
#define DEFAULT_MINOR_NUMBER 0
#define NB_DEVICES 1 // Number of devices to register
/*
* Hardware defines
*/
#define NB_PAGES 256 // Number of pages of the memory mapping
#define REG_IRQ 0x43C00000 // IRQ register address
/*
* Modules params
*/
static unsigned int irq_param = 0;
/*
* Kernel module information
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AwaX");
MODULE_VERSION("0.1");
MODULE_ALIAS(MODULE_NAME);
MODULE_DESCRIPTION("Kernel module which handles the hardware interrupts and process them");
module_param(irq_param, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(irq_param, "The IRQ line number to be used");
/* --------------------------------------------------------------
* Macros Definition
* ------------------------------------------------------------*/
#define LOG(kernLvl, str, tag) printk(kernLvl "%-6.6s %s() : "str"\n", tag, (char*) __func__);
#define LOGA(kernLvl, str, tag,...) printk(kernLvl "%-6.6s %s() : "str"\n", tag, (char*) __func__, __VA_ARGS__);
#define LOG_TRACE(str) LOG(KERN_DEBUG, str, "KTRACE")
#define LOG_DEBUG(str) LOG(KERN_INFO, str, "KDEBUG")
#define LOG_INFO(str) LOG(KERN_NOTICE, str, "KINFO")
#define LOG_IT(str) LOG(KERN_NOTICE, str, "IT")
#define LOG_WARN(str) LOG(KERN_WARNING, str, "KWARN")
#define LOG_ERROR(str) LOG(KERN_ERR, str, "KERROR")
#define LOG_FATAL(str) LOG(KERN_ALERT, str, "KFATAL")
#define LOG_TRACE_(str,...) LOGA(KERN_DEBUG, str, "KTRACE", __VA_ARGS__)
#define LOG_DEBUG_(str,...) LOGA(KERN_INFO, str, "KDEBUG", __VA_ARGS__)
#define LOG_INFO_(str,...) LOGA(KERN_NOTICE, str, "KINFO", __VA_ARGS__)
#define LOG_IT_(str,...) LOGA(KERN_NOTICE, str, "IT", __VA_ARGS__)
#define LOG_WARN_(str,...) LOGA(KERN_WARNING, str, "KWARN", __VA_ARGS__)
#define LOG_ERROR_(str,...) LOGA(KERN_ERR, str, "KERROR", __VA_ARGS__)
#define LOG_FATAL_(str,...) LOGA(KERN_ALERT, str, "KFATAL", __VA_ARGS__)
/* --------------------------------------------------------------
* Types Definition
* ------------------------------------------------------------*/
/*
* Internal data structure
*/
typedef struct _module_data {
int major; // Major device number
int minor; // Minor device number
dev_t mmap_dev; // Holds device numbers (major and minor)
struct cdev mmap_cdev; // Kernel internal struct representing the device
int *vmalloc_area; // Pointer to the vmalloc'd area - always page aligned
int *kmalloc_area; // Pointer to the kmalloc'd area, rounded up to a page boundary
void *kmalloc_ptr; // Original pointer for kmalloc'd area as returned by kmalloc
// Mapping
volatile int *map_area; // Base address of the registers kernel memory
volatile void *io_area; // Base address of the registers i/o physical memory
// Interrupts
unsigned int irq; // Interrupt number
} module_data;
/* --------------------------------------------------------------
* Functions Definition
* ------------------------------------------------------------*/
#endif /* DRIVER_H_ */
Driver Source File
/* --------------------------------------------------------------
* External References
* ------------------------------------------------------------*/
#include "driver.h"
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/memory.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/types.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <asm-generic/errno-base.h>
/* --------------------------------------------------------------
* Application Includes
* ------------------------------------------------------------*/
/* --------------------------------------------------------------
* Static data
* ------------------------------------------------------------*/
// Internal data
static module_data *_module;
/* --------------------------------------------------------------
* Local Functions Definition
* ------------------------------------------------------------*/
/*
* Module functions
*/
static int __init my_module_init (void);
static void __exit my_module_cleanup (void);
static int my_module_open (struct inode *inode, struct file *filp);
static int my_module_release (struct inode *inode, struct file *filp);
static int my_module_mmap (struct file *filp, struct vm_area_struct *vma);
/*
* Static functions
*/
static void my_vma_open (struct vm_area_struct *vma);
static void my_vma_close (struct vm_area_struct *vma);
static irqreturn_t my_irq_handler (int irq, void *dev_id, struct pt_regs *regs);
static int my_allocate_device (void);
static int my_register_device (void);
static unsigned int my_probe_irq (void);
static int my_mmap_kmem (struct file *filp, struct vm_area_struct *vma);
static int my_mmap_vmem (struct file *filp, struct vm_area_struct *vma);
/*
* Specifies the functions associated with the device operations.
*/
static struct file_operations _module_fops = {
.owner = THIS_MODULE,
.open = my_module_open,
.release = my_module_release,
.mmap = my_module_mmap,
};
/*
* Specifies the functions associated with the remap operations.
*/
static struct vm_operations_struct _module_vmops = {
.open = my_vma_open,
.close = my_vma_close,
};
/* --------------------------------------------------------------
* Functions Implementation
* ------------------------------------------------------------*/
/*****************************************************************************
* Initialization function of the module which allocates the major and minor
* numbers and registers the device to /proc/devices. The creation of the
* device in /dev must be done by an external script.
*
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static int __init my_module_init (void) {
unsigned int irqprobe = 0;
int err = 0;
int i = 0;
LOG_INFO_("Initializing module %s", MODULE_NAME);
LOG_INFO_("Module param : irq_param = %u", irq_param);
/*
* Init internal data
*/
_module = kmalloc(sizeof(module_data), GFP_KERNEL);
memset(_module, 0, sizeof(module_data));
if (_module == NULL) {
goto out;
}
_module->major = DEFAULT_MAJOR_NUMBER;
_module->minor = DEFAULT_MINOR_NUMBER;
_module->map_area = NULL;
_module->io_area = NULL;
_module->irq = irq_param;
/*
* Allocate kmalloc memory
*/
_module->kmalloc_ptr = kmalloc((NB_PAGES + 2) * PAGE_SIZE, GFP_KERNEL);
if (_module->kmalloc_ptr == NULL) {
err = -ENOMEM;
goto out_kfree;
}
// Round it up to the page bondary
_module->kmalloc_area = (int *) ((((unsigned long) _module->kmalloc_ptr) + PAGE_SIZE - 1) & PAGE_MASK);
// Use the kernel memory to access registers from the module
_module->map_area = _module->kmalloc_area;
/*
* Allocate vmalloc memory
*/
_module->vmalloc_area = (int *) vmalloc(NB_PAGES * PAGE_SIZE);
if (_module->vmalloc_area == NULL) {
err = -ENOMEM;
goto out_vfree;
}
/*
* Remap physical addresses
*/
_module->io_area = ioremap(REG_IRQ, NB_PAGES * PAGE_SIZE);
if (_module->io_area == NULL) {
LOG_ERROR_("Physical memory remapping failed (base_addr=%#x, size=%#lx)", REG_IRQ, NB_PAGES * PAGE_SIZE);
goto out_iofree;
}
/*
* Allocates the device numbers
*/
err = my_allocate_device();
if (err) {
LOG_ERROR_("Device allocation failed with code : %d", err);
goto out_unalloc_region;
}
// If no IRQ number has been specified
if (_module->irq <= 0) {
// Probes for an IRQ line number
LOG_INFO("Probing IRQ number...");
irqprobe = my_probe_irq();
if (irqprobe == 0) { // Probe failed
LOG_ERROR("IRQ probing failed : cannot find IRQ number");
} else if (irqprobe < 0) { // Probe error
LOG_ERROR_("IRQ probing failed with error code : %d", err);
} else {
// If an irq number is found
LOG_INFO_("IRQ number detected : %u", irqprobe);
_module->irq = irqprobe;
}
} else { // If an irq number has been specified via a module parameter
LOG_INFO_("IRQ number param specified : irq=%u", _module->irq);
}
// Registers the device making it live immediately
err = my_register_device();
if (err) {
LOG_ERROR_("Device register failed with code : %d", err);
goto out_unregister;
}
LOG_INFO_("Module %s initialized successfully !", MODULE_NAME);
return 0;
/*
* Error fallbacks
*/
out_unregister:
LOG_DEBUG_("Deallocating chrdev for %s", MODULE_NAME);
cdev_del(&_module->mmap_cdev);
// Unreserve the pages
LOG_DEBUG("Unreserving memory pages");
for (i = 0; i < NB_PAGES * PAGE_SIZE; i += PAGE_SIZE) {
SetPageReserved(vmalloc_to_page((void *) (((unsigned long) _module->vmalloc_area) + i)));
SetPageReserved(virt_to_page(((unsigned long )_module->kmalloc_area) + i));
}
out_unalloc_region:
LOG_DEBUG_("Unregistering device %s", MODULE_NAME);
unregister_chrdev_region(_module->mmap_dev, NB_DEVICES);
out_iofree:
iounmap(_module->io_area);
out_vfree:
vfree(_module->vmalloc_area);
out_kfree:
kfree(_module->kmalloc_ptr);
out:
return err;
}
/*****************************************************************************
* Cleanup function of the module which unregisters the major number and
* removes the created device from the system.
*
* #return
* void
*****************************************************************************/
static void __exit my_module_cleanup (void) {
LOG_INFO_("Cleaning up module %s", MODULE_NAME);
// Unregisters a range of device numbers.
unregister_chrdev_region(_module->mmap_dev, NB_DEVICES);
LOG_INFO_("Unregistered device %s", MODULE_NAME);
// Free kernel memory
vfree(_module->vmalloc_area);
kfree(_module->kmalloc_ptr);
kfree(_module);
// Remove the cdev from the system, possibly freeing the structure itself
cdev_del(&_module->mmap_cdev);
LOG_INFO_("Deallocated chrdev for %s", MODULE_NAME);
}
/*****************************************************************************
* Open function of the module which makes this module accessible from the
* user space.
*
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static int my_module_open (struct inode *inode, struct file *filp) {
int err = 0;
// If an interrupt line has been requested
if (_module->irq > 0) {
// Registers the interrupt handler to the kernel
err = request_irq(_module->irq, (irq_handler_t) my_irq_handler, 0, MODULE_NAME, _module);
if (err) {
LOG_ERROR_("%s : Cannot get assigned irq %d, request_irq() failed, code=%d", MODULE_NAME, _module->irq, err);
_module->irq = -1;
return err;
} else {
LOG_INFO_("IRQ number %u assigned successfully to module %s", _module->irq, MODULE_NAME);
}
} else {
LOG_ERROR("Invalid IRQ number : the device will not see the hardware interrupts");
}
LOG_INFO_("%s opened successfully", MODULE_NAME);
return 0;
}
/*****************************************************************************
* Close function of the module which releases the use of this module from
* the user space.
*
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static int my_module_release (struct inode *inode, struct file *filp) {
LOG_INFO_("%s closing...", MODULE_NAME);
// Removes the interrupt handler from kernel
LOG_INFO_("Releasing irq number %u", _module->irq);
free_irq(_module->irq, _module);
LOG_INFO_("%s closed", MODULE_NAME);
return 0;
}
/*****************************************************************************
* Creates a new mapping in the virtual address space of the calling process.
* It makes possible for a user process to access physical memory in the
* kernel space.
*
* #param filp
* The file or device.
* #param vma
* The virtual memory area into which the page range is being
* mapped.
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static int my_module_mmap (struct file *filp, struct vm_area_struct *vma) {
unsigned long start = vma->vm_start; // Virtual address where remapping begins
unsigned long end = vma->vm_end;
unsigned long length = end - start;
unsigned long maxLength = NB_PAGES * PAGE_SIZE;
unsigned long pgoff = vma->vm_pgoff; // PFN of the physAddr to which vAddr is mapped
// Checks length - do not allow larger mappings than the number of pages allocated
if (length > maxLength) {
LOG_ERROR_("Specified virtual memory area is too big : 0x%lx , 0x%lx", length, maxLength);
return -EIO;
}
// At offset 0
if (pgoff == 0) {
// we map the vmalloc'd area
LOG_DEBUG_("Allocating virtual memory, start=%#lx, length=%#lx, pgoff=%#lx", start, length, pgoff);
return my_mmap_vmem(filp, vma);
} else {
// we map the kmalloc'd area
LOG_DEBUG_("Allocating kernel memory, start=%#lx, length=%#lx, pgoff=%#lx", start, length, pgoff);
return my_mmap_kmem(filp, vma);
}
return -EIO;
}
static void my_vma_open (struct vm_area_struct* vma) {
LOG_INFO_("%s VMA open, virtAddr=%#lx, physAddr=%#lx", MODULE_NAME, vma->vm_start, vma->vm_pgoff << PAGE_SHIFT);
}
static void my_vma_close (struct vm_area_struct* vma) {
LOG_INFO_("%s VMA close", MODULE_NAME);
}
/*****************************************************************************
* Interrupt handler which :
* - Reads the registers to know the source of the IT,
* - Clears the IT and wakes up the handler task.
*
* #param irq
* The interrupt number requested.
* #param dev_id
* Pointer to the device structure passed to the function
* request_irq() containing internal data (used when the driver
* manages several instances of the same device).
* #param regs
* Used for debug. Holds a snapshot of the processor's context
* before the processor entered interrupted code.
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static irqreturn_t my_irq_handler (int irq, void *dev_id, struct pt_regs *regs) {
module_data *moduleData = dev_id;
LOG_IT_("%s interrupted, interrupt number = %d", MODULE_NAME, irq);
if (moduleData != NULL) {
// Resets irq
iowrite32(0x0, _module->io_area);
wmb();
} else {
LOG_ERROR("Device structure is NULL");
}
return IRQ_HANDLED;
}
/*****************************************************************************
* Allocates major and minor numbers for this device.
*
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static int my_allocate_device (void) {
int err = 0;
// If a non zero major number is specified
if (_module->major) {
// Updates the dev structure used as input for register_chrdev_region
_module->mmap_dev = MKDEV(_module->major, _module->minor);
// Registers a range of device numbers.
err = register_chrdev_region(_module->mmap_dev, NB_DEVICES, MODULE_NAME);
} else { // If major number is zero, then allocates it dynamically
// Allocates a range of char device numbers chosen dynamically
err = alloc_chrdev_region(&_module->mmap_dev, _module->minor, NB_DEVICES, MODULE_NAME);
_module->major = MAJOR(_module->mmap_dev);
}
// Checks result
if (err) {
LOG_ERROR_("cannot get major number %d", _module->major);
return err;
} else {
LOG_INFO_("Registered device %s : major=%d, minor=%d", MODULE_NAME, _module->major, _module->minor);
}
// Initializes cdev and file operations
cdev_init(&_module->mmap_cdev, &_module_fops);
_module->mmap_cdev.owner = THIS_MODULE;
_module->mmap_cdev.ops = &_module_fops;
return 0;
}
/*****************************************************************************
* Registers the device, makes it live immediately, therefore all
* initialization routines must be done before calling this function.
*
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static int my_register_device (void) {
int err = 0;
// Adds the device to the system making it live immediately
err = cdev_add(&_module->mmap_cdev, _module->mmap_dev, NB_DEVICES);
if (err) {
LOG_ERROR("Could not allocate chrdev");
return err;
} else {
LOG_INFO_("Allocated chrdev for %s", MODULE_NAME);
}
return 0;
}
/*****************************************************************************
* This function uses the probe functions of the kernel to find the irq
* number of the hardware device.
*
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static unsigned int my_probe_irq (void) {
unsigned int irq = -1;
int count = 0;
do {
volatile unsigned long mask;
// Reset the interrupts
iowrite32(0x0, _module->io_area);
wmb(); // Memory barrier
// Start kernel probing
mask = probe_irq_on();
// Trigger all interrupts
iowrite32(0xffffffff, _module->io_area);
wmb(); // Memory barrier
// Wait for it
ndelay(1000);
// Try to find which interrupt occurred
irq = probe_irq_off(mask);
} while (irq < 0 && count++ < 5);
return irq;
}
/*****************************************************************************
* Helper function, mmap's the vmalloc'd area which is not physically
* contiguous.
*
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static int my_mmap_vmem (struct file *filp, struct vm_area_struct *vma) {
unsigned long start = vma->vm_start;
unsigned long end = vma->vm_end;
unsigned long pfn = 0;
long length = end - start;
int ret = 0;
// Check length - do not allow larger mappings than the number of pages allocated
if (length > NB_PAGES * PAGE_SIZE) {
LOG_ERROR_("Specified length (%lu) is larger than the number of pages allocated", length);
return -EIO;
}
// Loop over all pages, map it page individually
while (length > 0) {
pfn = vmalloc_to_pfn(_module->vmalloc_area);
ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED);
if (ret < 0) {
LOG_ERROR_("remap_pfn_range() failed with error %d, addr=%#lx, offset=%#lx", ret, start, pfn);
return ret;
}
start += PAGE_SIZE;
_module->vmalloc_area += PAGE_SIZE;
length -= PAGE_SIZE;
}
vma->vm_ops = &_module_vmops; // Specifies open_vma() and close_vma() functions
my_vma_open(vma); // Calls explicitely open_vma() as its not done by calling mmap()
return 0;
}
/*****************************************************************************
* Helper function, mmap's the kmalloc'd area which is physically contiguous.
*
* #return
* SUCCESS : 0
* FAILURE : Negative error code.
*****************************************************************************/
static int my_mmap_kmem (struct file *filp, struct vm_area_struct *vma) {
unsigned long start = vma->vm_start;
unsigned long end = vma->vm_end;
long length = end - start;
int ret = 0;
// Check length - do not allow larger mappings than the number of pages allocated
if (length > NB_PAGES * PAGE_SIZE) {
LOG_ERROR_("Specified length (%lu) is larger than the number of pages allocated", length);
return -EIO;
}
// Map the whole physically contiguous area in one piece
//pfn = virt_to_phys((void *) _module->kmalloc_area) >> PAGE_SHIFT;
ret = remap_pfn_range(vma, start, vma->vm_pgoff, length, vma->vm_page_prot);
if (ret < 0) {
return ret;
}
vma->vm_ops = &_module_vmops; // Specifies open_vma() and close_vma() functions
my_vma_open(vma); // Calls explicitely open_vma() as its not done by calling mmap()
return 0;
}
/*
* MANDATORY
*
* Used by kernel to load this module and specifies its entry points.
*/
module_init(my_module_init);
module_exit(my_module_cleanup);
I am calculating a timestamp in the kernel and later I want to transfer the timestamp from kernel to the user space. So I am using procfs for communication between kernel and user. I am using the procfile_read function for sending the data from kernel as shown below.
I modified and calculated the timestamp of the kernel code as shown below.
This code is at the network device driver level.
int netif_rx(struct sk_buff *skb)
{
__net_timestamp(skb);//I modify the code in kernel to get the timestamp and store in buffer
// or I can use : skb->tstamp = ktime_get_real(); //to get the timestamp
}
/**
* procfs2.c - create a "file" in /proc
*
*/
#include <linux/module.h> /* Specifically, a module */
#include <linux/kernel.h> /* We're doing kernel work */
#include <linux/proc_fs.h> /* Necessary because we use the proc fs */
#include <asm/uaccess.h> /* for copy_from_user */
#define PROCFS_MAX_SIZE 1024
#define PROCFS_NAME "buffer1k"
/**
* This structure hold information about the /proc file
*
*/
static struct proc_dir_entry *Our_Proc_File;
/**
* The buffer used to store character for this module
*
*/
static char procfs_buffer[PROCFS_MAX_SIZE];
/**
* The size of the buffer
*
*/
static unsigned long procfs_buffer_size = 0;
/**
* This function is called then the /proc file is read
*
*/
int
procfile_read(char *buffer,
char **buffer_location,
off_t offset, int buffer_length, int *eof, void *data)
{
int ret;
printk(KERN_INFO "procfile_read (/proc/%s) called\n", PROCFS_NAME);
if (offset > 0) {
/* we have finished to read, return 0 */
ret = 0;
} else {
/* fill the buffer, return the buffer size */
memcpy(buffer, procfs_buffer, procfs_buffer_size);
ret = procfs_buffer_size;
}
return ret;
}
/**
*This function is called when the module is loaded
*
*/
int init_module()
{
/* create the /proc file */
Our_Proc_File = create_proc_entry(PROCFS_NAME, 0644, NULL);
if (Our_Proc_File == NULL) {
remove_proc_entry(PROCFS_NAME, &proc_root);
printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
PROCFS_NAME);
return -ENOMEM;
}
Our_Proc_File->read_proc = procfile_read;
Our_Proc_File->owner = THIS_MODULE;
Our_Proc_File->mode = S_IFREG | S_IRUGO;
Our_Proc_File->uid = 0;
Our_Proc_File->gid = 0;
Our_Proc_File->size = 37;
printk(KERN_INFO "/proc/%s created\n", PROCFS_NAME);
return 0; /* everything is ok */
}
/**
*This function is called when the module is unloaded
*
*/
void cleanup_module()
{
remove_proc_entry(PROCFS_NAME, &proc_root);
printk(KERN_INFO "/proc/%s removed\n", PROCFS_NAME);
}
The module initialization establishes a procfs entry with create_proc_entry(). The functions procfile_write and procfile_read are initialized to handle writes and reads on this entry. The module's cleanup_module() function, called when the module is unloaded, removes the procfs entry by calling cleanup_module().
My question is how to add the timestamp calculated into the procfile_read function and send it to the user space?
Crude and simple: set a global variable in your module with the timestamp and use snprintf to copy it into your profs_buffer. In fact, you might do snprintf directly into the provided buffer and loose the procfs_buffer all together.
There are multiple problems with this approach (it's not atomic, for one) but if you just want a crude debug or logging interface it works.