5.5 misc驱动框架
1. misc类设备驱动框架
linux内核中的杂项设备(Miscellaneous Devices)是一种通用的设备类型。现在的硬件设备多种多样,有很多设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于misc类设备,比如adc、buzzer等设备归属于misc类设备中。杂项设备的主设备号固定为10.
2. misc类设备驱动框架分析
2.1 miscdevice 结构体
struct miscdevice {int minor;const char *name;const struct file_operations *fops;struct list_head list;struct device *parent;struct device *this_device;const char *nodename;mode_t mode;
};
2.2 misc驱动框架源码
源码在kernel/drivers/char/misc.c中:
/** linux/drivers/char/misc.c** Generic misc open routine by Johan Myreen** Based on code from Linus** Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's* changes incorporated into 0.97pl4* by Peter Cervasio (pete%q106fm.uucp@wupost.wustl.edu) (08SEP92)* See busmouse.c for particulars.** Made things a lot mode modular - easy to compile in just one or two* of the misc drivers, as they are now completely independent. Linus.** Support for loadable modules. 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk>** Fixed a failing symbol register to free the device registration* Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-96** Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-96** Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-96** Handling of mouse minor numbers for kerneld:* Idea by Jacques Gelinas <jack@solucorp.qc.ca>,* adapted by Bjorn Ekwall <bj0rn@blox.se>* corrected by Alan Cox <alan@lxorguk.ukuu.org.uk>** Changes for kmod (from kerneld):* Cyrus Durgin <cider@speakeasy.org>** Added devfs support. Richard Gooch <rgooch@atnf.csiro.au> 10-Jan-1998*/#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>/** Head entry for the doubly linked miscdevice list*/
static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);/** Assigned numbers, used for dynamic minors*/
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);#ifdef CONFIG_PROC_FS
static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
{mutex_lock(&misc_mtx);return seq_list_start(&misc_list, *pos);
}static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{return seq_list_next(v, &misc_list, pos);
}static void misc_seq_stop(struct seq_file *seq, void *v)
{mutex_unlock(&misc_mtx);
}static int misc_seq_show(struct seq_file *seq, void *v)
{const struct miscdevice *p = list_entry(v, struct miscdevice, list);seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");return 0;
}static const struct seq_operations misc_seq_ops = {.start = misc_seq_start,.next = misc_seq_next,.stop = misc_seq_stop,.show = misc_seq_show,
};static int misc_seq_open(struct inode *inode, struct file *file)
{return seq_open(file, &misc_seq_ops);
}static const struct file_operations misc_proc_fops = {.owner = THIS_MODULE,.open = misc_seq_open,.read = seq_read,.llseek = seq_lseek,.release = seq_release,
};
#endifstatic int misc_open(struct inode * inode, struct file * file)
{int minor = iminor(inode);struct miscdevice *c;int err = -ENODEV;const struct file_operations *old_fops, *new_fops = NULL;mutex_lock(&misc_mtx);list_for_each_entry(c, &misc_list, list) {if (c->minor == minor) {new_fops = fops_get(c->fops); break;}}if (!new_fops) {mutex_unlock(&misc_mtx);request_module("char-major-%d-%d", MISC_MAJOR, minor);mutex_lock(&misc_mtx);list_for_each_entry(c, &misc_list, list) {if (c->minor == minor) {new_fops = fops_get(c->fops);break;}}if (!new_fops)goto fail;}err = 0;old_fops = file->f_op;file->f_op = new_fops;if (file->f_op->open) {file->private_data = c;err=file->f_op->open(inode,file);if (err) {fops_put(file->f_op);file->f_op = fops_get(old_fops);}}fops_put(old_fops);
fail:mutex_unlock(&misc_mtx);return err;
}static struct class *misc_class;static const struct file_operations misc_fops = {.owner = THIS_MODULE,.open = misc_open,
};/*** misc_register - register a miscellaneous device* @misc: device structure* * Register a miscellaneous device with the kernel. If the minor* number is set to %MISC_DYNAMIC_MINOR a minor number is assigned* and placed in the minor field of the structure. For other cases* the minor number requested is used.** The structure passed is linked into the kernel and may not be* destroyed until it has been unregistered.** A zero is returned on success and a negative errno code for* failure.*/int misc_register(struct miscdevice * misc)
{struct miscdevice *c;dev_t dev;int err = 0;INIT_LIST_HEAD(&misc->list);mutex_lock(&misc_mtx);list_for_each_entry(c, &misc_list, list) {if (c->minor == misc->minor) {mutex_unlock(&misc_mtx);return -EBUSY;}}if (misc->minor == MISC_DYNAMIC_MINOR) {int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);if (i >= DYNAMIC_MINORS) {mutex_unlock(&misc_mtx);return -EBUSY;}misc->minor = DYNAMIC_MINORS - i - 1;set_bit(i, misc_minors);}dev = MKDEV(MISC_MAJOR, misc->minor);misc->this_device = device_create(misc_class, misc->parent, dev,misc, "%s", misc->name);if (IS_ERR(misc->this_device)) {int i = DYNAMIC_MINORS - misc->minor - 1;if (i < DYNAMIC_MINORS && i >= 0)clear_bit(i, misc_minors);err = PTR_ERR(misc->this_device);goto out;}/** Add it to the front, so that later devices can "override"* earlier defaults*/list_add(&misc->list, &misc_list);out:mutex_unlock(&misc_mtx);return err;
}/*** misc_deregister - unregister a miscellaneous device* @misc: device to unregister** Unregister a miscellaneous device that was previously* successfully registered with misc_register(). Success* is indicated by a zero return, a negative errno code* indicates an error.*/int misc_deregister(struct miscdevice *misc)
{int i = DYNAMIC_MINORS - misc->minor - 1;if (list_empty(&misc->list))return -EINVAL;mutex_lock(&misc_mtx);list_del(&misc->list);device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));if (i < DYNAMIC_MINORS && i >= 0)clear_bit(i, misc_minors);mutex_unlock(&misc_mtx);return 0;
}EXPORT_SYMBOL(misc_register);
EXPORT_SYMBOL(misc_deregister);static char *misc_devnode(struct device *dev, mode_t *mode)
{struct miscdevice *c = dev_get_drvdata(dev);if (mode && c->mode)*mode = c->mode;if (c->nodename)return kstrdup(c->nodename, GFP_KERNEL);return NULL;
}static int __init misc_init(void)
{int err;#ifdef CONFIG_PROC_FSproc_create("misc", 0, NULL, &misc_proc_fops);
#endifmisc_class = class_create(THIS_MODULE, "misc");err = PTR_ERR(misc_class);if (IS_ERR(misc_class))goto fail_remove;err = -EIO;if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))goto fail_printk;misc_class->devnode = misc_devnode;return 0;fail_printk:printk("unable to get major %d for misc devices\n", MISC_MAJOR);class_destroy(misc_class);
fail_remove:remove_proc_entry("misc", NULL);return err;
}
subsys_initcall(misc_init);
(1)int misc_register(struct miscdevice * misc); misc设备注册
(2)int misc_deregister(struct miscdevice *misc); misc设备卸载
3. misc设备驱动实例
蜂鸣器驱动(buzzer)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <plat/regs-timer.h>
#include <mach/regs-irq.h>
#include <asm/mach/time.h>
#include <linux/clk.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>#include <linux/gpio.h>#include <plat/gpio-cfg.h>
//#include <plat/regs-clock.h>
//#include <plat/regs-gpio.h>//#include <plat/gpio-bank-e.h>
//#include <plat/gpio-bank-f.h>
//#include <plat/gpio-bank-k.h>#define DEVICE_NAME "buzzer"#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0static struct semaphore lock;// TCFG0在Uboot中设置,这里不再重复设置
// Timer0输入频率Finput=pclk/(prescaler1+1)/MUX1
// =66M/16/16
// TCFG0 = tcnt = (pclk/16/16)/freq;
// PWM0输出频率Foutput =Finput/TCFG0= freq
static void PWM_Set_Freq( unsigned long freq )
{unsigned long tcon;unsigned long tcnt;unsigned long tcfg1;struct clk *clk_p;unsigned long pclk;//unsigned tmp;//设置GPD0_2为PWM输出s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(2));tcon = __raw_readl(S3C2410_TCON);tcfg1 = __raw_readl(S3C2410_TCFG1);//mux = 1/16tcfg1 &= ~(0xf<<8);tcfg1 |= (0x4<<8);__raw_writel(tcfg1, S3C2410_TCFG1);clk_p = clk_get(NULL, "pclk");pclk = clk_get_rate(clk_p);tcnt = (pclk/16/16)/freq;__raw_writel(tcnt, S3C2410_TCNTB(2));__raw_writel(tcnt/2, S3C2410_TCMPB(2));//占空比为50%tcon &= ~(0xf<<12);tcon |= (0xb<<12); //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0__raw_writel(tcon, S3C2410_TCON);tcon &= ~(2<<12); //clear manual update bit__raw_writel(tcon, S3C2410_TCON);
}void PWM_Stop( void )
{//将GPD0_2设置为inputs3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(0));
}static int x210_pwm_open(struct inode *inode, struct file *file)
{if (!down_trylock(&lock))return 0;elsereturn -EBUSY;}static int x210_pwm_close(struct inode *inode, struct file *file)
{up(&lock);return 0;
}// PWM:GPF14->PWM0
static int x210_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{switch (cmd) {case PWM_IOCTL_SET_FREQ:printk("PWM_IOCTL_SET_FREQ:\r\n");if (arg == 0)return -EINVAL;PWM_Set_Freq(arg);break;case PWM_IOCTL_STOP:default:printk("PWM_IOCTL_STOP:\r\n");PWM_Stop();break;}return 0;
}static struct file_operations dev_fops = {.owner = THIS_MODULE,.open = x210_pwm_open,.release = x210_pwm_close, .ioctl = x210_pwm_ioctl,
};static struct miscdevice misc = {.minor = MISC_DYNAMIC_MINOR,.name = DEVICE_NAME,.fops = &dev_fops,
};static int __init dev_init(void)
{int ret;init_MUTEX(&lock);ret = misc_register(&misc);/* GPD0_2 (PWMTOUT2) */ret = gpio_request(S5PV210_GPD0(2), "GPD0");if(ret)printk("buzzer-x210: request gpio GPD0(2) fail");s3c_gpio_setpull(S5PV210_GPD0(2), S3C_GPIO_PULL_UP);s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(1));gpio_set_value(S5PV210_GPD0(2), 0);printk ("x210 "DEVICE_NAME" initialized\n");return ret;
}static void __exit dev_exit(void)
{misc_deregister(&misc);
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.9tripod.com");
MODULE_DESCRIPTION("x210 PWM Driver");