LINUX KERNEL driver hangs/freeze after handling mapped register - linux-kernel

I'm completely new developing in LINUX kernel, and I'm having some problems in a new LINUX driver I'm developing.
After I map NXP PWM registers using ioremap()/ioremap_nocache() and then I try to write to the register mappend my system hags/freeze.
Could you please help me understanding what it is happening?
My Driver is this:
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h> // for threads
#include <linux/fs.h>
#include <linux/sched.h> // for task_struct
#include <linux/delay.h> // for ndelay
#include <linux/uaccess.h> // Required for the copy to user function
#include <asm/io.h> // for ioremap()
#include <linux/interrupt.h>
#include <linux/gpio.h>
#define MX3_PWMCR_PRESCALER(x) ((((x) - 1) & 0xFFF) << 4)
#define DEVICE_NAME "pwm_cus_drv"
#define CLASS_NAME "pwm_custom_driver"
static volatile void __iomem *mmio_pwm1_base = NULL;
static volatile void __iomem *mmio_pwm2_base = NULL;
static int majorNumber;
static struct class* vfd_char_dev_class = NULL;
static struct device* vfd_char_dev = NULL;
static struct device_driver vfd_driver;
static int dev_open(struct inode *inodep, struct file *file_ptr);
static int dev_release(struct inode *inodep, struct file *file_ptr);
static ssize_t dev_read(struct file *file_ptr, char *buffer, size_t len, loff_t *offset);
static ssize_t dev_write(struct file *file_ptr, char *buffer, size_t len, loff_t *offset);
static long dev_ioctl(struct file *file_ptr, unsigned int cmd, unsigned long arg);
static irqreturn_t pwm_imx_futaba_isr(int irq, void *dev_id);
static bool Initialize_PWM_Signals(void);
static void pwm_init(void);
/**
* ISR used to attend PWM rising edge interrupt activation.
*/
irqreturn_t pwm_imx_futaba_isr(int irq, void *dev_id)
{
if(NULL != mmio_pwm1_base)
{
writel(0x00000078, mmio_pwm1_base + 0x04);
gpio_set_value(47, 1);
gpio_set_value(47, 0);
}
return IRQ_HANDLED;
}
/**
*
*/
bool Initialize_PWM_Signals(void)
{
u32 cs_pin_dir_value = 0;
u32 cs_pin_out_value = 0;
u32 duty_cycles = 0;
u32 period_cycles = 0;
u32 cr_1 = 0;
u32 cr_2 = 0;
pwm_init();
period_cycles = ((24000000)/(4000)) - 2; /* 4 KHz */
duty_cycles = period_cycles / 2; /* duty = 50% */
printk(KERN_NOTICE "PWM data. PERIOD[%d] DUTY[%d]\n", period_cycles, duty_cycles);
cr_1 = MX3_PWMCR_PRESCALER(1) | (1 << 24) | (1 << 23) | (2 << 16);
cr_2 = MX3_PWMCR_PRESCALER(1) | (1 << 24) | (1 << 23) | (2 << 16);
printk(KERN_NOTICE "Disabling IMX6UL PWMs \n");
/*******************************/
/* AFTER THIS, THE KERNEL HANGS*/
/*******************************/
writel(cr_1, mmio_pwm1_base + 0x00);
writel(cr_2, mmio_pwm2_base + 0x00);
printk(KERN_NOTICE "PWMs disabled\n");
if (1)
{
/* Configure IMX6UL PWM1 */
printk(KERN_NOTICE " Configuring PWM1 \n");
writel(duty_cycles, mmio_pwm1_base + 0x0C);
writel(period_cycles, mmio_pwm1_base + 0x10);
/* Configure IMX6UL PWM2 */
printk(KERN_NOTICE " Configuring PWM2 \n");
writel(duty_cycles, mmio_pwm2_base + 0x0C);
writel(period_cycles, mmio_pwm2_base + 0x10);
cr_1 |= (1 << 0);
cr_2 |= (1 << 0);
printk(KERN_NOTICE "Enabling IRQs !!\n");
writel(0x00000002, mmio_pwm1_base + 0x08);
/* Enabling IMX6UL PWMs */
printk(KERN_NOTICE " Enabling PWMs \n");
writel(cr_1, mmio_pwm1_base + 0x00);
writel(cr_2, mmio_pwm2_base + 0x00);
}
return 0;
}
/**
*
*/
int dev_open(struct inode *inodep, struct file *file_ptr)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
Initialize_PWM_Signals();
printk(KERN_NOTICE "[%s] Driver initialized \n", __func__);
}
/**
*
*/
int dev_release(struct inode *inodep, struct file *file_ptr)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
}
/**
*
*/
ssize_t dev_read(struct file *file_ptr, char *buffer, size_t len, loff_t *offset)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
}
/**
*
*/
ssize_t dev_write(struct file *file_ptr, char *buffer, size_t len, loff_t *offset)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
}
/**
*
*/
long dev_ioctl(struct file *file_ptr, unsigned int cmd, unsigned long arg)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
}
/**
*
*/
void pwm_init(void)
{
printk(KERN_ALERT "[%s]\n", __func__);
if(NULL != request_mem_region(0x2080000, 0x4000, DEVICE_NAME))
{
mmio_pwm1_base = ioremap_nocache(0x2080000, 0x4000);
if(IS_ERR(mmio_pwm1_base))
{
printk(KERN_NOTICE "Failed to map memory 1\n");
}
}
else
{
printk(KERN_NOTICE "Failed to map memory 2\n");
}
if(NULL != request_mem_region(0x2084000, 0x4000, DEVICE_NAME))
{
mmio_pwm2_base = ioremap_nocache(0x2084000, 0x4000);
if(IS_ERR(mmio_pwm2_base))
{
printk(KERN_NOTICE "Failed to map memory 3\n");
}
}
else
{
printk(KERN_NOTICE "Failed to map memory 4\n");
}
printk(KERN_NOTICE "PWMs memory mapped \n");
}
static const struct file_operations fops =
{
.owner = THIS_MODULE,
.open = dev_open,
.read = dev_read,
.write = dev_write,
.release = dev_release,
.unlocked_ioctl = dev_ioctl,
.compat_ioctl = dev_ioctl,
};
struct bus_type futaba_bus_type =
{
.name = DEVICE_NAME,
};
static int pwm_driver_init(void)
{
unsigned irqflags = 0;
unsigned ret = 0;
const char *dev_name = "pwm1_irq";
u32 pwm_irq = 25;
majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
if (majorNumber < 0)
{
printk(KERN_NOTICE "EBBChar failed to register a major number\n");
return majorNumber;
}
vfd_char_dev_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(vfd_char_dev_class))
{
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_NOTICE "Failed to register device class\n");
return PTR_ERR(vfd_char_dev_class);
}
vfd_char_dev = device_create(vfd_char_dev_class, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(vfd_char_dev))
{
class_destroy(vfd_char_dev_class);
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to create the device\n");
return PTR_ERR(vfd_char_dev);
}
ret = request_irq(pwm_irq, pwm_imx_futaba_isr, irqflags, dev_name, DEVICE_NAME);
if (0 != ret)
{
printk(KERN_NOTICE "can't get irq: %d\n", ret);
}
return 0;
}
static void pwm_driver_exit(void)
{
device_destroy(vfd_char_dev_class, MKDEV(majorNumber, 0));
class_unregister(vfd_char_dev_class);
class_destroy(vfd_char_dev_class);
unregister_chrdev(majorNumber, DEVICE_NAME);
iounmap(mmio_pwm1_base);
iounmap(mmio_pwm2_base);
}
module_init(pwm_driver_init);
module_exit(pwm_driver_exit);
MODULE_AUTHOR("New Drivers developer");
MODULE_DESCRIPTION(" PWM Handler ");
MODULE_LICENSE("GPL");

