in kernel driver, why does not mmap work in procfs? - linux-kernel

I implement mmap function, and mount it to file operation.
And create a file in /proc.
but when I insmod, it responses "mmap_example2: Unknown symbol _page_cachable_default
insmod: can't insert 'mmap_example2.ko': unknown symbol in module, or unknown parameter"
when i remove mmap function from file operations, it can be inserted.
so do i neglect something? how to make mmap work in procfs?
the code is below
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#define FILE_NAME "test"
typedef enum ALLOC_TYPE
{
KMALLOC = 0, VMALLOC, MAX_ALLOC_TYPE,
} eAllocType;
static unsigned char array[10]={0,1,2,3,4,5,6,7,8,9};
static unsigned char *buffer;
static int file_open(struct inode *pInode, struct file *pFile)
{
printk("%s\n", __FUNCTION__);
return 0;
}
static int file_release(struct inode *pInode, struct file *pFile)
{
printk("%s\n", __FUNCTION__);
return 0;
}
static int file_mmap(struct file *pFile, struct vm_area_struct* pVMA)
{
unsigned long page;
unsigned char i;
unsigned long start = (unsigned long)pVMA->vm_start;
unsigned long size = (unsigned long)(pVMA->vm_end - pVMA->vm_start);
page = virt_to_phys(buffer);
if(remap_pfn_range(pVMA,start,page>>PAGE_SHIFT,size,PAGE_SHARED))
return -1;
for(i=0;i<10;i++)
buffer[i] = array[i];
return 0;
}
struct file_operations file_ops =
{
.open = file_open,
.release = file_release,
.mmap = file_mmap,
};
static int mmap_example2_init(void)
{
struct proc_dir_entry* entry = NULL;
printk("%s init\n", __FUNCTION__);
if(!(entry = create_proc_entry(FILE_NAME,0666,NULL)))
{
printk("%s fail to create proc file\n",__FUNCTION__);
return -EINVAL;
}
entry->proc_fops = &file_ops;
buffer = kmalloc(10,GFP_KERNEL);
if (!buffer)
{
printk("allocate mem error\n");
return -1;
}
SetPageReserved(virt_to_page(buffer));
return 0;
}
static void mmap_example2_exit(void)
{
printk("%s exit\n", __FUNCTION__);
remove_proc_entry(FILE_NAME,NULL);
ClearPageReserved(virt_to_page(buffer));
kfree(buffer);
}
module_init(mmap_example2_init);
module_exit(mmap_example2_exit);

To add file_operations use proc_create instead of create_proc_entry and pass it your file_operation object
static struct file_operations myops =
{
.read = myread,
.mmap = mymmap,
};
static int simple_init(void)
{
ent=proc_create("mytest",0660,NULL,&myops);
printk(KERN_ALERT "hello, module %d...\n",irq);
return 0;
}

Related

Unable to print the message sent from user space C application to linux kernel module

