一、设备号
定义:/fs/char_dev.c
#include <linux/fs.h>
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
/**
* unregister_chrdev_region() - return a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)
{
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
}
二、字符设备
定义:/fs/char_dev.c
#include <linux/cdev.h>
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p->dev = dev;
p->count = count;
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
/**
* cdev_del() - remove a cdev from the system
* @p: the cdev structure to be removed
*
* cdev_del() removes @p from the system, possibly freeing the structure
* itself.
*/
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
三、设备节点
class
#include <linux/device.h>
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
extern void class_destroy(struct class *cls);
device:drivers/base/core.c
#include <linux/device.h>
/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
/**
* device_destroy - removes a device that was created with device_create()
* @class: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered
*
* This call unregisters and cleans up a device that was created with a
* call to device_create().
*/
void device_destroy(struct class *class, dev_t devt)
{
struct device *dev;
dev = class_find_device(class, NULL, &devt, __match_devt);
if (dev) {
put_device(dev);
device_unregister(dev);
}
}
四、驱动源码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LED_ON 1
#define LED_OFF 0
#define CLK_PCLKEN0_BASE (0xB0000218)
#define GPIOB_DIR_BASE (0XB8003040)
#define GPIOBDATAOUT_BASE (0xB8003044)
static void __iomem *CLK_PCLKEN0;
static void __iomem *GPIOB_DIR;
static void __iomem *GPIOBDATAOUT;
struct newchrled_dev{
struct cdev cdev;
struct class *class;
struct device *device;
dev_t devid;
int major;
int minor;
};
struct newchrled_dev newchrled;
static int newchrled_devs = 1; /* Number of Devices */
static char newchrled_name[] = "newchrled"; /* Name of device */
static void led_switch(char led1_sw, char led2_sw)
{
u32 val = 0;
val = readl(GPIOBDATAOUT);
//LED1
if(led1_sw == LED_ON)
{
val &= ~(1<<4);
}else if(led1_sw == LED_OFF)
{
val |= (1<<4);
}else
{
printk("LED1 Control cmd error!\r\n");
}
//LED2
if(led2_sw == LED_ON)
{
val &= ~(1<<5);
}else if(led2_sw == LED_OFF)
{
val |= (1<<5);
}else
{
printk("LED2 Control cmd error!\r\n");
}
writel(val,GPIOBDATAOUT);
}
static int newchrled_open(struct inode *inode, struct file *filp){
return 0;
}
static int newchrled_release(struct inode *inode, struct file *filp){
return 0;
}
static ssize_t newchrled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
int retvalue = 0;
char databuf[2];
copy_from_user(databuf,buf,count);
if(retvalue < 0)
{
printk("Control LED Failed!\r\n");
return -1;
}
led_switch(databuf[0],databuf[1]);
return 0;
}
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = newchrled_open,
.write = newchrled_write,
.release = newchrled_release,
};
static int __init newchrled_init(void)
{
int retvalue = 0;
int result;
u32 val = 0;
/* init led */
CLK_PCLKEN0 = ioremap(CLK_PCLKEN0_BASE,4);
GPIOB_DIR = ioremap(GPIOB_DIR_BASE,4);
GPIOBDATAOUT= ioremap(GPIOBDATAOUT_BASE,4);
val = readl(CLK_PCLKEN0);
val |= (1<<3);
writel(val, CLK_PCLKEN0);
val = readl(GPIOB_DIR);
val |= ((1<<4)|(1<<5)); //PB4 and PB5
writel(val, GPIOB_DIR);
val = readl(GPIOBDATAOUT);
val |= (1<<4)|(1<<5);
writel(val,GPIOBDATAOUT);
newchrled.devid = 0;
/* Register our major, and accept a dynamic number. */
if(newchrled.devid)
{
result = register_chrdev_region(newchrled.devid, newchrled_devs, newchrled_name);
}else
{
result = alloc_chrdev_region(&newchrled.devid, 0,newchrled_devs, newchrled_name);
newchrled.major = MAJOR(newchrled.devid);
newchrled.minor = MINOR(newchrled.devid);
}
if(result < 0)
{
printk("register chrdev failed!\r\n");
return -1;
}
printk("newchrled major = %d, minor = %d\r\n",newchrled.major, newchrled.minor);
/* Register a character device. */
cdev_init(&newchrled.cdev, &newchrled_fops);
newchrled.cdev.owner = THIS_MODULE;
newchrled.cdev.ops = &newchrled_fops;
result = cdev_add(&newchrled.cdev, newchrled.devid, newchrled_devs);
if(result < 0)
{
printk("cdev_add failed!\r\n");
goto fail_chrdev;
}
/* Create a sysfs class. */
newchrled.class = class_create(THIS_MODULE, newchrled_name);
if(IS_ERR(newchrled.class))
{
printk("class_create failed!\r\n");
result = PTR_ERR(newchrled.class);
goto fail_cdev;
}
/* Create a device. */
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, 0, newchrled_name);
if(IS_ERR(newchrled.device))
{
printk("device_create failed!\r\n");
result = PTR_ERR(newchrled.device);
goto fail_class;
}
printk("led_init\r\n");
return 0;
fail_class:
class_destroy(newchrled.class);
fail_cdev:
cdev_del(&newchrled.cdev);
fail_chrdev:
unregister_chrdev_region(newchrled.devid,newchrled_devs);
return result;
}
static void __exit newchrled_exit(void)
{
u32 val = 0;
/* close the led */
val = readl(GPIOBDATAOUT);
val |= (1<<4)|(1<<5);
writel(val, GPIOBDATAOUT);
iounmap(CLK_PCLKEN0);
iounmap(GPIOB_DIR);
iounmap(GPIOBDATAOUT);
device_destroy(newchrled.class,newchrled.devid);
class_destroy(newchrled.class);
cdev_del(&newchrled.cdev);
unregister_chrdev_region(newchrled.devid,newchrled_devs);
printk("led_exit\r\n");
}
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("mx");
五、总结
1.如果未定义设备号(为0),则使用alloc_chrdev_region函数申请设备号;否则,使用register_chrdev_region函数申请设备号;
2.判断报错goto语句执行顺序,与初始化顺序相反;如果出错返回,需要将之前申请成功的,都销毁注销;
3.出口函数注销资源,先后后前;