Thanks for all your answers. I solved this issue after using some NXP recommendations. At the end the problem was caused due to the IPG/PER clocks were not enabled. According NXP, clocks shall be enabled first before starting to modify registers associated with the module(in my case for PWM).

Typical reason of hang at register access is that hardware module owning the register is either powered down, or not clocked.
Per imx6ul device tree from mainline kernel (arch/arm/boot/dts/imx6ul.dtsi), there are clocks to enable:
pwm1: pwm#2080000 {
compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
reg = <0x02080000 0x4000>;
interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_PWM1>,
<&clks IMX6UL_CLK_PWM1>;
clock-names = "ipg", "per";
#pwm-cells = <2>;
status = "disabled";
};
By the way, driver for this module is available, drivers/pwm/pwm-imx.c

Related

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

'scull' device from ldd3 is not shown under /dev/

Following the book ldd3 (- Linux Device Drivers 3 ed.) and using, also, source code files available here (as suggested by another stackoverflow's user here), I am able to compile the scull device module and load it on my Linux-based OS. For more details:
$ insmod scull.ko
$ lsmod
Module Size Used by
scull 20480 0
$ dmesg | tail -...
[...] scullsingle registered at f300008
[...] sculluid registered at f300009
[...] scullwuid registered at f30000a
[...] sullpriv registered at f30000b
However, in the /dev/ directory, there is NOT the scull0 device entry as I was expecting. So I cannot use it.
In addition, when executing:
$ rmmod scull
The system displays this error:
rmmod: ERROR: ../libkmod/libkmod.c:514 lookup_builtin_file() could not open builtin file '/lib/modules/4.9.0/modules.builtin.bin'
The funny phenomena is that now, executing lsmod, the device seems disappeared and before, as you can see from the snipped, it was present.
MORE INFO (may be they can be useful in some way):
using a 4.9.0 kernel version
currently, I am cross-compiling the module
gcc used: aarch64-Linux-gnu-gcc
ARCH=arm64
snipped code of scull:
CODE:
/*
* main.c -- the bare scull char module
*
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <asm/uaccess.h> /* copy_*_user */
#include "scull.h" /* local definitions */
/*
* Our parameters which can be set at load time.
*/
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_nr_devs = SCULL_NR_DEVS; /* number of bare scull devices */
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;
module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);
MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");
struct scull_dev *scull_devices; /* allocated in scull_init_module */
/*
* Empty out the scull device; must be called with the device
* semaphore held.
*/
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset; /* "dev" is not-null */
int i;
for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
if (dptr->data) {
for (i = 0; i < qset; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
#ifdef SCULL_DEBUG /* use proc only if debugging */
/*
* The proc filesystem: function to read and entry
*/
int scull_read_procmem(struct seq_file *s, void *v)
{
int i, j;
int limit = s->size - 80; /* Don't print more than this */
for (i = 0; i < scull_nr_devs && s->count <= limit; i++) {
struct scull_dev *d = &scull_devices[i];
struct scull_qset *qs = d->data;
if (down_interruptible(&d->sem))
return -ERESTARTSYS;
seq_printf(s,"\nDevice %i: qset %i, q %i, sz %li\n",
i, d->qset, d->quantum, d->size);
for (; qs && s->count <= limit; qs = qs->next) { /* scan the list */
seq_printf(s, " item at %p, qset at %p\n",
qs, qs->data);
if (qs->data && !qs->next) /* dump only the last item */
for (j = 0; j < d->qset; j++) {
if (qs->data[j])
seq_printf(s, " % 4i: %8p\n",
j, qs->data[j]);
}
}
up(&scull_devices[i].sem);
}
return 0;
}
/*
* Here are our sequence iteration methods. Our "position" is
* simply the device number.
*/
static void *scull_seq_start(struct seq_file *s, loff_t *pos)
{
if (*pos >= scull_nr_devs)
return NULL; /* No more to read */
return scull_devices + *pos;
}
static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
if (*pos >= scull_nr_devs)
return NULL;
return scull_devices + *pos;
}
static void scull_seq_stop(struct seq_file *s, void *v)
{
/* Actually, there's nothing to do here */
}
static int scull_seq_show(struct seq_file *s, void *v)
{
struct scull_dev *dev = (struct scull_dev *) v;
struct scull_qset *d;
int i;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",
(int) (dev - scull_devices), dev->qset,
dev->quantum, dev->size);
for (d = dev->data; d; d = d->next) { /* scan the list */
seq_printf(s, " item at %p, qset at %p\n", d, d->data);
if (d->data && !d->next) /* dump only the last item */
for (i = 0; i < dev->qset; i++) {
if (d->data[i])
seq_printf(s, " % 4i: %8p\n",
i, d->data[i]);
}
}
up(&dev->sem);
return 0;
}
/*
* Tie the sequence operators up.
*/
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show
};
/*
* Now to implement the /proc files we need only make an open
* method which sets up the sequence operators.
*/
static int scullmem_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, scull_read_procmem, NULL);
}
static int scullseq_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &scull_seq_ops);
}
/*
* Create a set of file operations for our proc files.
*/
static struct file_operations scullmem_proc_ops = {
.owner = THIS_MODULE,
.open = scullmem_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
static struct file_operations scullseq_proc_ops = {
.owner = THIS_MODULE,
.open = scullseq_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
/*
* Actually create (and remove) the /proc file(s).
*/
static void scull_create_proc(void)
{
proc_create_data("scullmem", 0 /* default mode */,
NULL /* parent dir */, &scullmem_proc_ops,
NULL /* client data */);
proc_create("scullseq", 0, NULL, &scullseq_proc_ops);
}
static void scull_remove_proc(void)
{
/* no problem if it was not registered */
remove_proc_entry("scullmem", NULL /* parent dir */);
remove_proc_entry("scullseq", NULL);
}
#endif /* SCULL_DEBUG */
/*
* Open and close
*/
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
/* now trim to 0 the length of the device if open was write-only */
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
scull_trim(dev); /* ignore errors */
up(&dev->sem);
}
return 0; /* success */
}
int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* Follow the list
*/
struct scull_qset *scull_follow(struct scull_dev *dev, int n)
{
struct scull_qset *qs = dev->data;
/* Allocate first qset explicitly if need be */
if (! qs) {
qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs == NULL)
return NULL; /* Never mind */
memset(qs, 0, sizeof(struct scull_qset));
}
/* Then follow the list */
while (n--) {
if (!qs->next) {
qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs->next == NULL)
return NULL; /* Never mind */
memset(qs->next, 0, sizeof(struct scull_qset));
}
qs = qs->next;
continue;
}
return qs;
}
/*
* Data management: read and write
*/
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr; /* the first listitem */
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset; /* how many bytes in the listitem */
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
if (*f_pos >= dev->size)
goto out;
if (*f_pos + count > dev->size)
count = dev->size - *f_pos;
/* find listitem, qset index, and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position (defined elsewhere) */
dptr = scull_follow(dev, item);
if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
goto out; /* don't fill holes */
/* read only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* find listitem, qset index and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position */
dptr = scull_follow(dev, item);
if (dptr == NULL)
goto out;
if (!dptr->data) {
dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
if (!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if (!dptr->data[s_pos])
goto out;
}
/* write only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
/* update the size */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}
/*
* The ioctl() implementation
*/
long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err) return -EFAULT;
switch(cmd) {
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
return scull_quantum;
case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user *)arg);
if (retval == 0)
retval = __put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
scull_quantum = arg;
return tmp;
case SCULL_IOCSQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_qset, (int __user *)arg);
break;
case SCULL_IOCTQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_qset = arg;
break;
case SCULL_IOCGQSET:
retval = __put_user(scull_qset, (int __user *)arg);
break;
case SCULL_IOCQQSET:
return scull_qset;
case SCULL_IOCXQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
retval = __get_user(scull_qset, (int __user *)arg);
if (retval == 0)
retval = put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
scull_qset = arg;
return tmp;
/*
* The following two change the buffer size for scullpipe.
* The scullpipe device uses this same ioctl method, just to
* write less code. Actually, it's the same driver, isn't it?
*/
case SCULL_P_IOCTSIZE:
scull_p_buffer = arg;
break;
case SCULL_P_IOCQSIZE:
return scull_p_buffer;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
}
/*
* The "extended" operations -- only seek
*/
loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
struct scull_dev *dev = filp->private_data;
loff_t newpos;
switch(whence) {
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = dev->size + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0) return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.unlocked_ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
/*
* Finally, the module stuff
*/
/*
* The cleanup function is used to handle initialization failures as well.
* Thefore, it must be careful to work correctly even if some of the items
* have not been initialized
*/
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major, scull_minor);
/* Get rid of our char dev entries */
if (scull_devices) {
for (i = 0; i < scull_nr_devs; i++) {
scull_trim(scull_devices + i);
cdev_del(&scull_devices[i].cdev);
}
kfree(scull_devices);
}
#ifdef SCULL_DEBUG /* use proc only if debugging */
scull_remove_proc();
#endif
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, scull_nr_devs);
/* and call the cleanup functions for friend devices */
scull_p_cleanup();
scull_access_cleanup();
}
/*
* Set up the char_dev structure for this device.
*/
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
int scull_init_module(void)
{
int result, i;
dev_t dev = 0;
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
/* Initialize each device. */
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
sema_init(&scull_devices[i].sem, 1);
scull_setup_cdev(&scull_devices[i], i);
}
/* At this point call the init function for any friend device */
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
dev += scull_p_init(dev);
dev += scull_access_init(dev);
#ifdef SCULL_DEBUG /* only when debugging */
scull_create_proc();
#endif
return 0; /* succeed */
fail:
scull_cleanup_module();
return result;
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);
SOLUTION:
Of course, I was doing the things in the wrong way: in order to load CORRECTLY the scull device module, in the book's source code and, also, in the other link of the question, there is load_scull script that does everything for you.
# source scull_load
# lsmod
Module Size Used by
scull 20480 0
# ls -l /dev/ | grep scull
lrwxrwxrwx 1 root root 6 Dec 19 15:00 scull -> scull0
crw-rw-r-- 1 root staff 243, 0 Dec 19 15:00 scull0
crw-rw-r-- 1 root staff 243, 1 Dec 19 15:00 scull1
crw-rw-r-- 1 root staff 243, 2 Dec 19 15:00 scull2
crw-rw-r-- 1 root staff 243, 3 Dec 19 15:00 scull3
lrwxrwxrwx 1 root root 10 Dec 19 15:00 scullpipe -> scullpipe0
crw-rw-r-- 1 root staff 243, 4 Dec 19 15:00 scullpipe0
crw-rw-r-- 1 root staff 243, 5 Dec 19 15:00 scullpipe1
crw-rw-r-- 1 root staff 243, 6 Dec 19 15:00 scullpipe2
crw-rw-r-- 1 root staff 243, 7 Dec 19 15:00 scullpipe3
crw-rw-r-- 1 root staff 243, 11 Dec 19 15:00 scullpriv
crw-rw-r-- 1 root staff 243, 8 Dec 19 15:00 scullsingle
crw-rw-r-- 1 root staff 243, 9 Dec 19 15:00 sculluid
crw-rw-r-- 1 root staff 243, 10 Dec 19 15:00 scullwuid

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

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;
}