I have developed a simple linux kernel module :
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
ssize_t exer_open(struct inode *pinode, struct file *pfile) {
return 0;
}
ssize_t exer_read(struct file *pfile, char __user *buffer, size_t length, loff_t *offset) {
return 0;
}
ssize_t exer_write(struct file *pfile, const char __user *buffer, size_t length, loff_t *offset) {
return length;
}
ssize_t exer_close(struct inode *pinode, struct file *pfile) {
return 0;
}
struct file_operations exer_file_operations = {
.owner = THIS_MODULE,
.open = exer_open,
.read = exer_read,
.write = exer_write,
.release = exer_close,
};
int exer_simple_module_init(void) {
printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__);
register_chrdev(240, "Simple Char Drv", &exer_file_operations);
return 0;
}
void exer_simple_module_exit(void) {
unregister_chrdev(240, "Simple Char Drv");
}
module_init(exer_simple_module_init);
module_exit(exer_simple_module_exit);
I insert this module to the kernel using insmod command without any problem.
I want to use this module to print a message sent to it by user space program that I have developed too :
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
int main()
{
int ret, fd;
char stringToSend[] = "Hello World !";
fd = open("/dev/char_device", O_RDWR); // Open the device with read/write access
if (fd < 0)
{
perror("Failed to open the device...");
return errno;
}
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;
}
return 0;
}
When I execute the program and examin the kernel logs using tail -f /var/log/messages command I can see : user.alert kernel: Inside the exer_read function But I cant see the message " Hello World !"
I don't know what I am missing here especially I still beginner in developing modules and using it. Help me please!
For people who still can't find a solution for that, I have an answer.
This is the module :
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gaston");
MODULE_DESCRIPTION("A simple Linux char driver");
MODULE_VERSION("0.1");
#define MAX 256
static char message[MAX] =""; ///< Memory for the string that is passed from userspace
ssize_t exer_open(struct inode *pinode, struct file *pfile) {
printk(KERN_INFO "Device has been opened\n");
return 0;
}
ssize_t exer_read(struct file *pfile, char __user *buffer, size_t length, loff_t *offset) {
return 0;
}
ssize_t exer_write(struct file *pfile, const char __user *buffer, size_t length, loff_t *offset) {
if (length > MAX)
return -EINVAL;
if (copy_from_user(message, buffer, length) != 0)
return -EFAULT;
printk(KERN_INFO "Received %s characters from the user\n", message);
return 0;
}
ssize_t exer_close(struct inode *pinode, struct file *pfile) {
printk(KERN_INFO "Device successfully closed\n");
return 0;
}
struct file_operations exer_file_operations = {
.owner = THIS_MODULE,
.open = exer_open,
.read = exer_read,
.write = exer_write,
.release = exer_close,
};
int exer_simple_module_init(void) {
printk(KERN_INFO "Initializing the LKM\n");
register_chrdev(240, "Simple Char Drv", &exer_file_operations);
return 0;
}
void exer_simple_module_exit(void) {
unregister_chrdev(240, "Simple Char Drv");
}
module_init(exer_simple_module_init);
module_exit(exer_simple_module_exit);
Ans this is the application :
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#define BUFFER_LENGTH 256
int main()
{
int ret, fd;
char stringToSend[BUFFER_LENGTH];
fd = open("/dev/char_device", 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("%s", 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;
}
return 0;
}
You will see that this will work fine.

BUG: Unable to handle kernel paging request at error for the sample kernel module

I am writing a sample kernel module which reads data sent through ioctl call from application and prints them.
I am passing structure "ioctl_struct" through ioctl from the application and in the kernel module, I will be printing its member variables.
this works absolutely fine in a few machines. In a few machines
"BUG: unable to handle kernel paging request at"
the error is thrown while accessing "name and testStruct's id1 and id2".
I don't think this module is hardware/kernel dependent.
I am not sure where it's going wrong. any help would be appreciated.
thanks.
Driver.c kernel module
static const char DEVICE_NAME[]="testipc";
static struct proc_dir_entry * proc_ipc = NULL;
struct test
{
int id1;
int id2;
};
struct ioctl_struct
{
__user struct test *testStruct;
__user int * id;
__user char * name;
int cmd;
};
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.unlocked_ioctl = etx_ioctl,
.release = etx_release,
.unlocked_ioctl = etx_ioctl,
};
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
printk("reached ioctl....\n");
struct ioctl_struct buf;
if (copy_from_user(&buf, (void *)arg, sizeof(buf)))
return -EFAULT;
printk("succes..2\n");
printk("id %d\n",buf.id);
printk("cmd %d\n",buf.cmd);
printk("filename %s\n",buf.name);
printk("token %d\n",buf.testStruct->id1);
printk("token %d\n",buf.testStruct->id2);
return 0;
}
static int __init etx_driver_init(void)
{
printk("new test driver loaded..");
proc_ipc = proc_create(DEVICE_NAME, 0, NULL, &fops);
if (!proc_ipc)
{
printk(KERN_ALERT "Unable to create /proc/%s\n", DEVICE_NAME);
return 1;
}
return 0;
}
void __exit etx_driver_exit(void)
{
if (proc_ipc)
proc_remove(proc_ipc);
proc_ipc = NULL;
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lin");
MODULE_DESCRIPTION("A simple driver");
MODULE_VERSION("1.0");
and following is my application file
#include <stdio.h>
#include<sys/ioctl.h>
# define __user
static int fd=NULL;
#define TEST_IOCTL _IOWR('z', 80, struct ioctl_struct)
struct test
{
int id1;
int id2;
};
struct ioctl_struct
{
__user struct test *testStruct;
__user int * id;
__user char * name;
int cmd;
};
void init()
{
printf("\nOpening Driver\n");
fd = open("/proc/testipc", O_RDWR);
if(fd < 0) {
printf("Cannot open device file...\n");
return 0;
}
}
void send()
{
int id=5;
int *pid=id;
char name[10]={'H','e','l','l','o'};
struct test testStruct;
testStruct.id1=44;
testStruct.id2=33;
struct ioctl_struct request;
request.name = name ;
request.id = pid;
request.cmd = 33;
request.testStruct = &testStruct;
ioctl(fd, TEST_IOCTL, &request);
}
void finish()
{
printf("Closing Driver\n");
close(fd);
}
int main()
{
init();
send();
finish();
return 0;
}
In dmesg,
id 5,
cmd 33,
Hello,
44,
33,
should be printed

There is no entry for device under /dev even after class_create and device_create

I am making one simple char driver and I learnt that there are 2 ways I can get Major number for my driver to pair with - alloc_chrdev_region(and register_chrdev_region) and register_chrdev. I initially started with register_chrdev and it gave me my major number and also created entry in /dev (class and device create used).
But when I change for register_chrdev to alloc_chrdev_region to acquire major number (using chrdev_init and chrdev_add), leaving rest of the entry function same, I don't see an entry in /dev, though when I make it manually with mknode, and run the test application to use the driver, it works fine.
Below is the code of entry point that does not produce the /dev entry
#include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/kernel.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#include<linux/stat.h>
#include<linux/cdev.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#define DEVICE_NAME "myCharDevice"
#define MODULE_NAME "myCharDriver"
#define CLASS_NAME "myCharClass"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YASH BHATT");
MODULE_VERSION(".01");
static char *bufferMemory;
static int bufferPointer;
static int bufferSize = 15;
static dev_t myChrDevid;
static struct cdev *myChrDevCdev;
static struct class *pmyCharClass;
static struct device *pmyCharDevice;
int majorNumber = 0;
static int charDriverOpen(struct inode *inodep, struct file *filep);
static int charDriverClose(struct inode *inodep, struct file *filep);
static ssize_t charDriverWrite(struct file *filep, const char *buffer, size_t len, loff_t *offset);
static ssize_t charDriverRead(struct file *filep, char *buffer, size_t len, loff_t *offset);
static int charDriverEntry(void);
static void charDriverExit(void);
static ssize_t attrShowData(struct device*, struct device_attribute*, char*);
static ssize_t attrStoreData(struct device*, struct device_attribute*, const char*, size_t);
static ssize_t attrShowBuffer(struct device*, struct device_attribute*, char*);
static ssize_t attrStoreBuffer(struct device*, struct device_attribute*, const char*, size_t);
/* The following function is called when the file placed on the sysfs is accessed for read*/
static ssize_t attrShowData(struct device* pDev, struct device_attribute* attr, char* buffer)
{
printk(KERN_INFO "MESG: The data has been accessed through the entry in sysfs\n");
if (bufferPointer == 0)
{
printk(KERN_WARNING "Thre is no data to read from buffer!\n");
return -1;
}
strncpy(buffer, bufferMemory, bufferPointer);
/* Note : Here we can directly use strncpy because we are already in kernel space and do not need to translate address*/
return bufferPointer;
}
static ssize_t attrStoreData(struct device* pDev, struct device_attribute* attr, const char* buffer, size_t length)
{
printk(KERN_INFO "Writing to attribute\n");
bufferPointer = length;
strncpy(bufferMemory, buffer, length);
return length;
}
static ssize_t attrShowBuffer(struct device* pDev, struct device_attribute* attr, char* buffer)
{
int counter;
int temp = bufferSize;
char bufferSizeArray[4] = {0};
counter = 3;
//printk(KERN_INFO "Buffer = %d\n",bufferSize % 10);
do
{
bufferSizeArray[counter] = '0' + (bufferSize % 10);
//printk(KERN_INFO "Character at %d is : %c\n",counter,bufferSizeArray[counter]);
bufferSize /= 10;
counter--;
}
while(counter != -1);
strncpy(buffer, bufferSizeArray, 4);
bufferSize = temp;
/* Note : Here we can directly use strncpy because we are already in kernel space and do not need to translate address*/
return 4;
}
static ssize_t attrStoreBuffer(struct device* pDev, struct device_attribute* attr, const char* buffer, size_t length)
{
int counter;
bufferPointer = length;
//printk(KERN_INFO "Length : %d With first char %c\n",length,buffer[0]);
bufferSize = 0;
for (counter = 0; counter < length-1 ; counter++)
{
bufferSize = (bufferSize * 10) + (buffer[counter] - '0') ;
}
//printk(KERN_INFO "Buffer size new : %d\n",bufferSize);
return length;
}
/* These macros converts the function in to instances dev_attr_<_name>*/
/* Defination of the macro is as follows : DEVICE_ATTR(_name, _mode, _show, _store) */
/* Note the actual implementation of the macro makes an entry in the struct device_attribute. This macro does that for us */
static DEVICE_ATTR(ShowData, S_IRWXU, attrShowData, attrStoreData); // S_IRUSR gives read access to the user
static DEVICE_ATTR(Buffer, S_IRWXU, attrShowBuffer, attrStoreBuffer); // S_IRUSR gives read access to the user
static struct file_operations fops =
{
.open = charDriverOpen,
.release = charDriverClose,
.read = charDriverRead,
.write = charDriverWrite,
};
static int __init charDriverEntry()
{
int returnValue;
//majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
returnValue = alloc_chrdev_region(&myChrDevid, 0, 1, DEVICE_NAME);
/* This function takes 4 arguments - dev_t address, start of minor number, range/count of minor number, Name; Note - unlike register_chrdev fops have not
yet been tied to the major number */
if (returnValue < 0)
{
printk(KERN_ALERT "ERROR : can not aquire major number! error %d",returnValue);
return -1;
}
printk(KERN_INFO "Aquired Major Number! : %d\n", MAJOR(myChrDevid));
//cdev_init(&myChrDevCdev,&fops);
myChrDevCdev = cdev_alloc();
if (IS_ERR(myChrDevCdev))
{
printk(KERN_ALERT "Failed to allocate space for CharDev struct\n");
unregister_chrdev_region(myChrDevid, 1);
return -1;
}
cdev_init(myChrDevCdev,&fops);
myChrDevCdev->owner = THIS_MODULE;
//myChrDevCdev->ops = &fops;/* this function inits the c_dev structure with memset 0 and then does basic konject setup and then adds fops to cdev struct*/
/* this function adds the cdev to the kernel structure so that it becomes available for the users to use it */
// Now we will create class for this device
pmyCharClass = class_create(THIS_MODULE,CLASS_NAME);
if (IS_ERR(pmyCharClass))
{
printk(KERN_ALERT "Failed to Register Class\n");
cdev_del(myChrDevCdev);
kfree(myChrDevCdev);
unregister_chrdev_region(myChrDevid, 1);
return -1;
}
printk(KERN_INFO "Class created!\n");
pmyCharDevice = device_create(pmyCharClass, NULL, MKDEV(majorNumber,0),NULL,DEVICE_NAME);
if (IS_ERR(pmyCharDevice))
{
printk(KERN_ALERT "Failed to Register Class\n");
class_unregister(pmyCharClass);
class_destroy(pmyCharClass);
cdev_del(myChrDevCdev);
kfree(myChrDevCdev);
unregister_chrdev_region(myChrDevid, 1);
return -1;
}
printk(KERN_INFO "Device created!\n");
returnValue = cdev_add(myChrDevCdev, myChrDevid, 1);
if (returnValue < 0)
{
printk(KERN_ALERT "Failed to add chdev \n");
return -1;
}
/* We now have created the class and we have aquired major numer. But we have not yet tied out created fileops with anything.
We will do that now */
//returnValue = cdev_init(cdev)
printk(KERN_INFO "Now We will create the attribute entry in sysfs\n");
/* the function used is device_create_file(struct device *, struct device_attribute*) */
device_create_file(pmyCharDevice, &dev_attr_ShowData); // The second argumnet is the structure created by the DEVICE_ATTR macro
device_create_file(pmyCharDevice, &dev_attr_Buffer);
return 0;
}
static void __exit charDriverExit()
{
device_remove_file(pmyCharDevice, &dev_attr_Buffer);
device_remove_file(pmyCharDevice, &dev_attr_ShowData);
device_destroy(pmyCharClass, MKDEV(majorNumber,0));
class_unregister(pmyCharClass);
class_destroy(pmyCharClass);
//unregister_chrdev(majorNumber,DEVICE_NAME);
cdev_del(myChrDevCdev);
unregister_chrdev_region(myChrDevid, 1);
kfree(myChrDevCdev);
printk(KERN_INFO "Unmounting module done !\n");
}
static int charDriverOpen(struct inode *inodep, struct file *filep)
{
if ((filep->f_flags & O_ACCMODE) != O_RDWR)
{
printk(KERN_ALERT "WARNING : This driver can only be opened in both read and write mode\n");
return -1;
}
printk(KERN_INFO "INFO : CHARATER DRIVER OPENED\n");
bufferMemory = kmalloc(bufferSize,GFP_KERNEL);
bufferPointer = 0;
return 0;
}
static int charDriverClose(struct inode *inodep, struct file *filep)
{
kfree(bufferMemory);
printk(KERN_INFO "INFO : CHARACTER DRIVER CLOSED\n");
return 0;
}
static ssize_t charDriverWrite(struct file *filep, const char *buffer, size_t len, loff_t *offset)
{
// Here we will only allow to write one byte of data
if (len > bufferSize)
{
printk(KERN_WARNING "Attempted to write data larger than 15 byte!\n");
return 0;
}
//bufferMemory[bufferPointer] = *buffer;
copy_from_user(bufferMemory, buffer, len);
bufferPointer += len;
return len;
}
static ssize_t charDriverRead(struct file *filep, char *buffer, size_t len, loff_t *offset)
{
if(len > bufferSize || len > bufferPointer)
{
printk(KERN_WARNING "Attempting to read more than buffer size ! Deny\n");
return 0;
}
copy_to_user(buffer, bufferMemory, len);
// buffer[0] = bufferMemory[0];
bufferPointer -= len;
return len;
}
module_init(charDriverEntry);
module_exit(charDriverExit);
module_param(bufferSize, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(bufferSize, "Buffer Memory Size [15]");
Now if I replace the while alloc_chrdev_region, cdev_init and cdev_add with just register_chrdev(), The entry in /dev pops up. I am unable to figure out what more does register_chrdev() do that the former combination does not.
Thank you
Edit : Found the issue.
it was due to using MKDEV(majorNumber, 0); Without actually storing major number in the majorNumber variable using MAJOR();
Not deleting the question as someone can find it useful

Create an Entry in /proc

I have successfully created a pseudo Hello World Entry with a LKM.
http://pointer-overloading.blogspot.co.at/2013/09/linux-creating-entry-in-proc-file.html
However in my init function, i have one integer value which i want to return in "cat /proc/example".
Any Ideas how to do this in this code?
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static int hello_proc_show(struct seq_file *m, void *v) {
seq_printf(m, "Hello proc!\n");
return 0;
}
static int hello_proc_open(struct inode *inode, struct file *file) {
return single_open(file, hello_proc_show, NULL);
}
static const struct file_operations hello_proc_fops = {
.owner = THIS_MODULE,
.open = hello_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init hello_proc_init(void) {
proc_create("hello_proc", 0, NULL, &hello_proc_fops);
return 0;
}
static void __exit hello_proc_exit(void) {
remove_proc_entry("hello_proc", NULL);
}
MODULE_LICENSE("GPL");
module_init(hello_proc_init);
module_exit(hello_proc_exit);
If procfs is not the strict requirement then make use of 'kobject' to create the sysfs entry. Refer below example and modify foo_show function as per your requirement. Otherwise, the comment from #mali will do the trick.
https://github.com/saiyamd/linux/blob/master/samples/kobject/kobject-example.c

proc_create() example for kernel module

Can someone give me proc_create() example?
Earlier they used create_proc_entry() in the kernel but now they are using proc_create().
This example will create a proc entry which enables reading access. I think you can enable other kinds of access by changing the mode argument passed to the function. I haven't passed a parent directory because there is no need to. The structure file_operations is where you setup your reading and writing callbacks.
struct proc_dir_entry *proc_file_entry;
static const struct file_operations proc_file_fops = {
.owner = THIS_MODULE,
.open = open_callback,
.read = read_callback,
};
int __init init_module(void){
proc_file_entry = proc_create("proc_file_name", 0, NULL, &proc_file_fops);
if(proc_file_entry == NULL)
return -ENOMEM;
return 0;
}
You can check this example for more details: https://www.linux.com/learn/linux-training/37985-the-kernel-newbie-corner-kernel-debugging-using-proc-qsequenceq-files-part-1
Here is a 'hello_proc' code, which makes use of the newer 'proc_create()' interface.
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static int hello_proc_show(struct seq_file *m, void *v) {
seq_printf(m, "Hello proc!\n");
return 0;
}
static int hello_proc_open(struct inode *inode, struct file *file) {
return single_open(file, hello_proc_show, NULL);
}
static const struct file_operations hello_proc_fops = {
.owner = THIS_MODULE,
.open = hello_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init hello_proc_init(void) {
proc_create("hello_proc", 0, NULL, &hello_proc_fops);
return 0;
}
static void __exit hello_proc_exit(void) {
remove_proc_entry("hello_proc", NULL);
}
MODULE_LICENSE("GPL");
module_init(hello_proc_init);
module_exit(hello_proc_exit);
This code has been taken from http://pointer-overloading.blogspot.com/2013/09/linux-creating-entry-in-proc-file.html
This reply is just an update to #Alhaad Gokhale's great answer. The hello world module, as of kernels > 5.6 looks like this:
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static int hello_proc_show(struct seq_file *m, void *v) {
seq_printf(m, "Hello proc!\n");
return 0;
}
static int hello_proc_open(struct inode *inode, struct file *file) {
return single_open(file, hello_proc_show, NULL);
}
static const struct proc_ops hello_proc_fops = {
.proc_open = hello_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
static int __init hello_proc_init(void) {
proc_create("hello_proc", 0, NULL, &hello_proc_fops);
return 0;
}
static void __exit hello_proc_exit(void) {
remove_proc_entry("hello_proc", NULL);
}
MODULE_LICENSE("GPL");
module_init(hello_proc_init);
module_exit(hello_proc_exit);
Key differences to note are:
struct proc_ops replaces struct file_operations.
member name changes from .open, .read to .proc_open, .proc_read...
Need to remove the .owner line.
Further information:
This patch
This more detailed SO answer on changes.

Resources