linux/drivers/block/pktcdvd.c

/*
 * Copyright (C) 2000 Jens Axboe <[email protected]>
 * Copyright (C) 2001-2004 Peter Osterlund <[email protected]>
 * Copyright (C) 2006 Thomas Maier <[email protected]>
 *
 * 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,
 * such as drivers/scsi/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 ->submit_bio 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.
 *
 *************************************************************************/

#define pr_fmt(fmt)

#include <linux/backing-dev.h>
#include <linux/compat.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/file.h>
#include <linux/freezer.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/nospec.h>
#include <linux/pktcdvd.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/uaccess.h>

#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_ioctl.h>

#include <asm/unaligned.h>

#define DRIVER_NAME

#define MAX_SPEED

static DEFINE_MUTEX(pktcdvd_mutex);
static struct pktcdvd_device *pkt_devs[MAX_WRITERS];
static struct proc_dir_entry *pkt_proc;
static int pktdev_major;
static int write_congestion_on  =;
static int write_congestion_off =;
static struct mutex ctl_mutex;	/* Serialize open/close/setup/teardown */
static mempool_t psd_pool;
static struct bio_set pkt_bio_set;

/* /sys/class/pktcdvd */
static struct class	class_pktcdvd;
static struct dentry	*pkt_debugfs_root =; /* /sys/kernel/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 sector_t get_zone(sector_t sector, struct pktcdvd_device *pd)
{}

/**********************************************************
 * sysfs interface for pktcdvd
 * by (C) 2006  Thomas Maier <[email protected]>
 
  /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
 **********************************************************/

static ssize_t packets_started_show(struct device *dev,
				    struct device_attribute *attr, char *buf)
{}
static DEVICE_ATTR_RO(packets_started);

static ssize_t packets_finished_show(struct device *dev,
				     struct device_attribute *attr, char *buf)
{}
static DEVICE_ATTR_RO(packets_finished);

static ssize_t kb_written_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{}
static DEVICE_ATTR_RO(kb_written);

static ssize_t kb_read_show(struct device *dev,
			    struct device_attribute *attr, char *buf)
{}
static DEVICE_ATTR_RO(kb_read);

static ssize_t kb_read_gather_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{}
static DEVICE_ATTR_RO(kb_read_gather);

static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
			   const char *buf, size_t len)
{}
static DEVICE_ATTR_WO(reset);

static struct attribute *pkt_stat_attrs[] =;

static const struct attribute_group pkt_stat_group =;

static ssize_t size_show(struct device *dev,
			 struct device_attribute *attr, char *buf)
{}
static DEVICE_ATTR_RO(size);

static void init_write_congestion_marks(int* lo, int* hi)
{}

static ssize_t congestion_off_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{}

static ssize_t congestion_off_store(struct device *dev,
				    struct device_attribute *attr,
				    const char *buf, size_t len)
{}
static DEVICE_ATTR_RW(congestion_off);

static ssize_t congestion_on_show(struct device *dev,
				  struct device_attribute *attr, char *buf)
{}

static ssize_t congestion_on_store(struct device *dev,
				   struct device_attribute *attr,
				   const char *buf, size_t len)
{}
static DEVICE_ATTR_RW(congestion_on);

static struct attribute *pkt_wq_attrs[] =;

static const struct attribute_group pkt_wq_group =;

static const struct attribute_group *pkt_groups[] =;

static void pkt_sysfs_dev_new(struct pktcdvd_device *pd)
{}

static void pkt_sysfs_dev_remove(struct pktcdvd_device *pd)
{}


/********************************************************************
  /sys/class/pktcdvd/
                     add            map block device
                     remove         unmap packet dev
                     device_map     show mappings
 *******************************************************************/

static ssize_t device_map_show(const struct class *c, const struct class_attribute *attr,
			       char *data)
{}
static CLASS_ATTR_RO(device_map);

static ssize_t add_store(const struct class *c, const struct class_attribute *attr,
			 const char *buf, size_t count)
{}
static CLASS_ATTR_WO(add);

static ssize_t remove_store(const struct class *c, const struct class_attribute *attr,
			    const char *buf, size_t count)
{}
static CLASS_ATTR_WO(remove);

static struct attribute *class_pktcdvd_attrs[] =;
ATTRIBUTE_GROUPS();

static struct class class_pktcdvd =;

static int pkt_sysfs_init(void)
{}

static void pkt_sysfs_cleanup(void)
{}

/********************************************************************
  entries in debugfs

  /sys/kernel/debug/pktcdvd[0-7]/
			info

 *******************************************************************/

static void pkt_count_states(struct pktcdvd_device *pd, int *states)
{}

static int pkt_seq_show(struct seq_file *m, void *p)
{}
DEFINE_SHOW_ATTRIBUTE();

static void pkt_debugfs_dev_new(struct pktcdvd_device *pd)
{}