Kernel module still in use afther executing `$ echo 3 > /dev/tlc5947` (reference counting error)

Hellow I'm tring to learn writing kernel modules and device drivers. As a begginig I decided to start by making a driver for my tlc5947 led matrix. From simple kernel module with parameters I moved to char device driver but I'm having hard time with resource reference counting. Whole day I have been playing the restart game with my PI and still can't figure from where the bug is comming ...
This is the module header:
#include <linux/gpio.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#ifndef _TLC5947_LED_MATRIX_H
#define _TLC5947_LED_MATRIX_H
#define TLC5947_GPIOS 3
#define TLC5947_LEDS 24
#define TLC5947_MAXPWM 4096
#define GPIO_HIGH 1
#define GPIO_LOW 0
#define TLC5947_NAME "tlc5947"
#define CONST_PARAM S_IRUSR | S_IRGRP | S_IROTH
ushort tlc5947_chips = 255;
ushort tlc5947_data = 255;
ushort tlc5947_clock = 255;
ushort tlc5947_latch = 255;
static struct gpio tlc5947[TLC5947_GPIOS] = {
{.gpio = -1, .flags = GPIOF_OUT_INIT_HIGH, .label = "TLC5947 DATA"},
{.gpio = -1, .flags = GPIOF_OUT_INIT_HIGH, .label = "TLC5947 CLOCK"},
{.gpio = -1, .flags = GPIOF_OUT_INIT_HIGH, .label = "TLC5947 LATCH"},
};
static dev_t tlc5947_numbers;
static int tlc5947_major_number;
static int tlc5947_first_minor = 0;
static unsigned int tlc5947_minor_count = 1;
static struct file_operations tlc5947_file_operations;
static struct cdev* tlc5947_cdev;
static int tlc5947_file_opened = 0;
static int tlc5947_file_open(struct inode* inode, struct file* file);
static ssize_t tlc5947_file_write(struct file* file, const char __user* buffer, size_t buffer_length, loff_t* offset);
static int tlc5947_file_close(struct inode* inode, struct file* file);
static int is_tlc5947_param_set(ushort* tlc5947_param, const char* tlc5947_param_name);
static int __init tlc5947_init(void);
static void __exit tlc5947_exit(void);
#endif
The source:
#include "tlc5947_led_matrix.h"
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/gpio.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <asm/errno.h>
module_param(tlc5947_chips, ushort, CONST_PARAM);
MODULE_PARM_DESC(tlc5967_chips, "Number of tlc5967 chips that are chain connected.");
module_param(tlc5947_data, ushort, CONST_PARAM);
MODULE_PARM_DESC(tlc5967_data, "Number of gpio pin on wich DATA signal is connected (BCM Enum).");
module_param(tlc5947_clock, ushort, CONST_PARAM);
MODULE_PARM_DESC(tlc5967_clock, "Number of gpio pin on wich CLOCK signal is connected (BCM Enum).");
module_param(tlc5947_latch, ushort, CONST_PARAM);
MODULE_PARM_DESC(tlc5967_latch, "Number of gpio pin on wich LATCH signal is connected (BCM Enum).");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ivo Stratev");
MODULE_DESCRIPTION("Basic Linux Kernel module using GPIOs to drive tlc5947");
MODULE_SUPPORTED_DEVICE(TLC5947_NAME);
static int tlc5947_file_open(struct inode* inode, struct file* file) {
if(tlc5947_file_opened == 1) {
printk(KERN_ERR "Fail to open file /dev/%s\n", TLC5947_NAME);
return -EBUSY;
}
tlc5947_file_opened = 1;
return 0;
}
static ssize_t tlc5947_file_write(struct file* file, const char __user* buffer, size_t buffer_length, loff_t* offset) {
char data[10];
unsigned int i;
unsigned long copy_ret = copy_from_user(data, buffer, buffer_length);
if(copy_ret) {
printk(KERN_ERR "Error while copying data\nCalling copy_from_user reurned%ld\n", copy_ret);
return copy_ret;
}
data[0] -= '0';
i = tlc5947_chips * TLC5947_LEDS - 1;
gpio_set_value(tlc5947_latch, GPIO_LOW);
while(1) {
unsigned char bit = 11;
while(1) {
gpio_set_value(tlc5947_clock, GPIO_LOW);
gpio_set_value(tlc5947_data, (data[0] & (1 << bit)) ? GPIO_HIGH : GPIO_LOW);
gpio_set_value(tlc5947_clock, GPIO_HIGH);
if(bit == 0) {
break;
}
bit--;
}
if(i == 0) {
break;
}
i--;
}
gpio_set_value(tlc5947_clock, GPIO_LOW);
gpio_set_value(tlc5947_latch, GPIO_HIGH);
gpio_set_value(tlc5947_latch, GPIO_LOW);
return copy_ret;
}
static int tlc5947_file_close(struct inode* inode, struct file* file) {
tlc5947_file_opened = 0;
return 0;
}
static int is_tlc5947_param_set(ushort* tlc5947_param, const char* tlc5947_param_name) {
int is_255 = (*tlc5947_param) == 255;
if(is_255) {
printk(KERN_ERR "Parameter %s value not setted when loading the module\n", tlc5947_param_name);
}
return is_255;
}
static int __init tlc5947_init(void) {
int init_ret = 0;
init_ret |= is_tlc5947_param_set(&tlc5947_chips, "tlc5947_chips");
init_ret |= is_tlc5947_param_set(&tlc5947_data, "tlc5947_data");
init_ret |= is_tlc5947_param_set(&tlc5947_clock, "tlc5947_clock");
init_ret |= is_tlc5947_param_set(&tlc5947_latch, "tlc5947_latch");
if(init_ret) {
return -EINVAL;
}
tlc5947[0].gpio = tlc5947_data;
tlc5947[1].gpio = tlc5947_clock;
tlc5947[2].gpio = tlc5947_latch;
init_ret = gpio_request_array(tlc5947, TLC5947_GPIOS);
if(init_ret) {
printk(KERN_ERR "Unable to request GPIOs!\nCalling gpio_request_array returned %d\n", init_ret);
return init_ret;
}
init_ret = alloc_chrdev_region(&tlc5947_numbers, tlc5947_first_minor, tlc5947_minor_count, TLC5947_NAME);
if(init_ret) {
printk(KERN_ERR "Could not allocate device numbers\nCalling alloc_chrdev_region returned %d\n", init_ret);
return init_ret;
}
tlc5947_major_number = MAJOR(tlc5947_numbers);
printk(KERN_INFO "Device major number is %d. Use $ sudo make device\n", tlc5947_major_number);
tlc5947_cdev = cdev_alloc();
tlc5947_cdev->owner = THIS_MODULE;
tlc5947_file_operations.owner = THIS_MODULE;
tlc5947_file_operations.open = tlc5947_file_open;
tlc5947_file_operations.release = tlc5947_file_close;
tlc5947_file_operations.write = tlc5947_file_write;
tlc5947_cdev->ops = &tlc5947_file_operations;
init_ret = cdev_add(tlc5947_cdev, tlc5947_numbers, tlc5947_minor_count);
if(init_ret) {
printk(KERN_ERR "Failed to add device numbers to struct cdev\nCalling cdev_add returned %d\n", init_ret);
return init_ret;
}
return 0;
}
static void __exit tlc5947_exit(void) {
cdev_del(tlc5947_cdev);
unregister_chrdev_region(tlc5947_numbers, tlc5947_minor_count);
gpio_free_array(tlc5947, TLC5947_GPIOS);
}
module_init(tlc5947_init);
module_exit(tlc5947_exit);
And the makefile:
target = tlc5947
driver = tlc5947_led_matrix
obj-m := $(target).o
$(target)-objs := $(driver).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
rm -f /dev/$(target)
activate:
insmod $(target).ko tlc5947_chips=8 tlc5947_data=17 tlc5947_clock=27 tlc5947_latch=22
remove:
rmmod -rf $(target)
modprobe -rf $(target)
device:
mknod /dev/$(target) c 243 0
chmod 777 /dev/$(target)

