I am writing a character device that will be given a processes pid and return the parent process id, start time, and number of siblings. I am using this site: http://tldp.org/LDP/lkmpg/2.6/html/x892.html#AEN972 as a guide.
I would like to add an argument into the read call of the character driver but it is not letting me do that. My code looks like this:
/*
* chardev.c - Create an input/output character device
*/
#include <linux/kernel.h> /* We're doing kernel work */
#include <linux/module.h> /* Specifically, a module */
#include <linux/fs.h>
#include <asm/uaccess.h> /* for get_user and put_user */
#include "chardev.h"
#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80
struct procinfo {
pid_t pid;
pid_t ppid;
struct timespec start_time;
int num_sib;
};
/*
* Is the device open right now? Used to prevent
* concurent access into the same device
*/
static int Device_Open = 0;
/*
* The message the device will give when asked
*/
static char Message[BUF_LEN];
/*
* How far did the process reading the message get?
* Useful if the message is larger than the size of the
* buffer we get to fill in device_read.
*/
static char *Message_Ptr;
/*
* This is called whenever a process attempts to open the device file
*/
static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
printk(KERN_INFO "device_open(%p)\n", file);
#endif
/*
* We don't want to talk to two processes at the same time
*/
if (Device_Open)
return -EBUSY;
Device_Open++;
/*
* Initialize the message
*/
Message_Ptr = Message;
try_module_get(THIS_MODULE);
return SUCCESS;
}
static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
printk(KERN_INFO "device_release(%p,%p)\n", inode, file);
#endif
/*
* We're now ready for our next caller
*/
Device_Open--;
module_put(THIS_MODULE);
return SUCCESS;
}
/*
* This function is called whenever a process which has already opened the
* device file attempts to read from it.
*/
static ssize_t device_read(struct file *file, /* see include/linux/fs.h */
char __user * buffer, /* buffer to be
* filled with data */
pid_t pid,
size_t length, /* length of the buffer */
loff_t * offset)
{
/*
* Number of bytes actually written to the buffer
*/
int bytes_read = 0;
#ifdef DEBUG
printk(KERN_INFO "device_read(%p,%p,%d)\n", file, buffer, length);
#endif
/*
* If we're at the end of the message, return 0
* (which signifies end of file)
*/
if (*Message_Ptr == 0)
return 0;
/*
* Actually put the data into the buffer
*/
while (length && *Message_Ptr) {
/*
* Because the buffer is in the user data segment,
* not the kernel data segment, assignment wouldn't
* work. Instead, we have to use put_user which
* copies data from the kernel data segment to the
* user data segment.
*/
put_user(*(Message_Ptr++), buffer++);
length--;
bytes_read++;
}
#ifdef DEBUG
printk(KERN_INFO "Read %d bytes, %d left\n", bytes_read, length);
#endif
/*
* Read functions are supposed to return the number
* of bytes actually inserted into the buffer
*/
return bytes_read;
}
/*
* This function is called when somebody tries to
* write into our device file.
*/
static ssize_t
device_write(struct file *file,
const char __user * buffer, size_t length, loff_t * offset)
{
int i;
#ifdef DEBUG
printk(KERN_INFO "device_write(%p,%s,%d)", file, buffer, length);
#endif
for (i = 0; i < length && i < BUF_LEN; i++)
get_user(Message[i], buffer + i);
Message_Ptr = Message;
/*
* Again, return the number of input characters used
*/
return i;
}
/*
* This function is called whenever a process tries to do an ioctl on our
* device file. We get two extra parameters (additional to the inode and file
* structures, which all device functions get): the number of the ioctl called
* and the parameter given to the ioctl function.
*
* If the ioctl is write or read/write (meaning output is returned to the
* calling process), the ioctl call returns the output of this function.
*
*/
long device_ioctl(struct file *file, /* see include/linux/fs.h */
unsigned int ioctl_num, /* number and param for ioctl */
unsigned long ioctl_param)
{
int i;
char *temp;
char ch;
/*
* Switch according to the ioctl called
*/
switch (ioctl_num) {
case IOCTL_SET_MSG:
/*
* Receive a pointer to a message (in user space) and set that
* to be the device's message. Get the parameter given to
* ioctl by the process.
*/
temp = (char *)ioctl_param;
/*
* Find the length of the message
*/
get_user(ch, temp);
for (i = 0; ch && i < BUF_LEN; i++, temp++)
get_user(ch, temp);
device_write(file, (char *)ioctl_param, i, 0);
break;
case IOCTL_GET_MSG:
/*
* Give the current message to the calling process -
* the parameter we got is a pointer, fill it.
*/
i = device_read(file, (char *)ioctl_param, 99, 0);
/*
* Put a zero at the end of the buffer, so it will be
* properly terminated
*/
put_user('\0', (char *)ioctl_param + i);
break;
case IOCTL_GET_NTH_BYTE:
/*
* This ioctl is both input (ioctl_param) and
* output (the return value of this function)
*/
return Message[ioctl_param];
break;
}
return SUCCESS;
}
/* Module Declarations */
/*
* This structure will hold the functions to be called
* when a process does something to the device we
* created. Since a pointer to this structure is kept in
* the devices table, it can't be local to
* init_module. NULL is for unimplemented functions.
*/
struct file_operations Fops = {
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl,
.open = device_open,
.release = device_release, /* a.k.a. close */
};
/*
* Initialize the module - Register the character device
*/
int init_module()
{
int ret_val;
/*
* Register the character device (atleast try)
*/
ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);
/*
* Negative values signify an error
*/
if (ret_val < 0) {
printk(KERN_ALERT "%s failed with %d\n",
"Sorry, registering the character device ", ret_val);
return ret_val;
}
printk(KERN_INFO "%s The major device number is %d.\n",
"Registeration is a success", MAJOR_NUM);
printk(KERN_INFO "If you want to talk to the device driver,\n");
printk(KERN_INFO "you'll have to create a device file. \n");
printk(KERN_INFO "We suggest you use:\n");
printk(KERN_INFO "mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM);
printk(KERN_INFO "The device file name is important, because\n");
printk(KERN_INFO "the ioctl program assumes that's the\n");
printk(KERN_INFO "file you'll use.\n");
return 0;
}
/*
* Cleanup - unregister the appropriate file from /proc
*/
void cleanup_module()
{
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
#if 0
int ret;
/*
* Unregister the device
*/
ret = unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
/*
* If there's an error, report it
*/
if (ret < 0)
printk(KERN_ALERT "Error: unregister_chrdev: %d\n", ret);
#endif
}
Related
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
Trying to make a read function, that not only adds the numbers together. But also counts how many numbers were added.
Needs to be used in such script:
for i in 1 2 4 8 16 32 64; do
echo $i > /dev/summer0
sleep 60
done
But also just using a test case.
Here is the kernel module code:
/* the memory of the device
*/
int total;
/* Global variables declared as staic, so are global within the file.*/
static char *msg_Ptr;
static char msg[BUF_LEN];
static int n;
/* called after the device is opened
*/
int device_open(struct inode *inode, struct file *file)
{
printk("\nsummer device is open\n");
total = 0;
printk(msg);
msg_Ptr = msg;
return 0;
}
/* Read function */
static ssize_t device_read(struct file *filep, char *buffer, size_t length, loff_t *offset)
{
/* Number of bytes actually written to the buffer */
int bytes_read = 0;
/* If we're at the end of the message, return 0 signifying end of file */
if (*msg_Ptr == 0) return 0;
/* Actually put the data into the buffer */
while (length && *msg_Ptr)
{
/* The buffer is in the user data segment, not the kernel segment;
* assignment won't work. We have to use put_use which copies data from
* the kernel data segment to the user data segment. */
__put_user(*(msg_Ptr++), buffer++);
length++;
n++;
}
/* Most read functions return the number of bytes put into the buffer */
return n;
}
/* handling of ioctl events
*/
long device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
{
int number;
switch(ioctl_num)
{
case SUMMER_SET:
__get_user(number, (int*)ioctl_param);
total += number;
break;
case SUMMER_GET:
__put_user(total, (int*)ioctl_param);
break;
}
return 0;
}
/* table of event handlers of the device
*/
struct file_operations fops =
{
read : device_read,
//write : device_write,
open : device_open,
release: device_close,
unlocked_ioctl : device_ioctl
};
As for the testcase
int n;
int main(void)
{
int sum;
char buffer[BUF_LEN];
/* open character device */
int fd = open("/dev/summer0", O_RDWR); // open device
if(fd < 0)
{
printf("no device found\n");
return 0;
}
int nc = read(fd,buffer,4,n);
for(;;)
{
int num;
printf("Number (exit with 0) = ");
while(scanf("%d", &num) != 1)
;
printf("-- %d --\n", num);
if(num <= 0) break;
/* use ioctl to pass a value to the character device */
ioctl(fd, SUMMER_SET, &num);
n++;
}
/* use ioctl to get a value from the character device */
ioctl(fd, SUMMER_GET, &sum);
printf("#char = %d\n", nc);
printf("Result = %d\n", sum);
printf("Buffer: %s\n" , buffer);
close(fd); // close device
return 0;
}
The program is keeping track of the amount of numbers entered during a single test, but it is supposed to keep track of the numbers until I install the kernel module.
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);
Is fopen("tftp://1.1.1.1/file.txt","rb"); a valid statement? Can urls be opened using fopen in C programming?
No, but you can use libcurl, an example:
#include <stdio.h>
#include <curl/curl.h>
/*
* This is an example showing how to get a single file from an FTP server.
* It delays the actual destination file creation until the first write
* callback so that it won't create an empty file in case the remote file
* doesn't exist or something else fails.
*/
struct FtpFile {
const char *filename;
FILE *stream;
};
static size_t my_fwrite(void *buffer, size_t size, size_t nmemb, void *stream)
{
struct FtpFile *out=(struct FtpFile *)stream;
if(out && !out->stream) {
/* open file for writing */
out->stream=fopen(out->filename, "wb");
if(!out->stream)
return -1; /* failure, can't open file to write */
}
return fwrite(buffer, size, nmemb, out->stream);
}
int main(void)
{
CURL *curl;
CURLcode res;
struct FtpFile ftpfile={
"curl.tar.gz", /* name to store the file as if succesful */
NULL
};
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
/*
* You better replace the URL with one that works!
*/
curl_easy_setopt(curl, CURLOPT_URL,
"ftp://ftp.example.com/pub/www/utilities/curl/curl-7.9.2.tar.gz");
/* Define our callback to get called when there's data to be written */
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_fwrite);
/* Set a pointer to our struct to pass to the callback */
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ftpfile);
/* Switch on full protocol/debug output */
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
res = curl_easy_perform(curl);
/* always cleanup */
curl_easy_cleanup(curl);
if(CURLE_OK != res) {
/* we failed */
fprintf(stderr, "curl told us %d\n", res);
}
}
if(ftpfile.stream)
fclose(ftpfile.stream); /* close the local file */
curl_global_cleanup();
return 0;
}
Or (as pointed out by #Paul) you can pipe a process (E.g.: wget url) with popen:
#include <stdio.h>
FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);
int main(void)
{
/* wget -q = silent mode */
FILE *cmd = popen("wget -q -O - ftp://debian.org/debian-security/README.security", "r");
char result[1024];
while (fgets(result, sizeof(result), cmd) != NULL)
printf("%s", result);
pclose(cmd);
return 0;
}
The fopen in <stdio.h> doesn't do that.
However, nothing prevents someone from writing a function called fopen() that does something else.
FILE *popen(const char *command, const char *mode) can be used to spawn a process running an appropriate command line tool such as tftp or wget, and thereby accomplish this downloading of a remote resource into a file descriptor accessible from C code. The syntax for a popen() call is very similar to what you have shown. It is missing the program name for the download utility, though. A bare url or ftp address won't work for popen().
See:
fopen man page
popen man page
Also note:
The PHP language version of fopen() does open bare URLs. But PHP != C
It's not that easy as simply using fopen but it can be done.
You need to use libcurl. Take a look here.
From the site:
/*****************************************************************************
*
* This example requires libcurl 7.9.7 or later.
*/
#include <stdio.h>
#include <string.h>
#ifndef WIN32
#include <sys/time.h>
#endif
#include <stdlib.h>
#include <errno.h>
#include <curl/curl.h>
enum fcurl_type_e {
CFTYPE_NONE=0,
CFTYPE_FILE=1,
CFTYPE_CURL=2
};
struct fcurl_data
{
enum fcurl_type_e type; /* type of handle */
union {
CURL *curl;
FILE *file;
} handle; /* handle */
char *buffer; /* buffer to store cached data*/
size_t buffer_len; /* currently allocated buffers length */
size_t buffer_pos; /* end of data in buffer*/
int still_running; /* Is background url fetch still in progress */
};
typedef struct fcurl_data URL_FILE;
/* exported functions */
URL_FILE *url_fopen(const char *url,const char *operation);
int url_fclose(URL_FILE *file);
int url_feof(URL_FILE *file);
size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file);
char * url_fgets(char *ptr, size_t size, URL_FILE *file);
void url_rewind(URL_FILE *file);
/* we use a global one for convenience */
CURLM *multi_handle;
/* curl calls this routine to get more data */
static size_t write_callback(char *buffer,
size_t size,
size_t nitems,
void *userp)
{
char *newbuff;
size_t rembuff;
URL_FILE *url = (URL_FILE *)userp;
size *= nitems;
rembuff=url->buffer_len - url->buffer_pos; /* remaining space in buffer */
if(size > rembuff) {
/* not enough space in buffer */
newbuff=realloc(url->buffer,url->buffer_len + (size - rembuff));
if(newbuff==NULL) {
fprintf(stderr,"callback buffer grow failed\n");
size=rembuff;
}
else {
/* realloc suceeded increase buffer size*/
url->buffer_len+=size - rembuff;
url->buffer=newbuff;
}
}
memcpy(&url->buffer[url->buffer_pos], buffer, size);
url->buffer_pos += size;
return size;
}
/* use to attempt to fill the read buffer up to requested number of bytes */
static int fill_buffer(URL_FILE *file, size_t want)
{
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
struct timeval timeout;
int rc;
/* only attempt to fill buffer if transactions still running and buffer
* doesnt exceed required size already
*/
if((!file->still_running) || (file->buffer_pos > want))
return 0;
/* attempt to fill buffer */
do {
int maxfd = -1;
long curl_timeo = -1;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
/* set a suitable timeout to fail on */
timeout.tv_sec = 60; /* 1 minute */
timeout.tv_usec = 0;
curl_multi_timeout(multi_handle, &curl_timeo);
if(curl_timeo >= 0) {
timeout.tv_sec = curl_timeo / 1000;
if(timeout.tv_sec > 1)
timeout.tv_sec = 1;
else
timeout.tv_usec = (curl_timeo % 1000) * 1000;
}
/* get file descriptors from the transfers */
curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
/* In a real-world program you OF COURSE check the return code of the
function calls. On success, the value of maxfd is guaranteed to be
greater or equal than -1. We call select(maxfd + 1, ...), specially
in case of (maxfd == -1), we call select(0, ...), which is basically
equal to sleep. */
rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
switch(rc) {
case -1:
/* select error */
break;
case 0:
default:
/* timeout or readable/writable sockets */
curl_multi_perform(multi_handle, &file->still_running);
break;
}
} while(file->still_running && (file->buffer_pos < want));
return 1;
}
/* use to remove want bytes from the front of a files buffer */
static int use_buffer(URL_FILE *file,int want)
{
/* sort out buffer */
if((file->buffer_pos - want) <=0) {
/* ditch buffer - write will recreate */
if(file->buffer)
free(file->buffer);
file->buffer=NULL;
file->buffer_pos=0;
file->buffer_len=0;
}
else {
/* move rest down make it available for later */
memmove(file->buffer,
&file->buffer[want],
(file->buffer_pos - want));
file->buffer_pos -= want;
}
return 0;
}
URL_FILE *url_fopen(const char *url,const char *operation)
{
/* this code could check for URLs or types in the 'url' and
basicly use the real fopen() for standard files */
URL_FILE *file;
(void)operation;
file = malloc(sizeof(URL_FILE));
if(!file)
return NULL;
memset(file, 0, sizeof(URL_FILE));
if((file->handle.file=fopen(url,operation)))
file->type = CFTYPE_FILE; /* marked as URL */
else {
file->type = CFTYPE_CURL; /* marked as URL */
file->handle.curl = curl_easy_init();
curl_easy_setopt(file->handle.curl, CURLOPT_URL, url);
curl_easy_setopt(file->handle.curl, CURLOPT_WRITEDATA, file);
curl_easy_setopt(file->handle.curl, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(file->handle.curl, CURLOPT_WRITEFUNCTION, write_callback);
if(!multi_handle)
multi_handle = curl_multi_init();
curl_multi_add_handle(multi_handle, file->handle.curl);
/* lets start the fetch */
curl_multi_perform(multi_handle, &file->still_running);
if((file->buffer_pos == 0) && (!file->still_running)) {
/* if still_running is 0 now, we should return NULL */
/* make sure the easy handle is not in the multi handle anymore */
curl_multi_remove_handle(multi_handle, file->handle.curl);
/* cleanup */
curl_easy_cleanup(file->handle.curl);
free(file);
file = NULL;
}
}
return file;
}
int url_fclose(URL_FILE *file)
{
int ret=0;/* default is good return */
switch(file->type) {
case CFTYPE_FILE:
ret=fclose(file->handle.file); /* passthrough */
break;
case CFTYPE_CURL:
/* make sure the easy handle is not in the multi handle anymore */
curl_multi_remove_handle(multi_handle, file->handle.curl);
/* cleanup */
curl_easy_cleanup(file->handle.curl);
break;
default: /* unknown or supported type - oh dear */
ret=EOF;
errno=EBADF;
break;
}
if(file->buffer)
free(file->buffer);/* free any allocated buffer space */
free(file);
return ret;
}
int url_feof(URL_FILE *file)
{
int ret=0;
switch(file->type) {
case CFTYPE_FILE:
ret=feof(file->handle.file);
break;
case CFTYPE_CURL:
if((file->buffer_pos == 0) && (!file->still_running))
ret = 1;
break;
default: /* unknown or supported type - oh dear */
ret=-1;
errno=EBADF;
break;
}
return ret;
}
size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file)
{
size_t want;
switch(file->type) {
case CFTYPE_FILE:
want=fread(ptr,size,nmemb,file->handle.file);
break;
case CFTYPE_CURL:
want = nmemb * size;
fill_buffer(file,want);
/* check if theres data in the buffer - if not fill_buffer()
* either errored or EOF */
if(!file->buffer_pos)
return 0;
/* ensure only available data is considered */
if(file->buffer_pos < want)
want = file->buffer_pos;
/* xfer data to caller */
memcpy(ptr, file->buffer, want);
use_buffer(file,want);
want = want / size; /* number of items */
break;
default: /* unknown or supported type - oh dear */
want=0;
errno=EBADF;
break;
}
return want;
}
char *url_fgets(char *ptr, size_t size, URL_FILE *file)
{
size_t want = size - 1;/* always need to leave room for zero termination */
size_t loop;
switch(file->type) {
case CFTYPE_FILE:
ptr = fgets(ptr,size,file->handle.file);
break;
case CFTYPE_CURL:
fill_buffer(file,want);
/* check if theres data in the buffer - if not fill either errored or
* EOF */
if(!file->buffer_pos)
return NULL;
/* ensure only available data is considered */
if(file->buffer_pos < want)
want = file->buffer_pos;
/*buffer contains data */
/* look for newline or eof */
for(loop=0;loop < want;loop++) {
if(file->buffer[loop] == '\n') {
want=loop+1;/* include newline */
break;
}
}
/* xfer data to caller */
memcpy(ptr, file->buffer, want);
ptr[want]=0;/* allways null terminate */
use_buffer(file,want);
break;
default: /* unknown or supported type - oh dear */
ptr=NULL;
errno=EBADF;
break;
}
return ptr;/*success */
}
void url_rewind(URL_FILE *file)
{
switch(file->type) {
case CFTYPE_FILE:
rewind(file->handle.file); /* passthrough */
break;
case CFTYPE_CURL:
/* halt transaction */
curl_multi_remove_handle(multi_handle, file->handle.curl);
/* restart */
curl_multi_add_handle(multi_handle, file->handle.curl);
/* ditch buffer - write will recreate - resets stream pos*/
if(file->buffer)
free(file->buffer);
file->buffer=NULL;
file->buffer_pos=0;
file->buffer_len=0;
break;
default: /* unknown or supported type - oh dear */
break;
}
}
/* Small main program to retrive from a url using fgets and fread saving the
* output to two test files (note the fgets method will corrupt binary files if
* they contain 0 chars */
int main(int argc, char *argv[])
{
URL_FILE *handle;
FILE *outf;
int nread;
char buffer[256];
const char *url;
if(argc < 2)
url="http://192.168.7.3/testfile";/* default to testurl */
else
url=argv[1];/* use passed url */
/* copy from url line by line with fgets */
outf=fopen("fgets.test","w+");
if(!outf) {
perror("couldn't open fgets output file\n");
return 1;
}
handle = url_fopen(url, "r");
if(!handle) {
printf("couldn't url_fopen() %s\n", url);
fclose(outf);
return 2;
}
while(!url_feof(handle)) {
url_fgets(buffer,sizeof(buffer),handle);
fwrite(buffer,1,strlen(buffer),outf);
}
url_fclose(handle);
fclose(outf);
/* Copy from url with fread */
outf=fopen("fread.test","w+");
if(!outf) {
perror("couldn't open fread output file\n");
return 1;
}
handle = url_fopen("testfile", "r");
if(!handle) {
printf("couldn't url_fopen() testfile\n");
fclose(outf);
return 2;
}
do {
nread = url_fread(buffer, 1,sizeof(buffer), handle);
fwrite(buffer,1,nread,outf);
} while(nread);
url_fclose(handle);
fclose(outf);
/* Test rewind */
outf=fopen("rewind.test","w+");
if(!outf) {
perror("couldn't open fread output file\n");
return 1;
}
handle = url_fopen("testfile", "r");
if(!handle) {
printf("couldn't url_fopen() testfile\n");
fclose(outf);
return 2;
}
nread = url_fread(buffer, 1,sizeof(buffer), handle);
fwrite(buffer,1,nread,outf);
url_rewind(handle);
buffer[0]='\n';
fwrite(buffer,1,1,outf);
nread = url_fread(buffer, 1,sizeof(buffer), handle);
fwrite(buffer,1,nread,outf);
url_fclose(handle);
fclose(outf);
return 0;/* all done */
}
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.