内核版本:4.14.0,基于设备树
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/uaccess.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#define DEVICE_CNT 1 /* Number of device id */
#define DEVICE_NAME "xxx" /* Device name */
#define COMPAT_PROPT "xxx,xxx" /* Compatible property of the device matched with this driver. */
/* Device information structure. */
struct dev_info {
dev_t devid; /* Device ID */
struct cdev cdev; /* cdev */
struct class *class;
struct device *device;
int major; /* Major device id */
int minor; /* Minor device id */
wait_queue_head_t WaitQueue; /* Wait queue head */
struct fasync_struct *async_queue;
};
static struct dev_info device_info;
/*
* @description : Open the device.
* @param – inode : The inode passed to the driver.
* @param - filp : Device file, the file structure has a member variable called 'private_data',
* which is normally pointed to the device structure when it is opened.
* @return : 0: Successful; Others: Failed.
*/
static int device_open(struct inode *inode, struct file *filp)
{
filp->private_data = &device_info; /* Set private data.*/
return 0;
}
/*
* @description : Read from the device.
* @param - filp : The device file (file descriptor) to open.
* @param - buf : The data buffer returns to the user space.
* @param - cnt : The length of the data to be read.
* @param - offt : Offset relative to the first address of the file.
* @return : The number of bytes read, negative means the read failed.
*/
static ssize_t device_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
if (filp->f_flags & O_NONBLOCK) //Read by nonblock mode.
{
}
else //Read by block mode.
{
/* Wait the wait queue is waked up */
ret = wait_event_interruptible(device_info.r_wait, xxx);
if (ret)
return ret;
}
ret = copy_to_user(buf, &xxx, xxx);
return xxx-ret ;
}
/*
* @description : Write to the device
* @param - filp : Device file, indicating the opened file descriptor.
* @param - buf : The data to write to the device.
* @param - cnt : The length of the data to be written.
* @param - offt : Offset relative to the first address of the file.
* @return : The number of bytes written, negative means the write failed.
*/
static ssize_t device_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
ret = copy_from_user(xxx, buf, cnt);
if (ret < 0)
{
printk(KERN_ERR "kernel get data failed!\n");
return -EFAULT;
}
return cnt;
}
/*
* @description : The ioctl function.
* @param - filp : Device file, indicating the opened file descriptor.
* @param - cmd : Command form app.
* @param - arg : Parameter from app.
* @return : 0: Successful; Others: Failed.
*/
static long device_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
return 0 ;
}
/*
* @description : Handle the block access.
* @param - filp : Device file, indicating the opened file descriptor.
* @param - wait : Wait table from app.
* @return : status of the device or resource.
*/
static unsigned int device_poll(struct file *filp, struct poll_table_struct *wait)
{
/* Add waiting queue header that may cause device state changes to the poll_table */
poll_wait(filp, &device_info.WaitQueue, wait);
/* Return status of the device or resource according to fact */
if (xxx)
return xxx;
else
return 0;
}
/*
* @description : Handle the async notice.Asynchronously flush the pending data
* in the buffer to the disk.
* @param - fd : File descriptor.
* @param - filp : Device file, indicating the opened file descriptor.
* @param - on : Mode.
* @return : Negative means the operation failed.
*/
static int device_fasync(int fd, struct file *filp, int on)
{
/* Set up the fasync queue */
return fasync_helper(fd, filp, on, &device_info.async_queue);
}
/*
* @description : Close/Release the device.
* @param - filp : The device file (file descriptor) to close.
* @return : Negative means the operation failed.
*/
static int device_release(struct inode *inode, struct file *filp)
{
/* Release the fasync_struct structure */
return device_fasync(-1, filp, 0);
}
/* The device operation function structure. */
static const struct file_operations device_fops = {
.owner = THIS_MODULE,
.open = device_open,
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_unlocked_ioctl,
.poll = device_poll,
/*
* When an application changes the fasync flag by calling "fcntl(fd, F_SETFL, flags | FASYNC)",
* fasync function will be executed.
*/
.fasync = device_fasync,
.release = device_release,
};
/*
* @description : Initialize the device.
* @param -nd: Device node.
* @return : 0: Successful; Others: Failed.
*/
static int device_init(struct device_node *nd)
{
return 0;
}
/*
* @description : Probe function of the platform, it will be executed when the
* platform driver and platform device matching successfully.
* @param -pdev : Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int device_probe(struct platform_device *pdev)
{
int ret;
printk(KERN_INFO "%s: driver and device has matched!\n", DEVICE_NAME);
/* Device init */
ret = device_init(pdev->dev.of_node);
if (ret)
return ret;
/* Creat device id. */
if (device_info.major)
{
device_info.devid = MKDEV(device_info.major, 0);
ret = register_chrdev_region(device_info.devid, DEVICE_CNT, DEVICE_NAME);
if (ret)
goto out1;
}
else
{
ret = alloc_chrdev_region(&device_info.devid, 0, DEVICE_CNT, DEVICE_NAME);
if (ret)
goto out1;
device_info.major = MAJOR(device_info.devid);
device_info.minor = MINOR(device_info.devid);
}
printk("%s: device id: major=%d, minor=%d\r\n", DEVICE_NAME, device_info.major, device_info.minor);
/* Initialize char device. */
device_info.cdev.owner = THIS_MODULE;
cdev_init(&device_info.cdev, &device_fops);
/* Add a char device. */
ret = cdev_add(&device_info.cdev, device_info.devid, DEVICE_CNT);
if (ret)
goto out2;
/* Creat class. */
device_info.class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(device_info.class))
{
ret = PTR_ERR(device_info.class);
goto out3;
}
/* Creat device (node) file. */
device_info.device = device_create(device_info.class, &pdev->dev, device_info.devid, NULL, DEVICE_NAME);
if (IS_ERR(device_info.device))
{
ret = PTR_ERR(device_info.device);
goto out4;
}
return 0;
out4:
class_destroy(device_info.class);
out3:
cdev_del(&device_info.cdev);
out2:
unregister_chrdev_region(device_info.devid, DEVICE_CNT);
out1:
/* Some code to release resources */
return ret;
}
/*
* @description : Release some resources. This function will be executed when the platform
* driver module is unloaded.
* @param -dev : Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int device_remove(struct platform_device *pdev)
{
/* Logoff device (node) */
device_destroy(device_info.class, device_info.devid);
/* Logoff class */
class_destroy(device_info.class);
/* Delete char device */
cdev_del(&device_info.cdev);
/* Logoff device id */
unregister_chrdev_region(device_info.devid, DEVICE_CNT);
/* Some code to release resources */
gpio_free(device_info.gpio_id);
printk(KERN_INFO "%s: platform driver removed!\n", DEVICE_NAME);
return 0;
}
/* Match table */
static const struct of_device_id device_of_match[] = {
{.compatible = COMPAT_PROPT},
{/* Sentinel */}
};
/*
* Declare device matching table. Note that this macro is generally used to dynamically
* load and unload drivers for hot-pluggable devices such as USB devices.
*/
MODULE_DEVICE_TABLE(of, device_of_match);
/* Platform driver struct */
static struct platform_driver device_driver = {
.driver = {
.name = DEVICE_NAME, //Drive name, used to match device who has the same name.
.of_match_table = device_of_match, //Used to match the device tree who has the same compatible property.
},
.probe = device_probe, //probe function
.remove = device_remove, //remove function
};
/*
* Register or unregister platform driver,
* and Register the entry and exit functions of the Module.
*/
module_platform_driver(device_driver);
/*
* Author, driver information and LICENSE.
*/
MODULE_AUTHOR("蒋楼丶");
MODULE_DESCRIPTION(DEVICE_NAME" Platform Driver");
MODULE_LICENSE("GPL");