static void pkt_debugfs_dev_remove(struct pktcdvd_device *pd)
{}

static void pkt_debugfs_init(void)
{}

static void pkt_debugfs_cleanup(void)
{}

/* ----------------------------------------------------------*/


static void pkt_bio_finished(struct pktcdvd_device *pd)
{}

/*
 * Allocate a packet_data struct
 */
static struct packet_data *pkt_alloc_packet_data(int frames)
{}

/*
 * Free a packet_data struct
 */
static void pkt_free_packet_data(struct packet_data *pkt)
{}

static void pkt_shrink_pktlist(struct pktcdvd_device *pd)
{}

static int pkt_grow_pktlist(struct pktcdvd_device *pd, int nr_packets)
{}

static inline struct pkt_rb_node *pkt_rbtree_next(struct pkt_rb_node *node)
{}

static void pkt_rbtree_erase(struct pktcdvd_device *pd, struct pkt_rb_node *node)
{}

/*
 * Find the first node in the pd->bio_queue rb tree with a starting sector >= s.
 */
static struct pkt_rb_node *pkt_rbtree_find(struct pktcdvd_device *pd, sector_t s)
{}

/*
 * Insert a node into the pd->bio_queue rb tree.
 */
static void pkt_rbtree_insert(struct pktcdvd_device *pd, struct pkt_rb_node *node)
{}

/*
 * Send a packet_command to the underlying block device and
 * wait for completion.
 */
static int pkt_generic_packet(struct pktcdvd_device *pd, struct packet_command *cgc)
{}

static const char *sense_key_string(__u8 index)
{}

/*
 * A generic sense dump / resolve mechanism should be implemented across
 * all ATAPI + SCSI devices.
 */
static void pkt_dump_sense(struct pktcdvd_device *pd,
			   struct packet_command *cgc)
{}

/*
 * flush the drive cache to media
 */
static int pkt_flush_cache(struct pktcdvd_device *pd)
{}

/*
 * speed is given as the normal factor, e.g. 4 for 4x
 */
static noinline_for_stack int pkt_set_speed(struct pktcdvd_device *pd,
				unsigned write_speed, unsigned read_speed)
{}

/*
 * Queue a bio for processing by the low-level CD device. Must be called
 * from process context.
 */
static void pkt_queue_bio(struct pktcdvd_device *pd, struct bio *bio)
{}

/*
 * Process the queued read/write requests. This function handles special
 * requirements for CDRW drives:
 * - A cache flush command must be inserted before a read request if the
 *   previous request was a write.
 * - Switching between reading and writing is slow, so don't do it more often
 *   than necessary.
 * - Optimize for throughput at the expense of latency. This means that streaming
 *   writes will never be interrupted by a read, but if the drive has to seek
 *   before the next write, switch to reading instead if there are any pending
 *   read requests.
 * - Set the read speed according to current usage pattern. When only reading
 *   from the device, it's best to use the highest possible read speed, but
 *   when switching often between reading and writing, it's better to have the
 *   same read and write speeds.
 */
static void pkt_iosched_process_queue(struct pktcdvd_device *pd)
{}

/*
 * Special care is needed if the underlying block device has a small
 * max_phys_segments value.
 */
static int pkt_set_segment_merging(struct pktcdvd_device *pd, struct request_queue *q)
{}

static void pkt_end_io_read(struct bio *bio)
{}

static void pkt_end_io_packet_write(struct bio *bio)
{}

/*
 * Schedule reads for the holes in a packet
 */
static void pkt_gather_data(struct pktcdvd_device *pd, struct packet_data *pkt)
{}

/*
 * Find a packet matching zone, or the least recently used packet if
 * there is no match.
 */
static struct packet_data *pkt_get_packet_data(struct pktcdvd_device *pd, int zone)
{}

static void pkt_put_packet_data(struct pktcdvd_device *pd, struct packet_data *pkt)
{}

static inline void pkt_set_state(struct device *ddev, struct packet_data *pkt,
				 enum packet_data_state state)
{}

/*
 * Scan the work queue to see if we can start a new packet.
 * returns non-zero if any work was done.
 */
static int pkt_handle_queue(struct pktcdvd_device *pd)
{}

/**
 * bio_list_copy_data - copy contents of data buffers from one chain of bios to
 * another
 * @src: source bio list
 * @dst: destination bio list
 *
 * Stops when it reaches the end of either the @src list or @dst list - that is,
 * copies min(src->bi_size, dst->bi_size) bytes (or the equivalent for lists of
 * bios).
 */
static void bio_list_copy_data(struct bio *dst, struct bio *src)
{}

/*
 * Assemble a bio to write one packet and queue the bio for processing
 * by the underlying block device.
 */
static void pkt_start_write(struct pktcdvd_device *pd, struct packet_data *pkt)
{}

