pktcdvd.c

来自「linux 内核源代码」· C语言 代码 · 共 2,596 行 · 第 1/5 页

C
2,596
字号
/* * Copyright (C) 2000 Jens Axboe <axboe@suse.de> * Copyright (C) 2001-2004 Peter Osterlund <petero2@telia.com> * Copyright (C) 2006 Thomas Maier <balagi@justmail.de> * * May be copied or modified under the terms of the GNU General Public * License.  See linux/COPYING for more information. * * Packet writing layer for ATAPI and SCSI CD-RW, DVD+RW, DVD-RW and * DVD-RAM devices. * * Theory of operation: * * At the lowest level, there is the standard driver for the CD/DVD device, * typically ide-cd.c or sr.c. This driver can handle read and write requests, * but it doesn't know anything about the special restrictions that apply to * packet writing. One restriction is that write requests must be aligned to * packet boundaries on the physical media, and the size of a write request * must be equal to the packet size. Another restriction is that a * GPCMD_FLUSH_CACHE command has to be issued to the drive before a read * command, if the previous command was a write. * * The purpose of the packet writing driver is to hide these restrictions from * higher layers, such as file systems, and present a block device that can be * randomly read and written using 2kB-sized blocks. * * The lowest layer in the packet writing driver is the packet I/O scheduler. * Its data is defined by the struct packet_iosched and includes two bio * queues with pending read and write requests. These queues are processed * by the pkt_iosched_process_queue() function. The write requests in this * queue are already properly aligned and sized. This layer is responsible for * issuing the flush cache commands and scheduling the I/O in a good order. * * The next layer transforms unaligned write requests to aligned writes. This * transformation requires reading missing pieces of data from the underlying * block device, assembling the pieces to full packets and queuing them to the * packet I/O scheduler. * * At the top layer there is a custom make_request_fn function that forwards * read requests directly to the iosched queue and puts write requests in the * unaligned write queue. A kernel thread performs the necessary read * gathering to convert the unaligned writes to aligned writes and then feeds * them to the packet I/O scheduler. * *************************************************************************/#include <linux/pktcdvd.h>#include <linux/module.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/kthread.h>#include <linux/errno.h>#include <linux/spinlock.h>#include <linux/file.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/miscdevice.h>#include <linux/freezer.h>#include <linux/mutex.h>#include <scsi/scsi_cmnd.h>#include <scsi/scsi_ioctl.h>#include <scsi/scsi.h>#include <linux/debugfs.h>#include <linux/device.h>#include <asm/uaccess.h>#define DRIVER_NAME	"pktcdvd"#if PACKET_DEBUG#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)#else#define DPRINTK(fmt, args...)#endif#if PACKET_DEBUG > 1#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)#else#define VPRINTK(fmt, args...)#endif#define MAX_SPEED 0xffff#define ZONE(sector, pd) (((sector) + (pd)->offset) & ~((pd)->settings.size - 1))static struct pktcdvd_device *pkt_devs[MAX_WRITERS];static struct proc_dir_entry *pkt_proc;static int pktdev_major;static int write_congestion_on  = PKT_WRITE_CONGESTION_ON;static int write_congestion_off = PKT_WRITE_CONGESTION_OFF;static struct mutex ctl_mutex;	/* Serialize open/close/setup/teardown */static mempool_t *psd_pool;static struct class	*class_pktcdvd = NULL;    /* /sys/class/pktcdvd */static struct dentry	*pkt_debugfs_root = NULL; /* /debug/pktcdvd *//* forward declaration */static int pkt_setup_dev(dev_t dev, dev_t* pkt_dev);static int pkt_remove_dev(dev_t pkt_dev);static int pkt_seq_show(struct seq_file *m, void *p);/* * create and register a pktcdvd kernel object. */static struct pktcdvd_kobj* pkt_kobj_create(struct pktcdvd_device *pd,					const char* name,					struct kobject* parent,					struct kobj_type* ktype){	struct pktcdvd_kobj *p;	p = kzalloc(sizeof(*p), GFP_KERNEL);	if (!p)		return NULL;	kobject_set_name(&p->kobj, "%s", name);	p->kobj.parent = parent;	p->kobj.ktype = ktype;	p->pd = pd;	if (kobject_register(&p->kobj) != 0) {		kobject_put(&p->kobj);		return NULL;	}	return p;}/* * remove a pktcdvd kernel object. */static void pkt_kobj_remove(struct pktcdvd_kobj *p){	if (p)		kobject_unregister(&p->kobj);}/* * default release function for pktcdvd kernel objects. */static void pkt_kobj_release(struct kobject *kobj){	kfree(to_pktcdvdkobj(kobj));}/********************************************************** * * sysfs interface for pktcdvd * by (C) 2006  Thomas Maier <balagi@justmail.de> * **********************************************************/#define DEF_ATTR(_obj,_name,_mode) \	static struct attribute _obj = { .name = _name, .mode = _mode }/**********************************************************  /sys/class/pktcdvd/pktcdvd[0-7]/                     stat/reset                     stat/packets_started                     stat/packets_finished                     stat/kb_written                     stat/kb_read                     stat/kb_read_gather                     write_queue/size                     write_queue/congestion_off                     write_queue/congestion_on **********************************************************/DEF_ATTR(kobj_pkt_attr_st1, "reset", 0200);DEF_ATTR(kobj_pkt_attr_st2, "packets_started", 0444);DEF_ATTR(kobj_pkt_attr_st3, "packets_finished", 0444);DEF_ATTR(kobj_pkt_attr_st4, "kb_written", 0444);DEF_ATTR(kobj_pkt_attr_st5, "kb_read", 0444);DEF_ATTR(kobj_pkt_attr_st6, "kb_read_gather", 0444);static struct attribute *kobj_pkt_attrs_stat[] = {	&kobj_pkt_attr_st1,	&kobj_pkt_attr_st2,	&kobj_pkt_attr_st3,	&kobj_pkt_attr_st4,	&kobj_pkt_attr_st5,	&kobj_pkt_attr_st6,	NULL};DEF_ATTR(kobj_pkt_attr_wq1, "size", 0444);DEF_ATTR(kobj_pkt_attr_wq2, "congestion_off", 0644);DEF_ATTR(kobj_pkt_attr_wq3, "congestion_on",  0644);static struct attribute *kobj_pkt_attrs_wqueue[] = {	&kobj_pkt_attr_wq1,	&kobj_pkt_attr_wq2,	&kobj_pkt_attr_wq3,	NULL};static ssize_t kobj_pkt_show(struct kobject *kobj,			struct attribute *attr, char *data){	struct pktcdvd_device *pd = to_pktcdvdkobj(kobj)->pd;	int n = 0;	int v;	if (strcmp(attr->name, "packets_started") == 0) {		n = sprintf(data, "%lu\n", pd->stats.pkt_started);	} else if (strcmp(attr->name, "packets_finished") == 0) {		n = sprintf(data, "%lu\n", pd->stats.pkt_ended);	} else if (strcmp(attr->name, "kb_written") == 0) {		n = sprintf(data, "%lu\n", pd->stats.secs_w >> 1);	} else if (strcmp(attr->name, "kb_read") == 0) {		n = sprintf(data, "%lu\n", pd->stats.secs_r >> 1);	} else if (strcmp(attr->name, "kb_read_gather") == 0) {		n = sprintf(data, "%lu\n", pd->stats.secs_rg >> 1);	} else if (strcmp(attr->name, "size") == 0) {		spin_lock(&pd->lock);		v = pd->bio_queue_size;		spin_unlock(&pd->lock);		n = sprintf(data, "%d\n", v);	} else if (strcmp(attr->name, "congestion_off") == 0) {		spin_lock(&pd->lock);		v = pd->write_congestion_off;		spin_unlock(&pd->lock);		n = sprintf(data, "%d\n", v);	} else if (strcmp(attr->name, "congestion_on") == 0) {		spin_lock(&pd->lock);		v = pd->write_congestion_on;		spin_unlock(&pd->lock);		n = sprintf(data, "%d\n", v);	}	return n;}static void init_write_congestion_marks(int* lo, int* hi){	if (*hi > 0) {		*hi = max(*hi, 500);		*hi = min(*hi, 1000000);		if (*lo <= 0)			*lo = *hi - 100;		else {			*lo = min(*lo, *hi - 100);			*lo = max(*lo, 100);		}	} else {		*hi = -1;		*lo = -1;	}}static ssize_t kobj_pkt_store(struct kobject *kobj,			struct attribute *attr,			const char *data, size_t len){	struct pktcdvd_device *pd = to_pktcdvdkobj(kobj)->pd;	int val;	if (strcmp(attr->name, "reset") == 0 && len > 0) {		pd->stats.pkt_started = 0;		pd->stats.pkt_ended = 0;		pd->stats.secs_w = 0;		pd->stats.secs_rg = 0;		pd->stats.secs_r = 0;	} else if (strcmp(attr->name, "congestion_off") == 0		   && sscanf(data, "%d", &val) == 1) {		spin_lock(&pd->lock);		pd->write_congestion_off = val;		init_write_congestion_marks(&pd->write_congestion_off,					&pd->write_congestion_on);		spin_unlock(&pd->lock);	} else if (strcmp(attr->name, "congestion_on") == 0		   && sscanf(data, "%d", &val) == 1) {		spin_lock(&pd->lock);		pd->write_congestion_on = val;		init_write_congestion_marks(&pd->write_congestion_off,					&pd->write_congestion_on);		spin_unlock(&pd->lock);	}	return len;}static struct sysfs_ops kobj_pkt_ops = {	.show = kobj_pkt_show,	.store = kobj_pkt_store};static struct kobj_type kobj_pkt_type_stat = {	.release = pkt_kobj_release,	.sysfs_ops = &kobj_pkt_ops,	.default_attrs = kobj_pkt_attrs_stat};static struct kobj_type kobj_pkt_type_wqueue = {	.release = pkt_kobj_release,	.sysfs_ops = &kobj_pkt_ops,	.default_attrs = kobj_pkt_attrs_wqueue};static void pkt_sysfs_dev_new(struct pktcdvd_device *pd){	if (class_pktcdvd) {		pd->clsdev = class_device_create(class_pktcdvd,					NULL, pd->pkt_dev,					NULL, "%s", pd->name);		if (IS_ERR(pd->clsdev))			pd->clsdev = NULL;	}	if (pd->clsdev) {		pd->kobj_stat = pkt_kobj_create(pd, "stat",					&pd->clsdev->kobj,					&kobj_pkt_type_stat);		pd->kobj_wqueue = pkt_kobj_create(pd, "write_queue",					&pd->clsdev->kobj,					&kobj_pkt_type_wqueue);	}}static void pkt_sysfs_dev_remove(struct pktcdvd_device *pd){	pkt_kobj_remove(pd->kobj_stat);	pkt_kobj_remove(pd->kobj_wqueue);	if (class_pktcdvd)		class_device_destroy(class_pktcdvd, pd->pkt_dev);}/********************************************************************  /sys/class/pktcdvd/                     add            map block device                     remove         unmap packet dev                     device_map     show mappings *******************************************************************/static void class_pktcdvd_release(struct class *cls){	kfree(cls);}static ssize_t class_pktcdvd_show_map(struct class *c, char *data){	int n = 0;	int idx;	mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);	for (idx = 0; idx < MAX_WRITERS; idx++) {		struct pktcdvd_device *pd = pkt_devs[idx];		if (!pd)			continue;		n += sprintf(data+n, "%s %u:%u %u:%u\n",			pd->name,			MAJOR(pd->pkt_dev), MINOR(pd->pkt_dev),			MAJOR(pd->bdev->bd_dev),			MINOR(pd->bdev->bd_dev));	}	mutex_unlock(&ctl_mutex);	return n;}static ssize_t class_pktcdvd_store_add(struct class *c, const char *buf,					size_t count){	unsigned int major, minor;	if (sscanf(buf, "%u:%u", &major, &minor) == 2) {		/* pkt_setup_dev() expects caller to hold reference to self */		if (!try_module_get(THIS_MODULE))			return -ENODEV;		pkt_setup_dev(MKDEV(major, minor), NULL);		module_put(THIS_MODULE);		return count;	}	return -EINVAL;}static ssize_t class_pktcdvd_store_remove(struct class *c, const char *buf,					size_t count){	unsigned int major, minor;	if (sscanf(buf, "%u:%u", &major, &minor) == 2) {		pkt_remove_dev(MKDEV(major, minor));		return count;	}	return -EINVAL;}static struct class_attribute class_pktcdvd_attrs[] = { __ATTR(add,            0200, NULL, class_pktcdvd_store_add), __ATTR(remove,         0200, NULL, class_pktcdvd_store_remove), __ATTR(device_map,     0444, class_pktcdvd_show_map, NULL), __ATTR_NULL};static int pkt_sysfs_init(void){	int ret = 0;	/*	 * create control files in sysfs	 * /sys/class/pktcdvd/...	 */	class_pktcdvd = kzalloc(sizeof(*class_pktcdvd), GFP_KERNEL);	if (!class_pktcdvd)		return -ENOMEM;	class_pktcdvd->name = DRIVER_NAME;	class_pktcdvd->owner = THIS_MODULE;	class_pktcdvd->class_release = class_pktcdvd_release;	class_pktcdvd->class_attrs = class_pktcdvd_attrs;	ret = class_register(class_pktcdvd);	if (ret) {		kfree(class_pktcdvd);		class_pktcdvd = NULL;		printk(DRIVER_NAME": failed to create class pktcdvd\n");		return ret;	}	return 0;}static void pkt_sysfs_cleanup(void){	if (class_pktcdvd)		class_destroy(class_pktcdvd);	class_pktcdvd = NULL;}/********************************************************************  entries in debugfs  /debugfs/pktcdvd[0-7]/			info *******************************************************************/static int pkt_debugfs_seq_show(struct seq_file *m, void *p){	return pkt_seq_show(m, p);}static int pkt_debugfs_fops_open(struct inode *inode, struct file *file){	return single_open(file, pkt_debugfs_seq_show, inode->i_private);}static const struct file_operations debug_fops = {	.open		= pkt_debugfs_fops_open,	.read		= seq_read,	.llseek		= seq_lseek,	.release	= single_release,	.owner		= THIS_MODULE,};static void pkt_debugfs_dev_new(struct pktcdvd_device *pd){	if (!pkt_debugfs_root)		return;	pd->dfs_f_info = NULL;	pd->dfs_d_root = debugfs_create_dir(pd->name, pkt_debugfs_root);	if (IS_ERR(pd->dfs_d_root)) {		pd->dfs_d_root = NULL;		return;	}	pd->dfs_f_info = debugfs_create_file("info", S_IRUGO,				pd->dfs_d_root, pd, &debug_fops);	if (IS_ERR(pd->dfs_f_info)) {		pd->dfs_f_info = NULL;		return;	}}static void pkt_debugfs_dev_remove(struct pktcdvd_device *pd){	if (!pkt_debugfs_root)		return;	if (pd->dfs_f_info)		debugfs_remove(pd->dfs_f_info);	pd->dfs_f_info = NULL;	if (pd->dfs_d_root)		debugfs_remove(pd->dfs_d_root);	pd->dfs_d_root = NULL;}static void pkt_debugfs_init(void){	pkt_debugfs_root = debugfs_create_dir(DRIVER_NAME, NULL);	if (IS_ERR(pkt_debugfs_root)) {		pkt_debugfs_root = NULL;		return;	}}static void pkt_debugfs_cleanup(void){	if (!pkt_debugfs_root)		return;	debugfs_remove(pkt_debugfs_root);	pkt_debugfs_root = NULL;}/* ----------------------------------------------------------*/static void pkt_bio_finished(struct pktcdvd_device *pd){	BUG_ON(atomic_read(&pd->cdrw.pending_bios) <= 0);	if (atomic_dec_and_test(&pd->cdrw.pending_bios)) {		VPRINTK(DRIVER_NAME": queue empty\n");		atomic_set(&pd->iosched.attention, 1);		wake_up(&pd->wqueue);	}}static void pkt_bio_destructor(struct bio *bio){	kfree(bio->bi_io_vec);	kfree(bio);}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?