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 + -
显示快捷键?