static void pkt_finish_packet(struct packet_data *pkt, blk_status_t status)
{}

static void pkt_run_state_machine(struct pktcdvd_device *pd, struct packet_data *pkt)
{}

static void pkt_handle_packets(struct pktcdvd_device *pd)
{}

/*
 * kcdrwd is woken up when writes have been queued for one of our
 * registered devices
 */
static int kcdrwd(void *foobar)
{}

static void pkt_print_settings(struct pktcdvd_device *pd)
{}

static int pkt_mode_sense(struct pktcdvd_device *pd, struct packet_command *cgc, int page_code, int page_control)
{}

static int pkt_mode_select(struct pktcdvd_device *pd, struct packet_command *cgc)
{}

static int pkt_get_disc_info(struct pktcdvd_device *pd, disc_information *di)
{}

static int pkt_get_track_info(struct pktcdvd_device *pd, __u16 track, __u8 type, track_information *ti)
{}

static noinline_for_stack int pkt_get_last_written(struct pktcdvd_device *pd,
						long *last_written)
{}

/*
 * write mode select package based on pd->settings
 */
static noinline_for_stack int pkt_set_write_settings(struct pktcdvd_device *pd)
{}

/*
 * 1 -- we can write to this track, 0 -- we can't
 */
static int pkt_writable_track(struct pktcdvd_device *pd, track_information *ti)
{}

/*
 * 1 -- we can write to this disc, 0 -- we can't
 */
static int pkt_writable_disc(struct pktcdvd_device *pd, disc_information *di)
{}

static noinline_for_stack int pkt_probe_settings(struct pktcdvd_device *pd)
{}

/*
 * enable/disable write caching on drive
 */
static noinline_for_stack int pkt_write_caching(struct pktcdvd_device *pd)
{}

static int pkt_lock_door(struct pktcdvd_device *pd, int lockflag)
{}

/*
 * Returns drive maximum write speed
 */
static noinline_for_stack int pkt_get_max_speed(struct pktcdvd_device *pd,
						unsigned *write_speed)
{}

/* These tables from cdrecord - I don't have orange book */
/* standard speed CD-RW (1-4x) */
static char clv_to_speed[16] =;
/* high speed CD-RW (-10x) */
static char hs_clv_to_speed[16] =;
/* ultra high speed CD-RW */
static char us_clv_to_speed[16] =;

/*
 * reads the maximum media speed from ATIP
 */
static noinline_for_stack int pkt_media_speed(struct pktcdvd_device *pd,
						unsigned *speed)
{}

static noinline_for_stack int pkt_perform_opc(struct pktcdvd_device *pd)
{}

static int pkt_open_write(struct pktcdvd_device *pd)
{}

/*
 * called at open time.
 */
static int pkt_open_dev(struct pktcdvd_device *pd, bool write)
{}

/*
 * called when the device is closed. makes sure that the device flushes
 * the internal cache before we close.
 */
static void pkt_release_dev(struct pktcdvd_device *pd, int flush)
{}

static struct pktcdvd_device *pkt_find_dev_from_minor(unsigned int dev_minor)
{}

static int pkt_open(struct gendisk *disk, blk_mode_t mode)
{}

static void pkt_release(struct gendisk *disk)
{}


static void pkt_end_io_read_cloned(struct bio *bio)
{}

static void pkt_make_request_read(struct pktcdvd_device *pd, struct bio *bio)
{}

static void pkt_make_request_write(struct bio *bio)
{}

static void pkt_submit_bio(struct bio *bio)
{}

static int pkt_new_dev(struct pktcdvd_device *pd, dev_t dev)
{}

static int pkt_ioctl(struct block_device *bdev, blk_mode_t mode,
		unsigned int cmd, unsigned long arg)
{}

static unsigned int pkt_check_events(struct gendisk *disk,
				     unsigned int clearing)
{}

static char *pkt_devnode(struct gendisk *disk, umode_t *mode)
{}

static const struct block_device_operations pktcdvd_ops =;

/*
 * Set up mapping from pktcdvd device to CD-ROM device.
 */
static int pkt_setup_dev(dev_t dev, dev_t* pkt_dev)
{}

/*
 * Tear down mapping from pktcdvd device to CD-ROM device.
 */
static int pkt_remove_dev(dev_t pkt_dev)
{}

static void pkt_get_status(struct pkt_ctrl_command *ctrl_cmd)
{}

static long pkt_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{}

#ifdef CONFIG_COMPAT
static long pkt_ctl_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{}
#endif

static const struct file_operations pkt_ctl_fops =;

static struct miscdevice pkt_misc =;

static int __init pkt_init(void)
{}

static void __exit pkt_exit(void)
{}

MODULE_DESCRIPTION();
MODULE_AUTHOR();
MODULE_LICENSE();

module_init();
module_exit(pkt_exit);