Linux kernel copy_to_user to user space display different result

There some bugs reading from the user space with this application. Is my copy_to_user dont correctly?
The following is the readout from terminal:
Press r to read from device or w to write the device r
0x-1075024108 0x15123440 0xe70401 0xe6f8dc 0xe73524
0x0 0x15037588 0xbfec6f14 0xe57612 0xbfec6f34
0x15037140 0x2 0xe57334 0xc6d690 0xd696910
0x-1075024080 0x15071734 0xc737c9 0x804835a 0x2
The following is the code from apps layer:
read(fd, read_buf, sizeof(read_buf));
for(i=0;i<=(BUFF_SIZE / sizeof(int));i+=5)
printf(" 0x%x 0x%x 0x%x 0x%x 0x%x \n",
read_buf[i],read_buf[i+1],read_buf[i+2],
read_buf[i+3],read_buf[i+4]);
break;
and the following is my driver code:
#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#ifdef MODVERSIONS
# include <linux/modversions.h>
#endif
#include <asm/io.h>
#include <asm/uaccess.h> // required for copy_from and copy_to user
/* character device structures */
static dev_t mmap_dev;
static struct cdev mmap_cdev;
/* methods of the character device */
static int mmap_open(struct inode *inode, struct file *filp);
static int mmap_release(struct inode *inode, struct file *filp);
/* the file operations, i.e. all character device methods */
static struct file_operations mmap_fops = {
.open = mmap_open,
.release= mmap_release,
.owner = THIS_MODULE,
};
static int *vmalloc_area;
#define NPAGES 1//16
#define BUFF_SIZE 64 // bytes
/* character device open method */
static int mmap_open(struct inode *inode, struct file *filp)
{
return 0;
}
/* character device last close method */
static int mmap_release(struct inode *inode, struct file *filp)
{
return 0;
}
ssize_t read(struct file *filp, int *buff, size_t count, loff_t *offp)
{
unsigned long bytes_left;
printk("Inside read \n");
bytes_left = copy_to_user(buff, vmalloc_area , count);
if(bytes_left<0)
bytes_left = -EFAULT;
return bytes_left;
}
/* module initialization - called at module load time */
static int __init membuff_init(void)
{
int ret = 0, i =0;
printk(KERN_ERR "#membuff_init\n");
/* allocate a memory area with vmalloc. */
if ((vmalloc_area = vmalloc(BUFF_SIZE)) == NULL) {
ret = -ENOMEM;
goto out_vfree;
}
/* get the major number of the character device */
if( (ret = alloc_chrdev_region(&mmap_dev, 0, 1, "mmap")) < 0) {
printk(KERN_ERR "#membuff_init could not allocate major number for mmap\n");
goto out_vfree;
}
printk(KERN_ERR "#membuff_init Major number for mmap: %d\n",MAJOR(mmap_dev));
/* initialize the device structure and register the device with the kernel */
cdev_init(&mmap_cdev, &mmap_fops);
if ((ret = cdev_add(&mmap_cdev, mmap_dev, 1)) < 0) {
printk(KERN_ERR "#membuff_init could not allocate chrdev for mmap\n");
goto out_unalloc_region;
}
for (i = 0; i < (BUFF_SIZE / sizeof(int)); i +=1) {
vmalloc_area[i] = i;
printk(KERN_ERR "#membuff_init: %d\n",vmalloc_area[i]);
}
return ret;
out_unalloc_region:
unregister_chrdev_region(mmap_dev, 1);
out_vfree:
if(vmalloc_area)
vfree(vmalloc_area);
return ret;
}
/* module unload */
static void __exit mmap_exit(void)
{
if(vmalloc_area)
vfree(vmalloc_area);
/* remove the character deivce */
cdev_del(&mmap_cdev);
unregister_chrdev_region(mmap_dev, 1);
printk(KERN_ERR "#mmap_exit\n");
}
module_init(membuff_init);
module_exit(mmap_exit);
MODULE_DESCRIPTION("trying out copy_to_user");
MODULE_LICENSE("Dual BSD/GPL");

Resources