pktcdvd.c

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

C
2,596
字号
static struct bio *pkt_bio_alloc(int nr_iovecs){	struct bio_vec *bvl = NULL;	struct bio *bio;	bio = kmalloc(sizeof(struct bio), GFP_KERNEL);	if (!bio)		goto no_bio;	bio_init(bio);	bvl = kcalloc(nr_iovecs, sizeof(struct bio_vec), GFP_KERNEL);	if (!bvl)		goto no_bvl;	bio->bi_max_vecs = nr_iovecs;	bio->bi_io_vec = bvl;	bio->bi_destructor = pkt_bio_destructor;	return bio; no_bvl:	kfree(bio); no_bio:	return NULL;}/* * Allocate a packet_data struct */static struct packet_data *pkt_alloc_packet_data(int frames){	int i;	struct packet_data *pkt;	pkt = kzalloc(sizeof(struct packet_data), GFP_KERNEL);	if (!pkt)		goto no_pkt;	pkt->frames = frames;	pkt->w_bio = pkt_bio_alloc(frames);	if (!pkt->w_bio)		goto no_bio;	for (i = 0; i < frames / FRAMES_PER_PAGE; i++) {		pkt->pages[i] = alloc_page(GFP_KERNEL|__GFP_ZERO);		if (!pkt->pages[i])			goto no_page;	}	spin_lock_init(&pkt->lock);	for (i = 0; i < frames; i++) {		struct bio *bio = pkt_bio_alloc(1);		if (!bio)			goto no_rd_bio;		pkt->r_bios[i] = bio;	}	return pkt;no_rd_bio:	for (i = 0; i < frames; i++) {		struct bio *bio = pkt->r_bios[i];		if (bio)			bio_put(bio);	}no_page:	for (i = 0; i < frames / FRAMES_PER_PAGE; i++)		if (pkt->pages[i])			__free_page(pkt->pages[i]);	bio_put(pkt->w_bio);no_bio:	kfree(pkt);no_pkt:	return NULL;}/* * Free a packet_data struct */static void pkt_free_packet_data(struct packet_data *pkt){	int i;	for (i = 0; i < pkt->frames; i++) {		struct bio *bio = pkt->r_bios[i];		if (bio)			bio_put(bio);	}	for (i = 0; i < pkt->frames / FRAMES_PER_PAGE; i++)		__free_page(pkt->pages[i]);	bio_put(pkt->w_bio);	kfree(pkt);}static void pkt_shrink_pktlist(struct pktcdvd_device *pd){	struct packet_data *pkt, *next;	BUG_ON(!list_empty(&pd->cdrw.pkt_active_list));	list_for_each_entry_safe(pkt, next, &pd->cdrw.pkt_free_list, list) {		pkt_free_packet_data(pkt);	}	INIT_LIST_HEAD(&pd->cdrw.pkt_free_list);}static int pkt_grow_pktlist(struct pktcdvd_device *pd, int nr_packets){	struct packet_data *pkt;	BUG_ON(!list_empty(&pd->cdrw.pkt_free_list));	while (nr_packets > 0) {		pkt = pkt_alloc_packet_data(pd->settings.size >> 2);		if (!pkt) {			pkt_shrink_pktlist(pd);			return 0;		}		pkt->id = nr_packets;		pkt->pd = pd;		list_add(&pkt->list, &pd->cdrw.pkt_free_list);		nr_packets--;	}	return 1;}static inline struct pkt_rb_node *pkt_rbtree_next(struct pkt_rb_node *node){	struct rb_node *n = rb_next(&node->rb_node);	if (!n)		return NULL;	return rb_entry(n, struct pkt_rb_node, rb_node);}static void pkt_rbtree_erase(struct pktcdvd_device *pd, struct pkt_rb_node *node){	rb_erase(&node->rb_node, &pd->bio_queue);	mempool_free(node, pd->rb_pool);	pd->bio_queue_size--;	BUG_ON(pd->bio_queue_size < 0);}/* * 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){	struct rb_node *n = pd->bio_queue.rb_node;	struct rb_node *next;	struct pkt_rb_node *tmp;	if (!n) {		BUG_ON(pd->bio_queue_size > 0);		return NULL;	}	for (;;) {		tmp = rb_entry(n, struct pkt_rb_node, rb_node);		if (s <= tmp->bio->bi_sector)			next = n->rb_left;		else			next = n->rb_right;		if (!next)			break;		n = next;	}	if (s > tmp->bio->bi_sector) {		tmp = pkt_rbtree_next(tmp);		if (!tmp)			return NULL;	}	BUG_ON(s > tmp->bio->bi_sector);	return tmp;}/* * Insert a node into the pd->bio_queue rb tree. */static void pkt_rbtree_insert(struct pktcdvd_device *pd, struct pkt_rb_node *node){	struct rb_node **p = &pd->bio_queue.rb_node;	struct rb_node *parent = NULL;	sector_t s = node->bio->bi_sector;	struct pkt_rb_node *tmp;	while (*p) {		parent = *p;		tmp = rb_entry(parent, struct pkt_rb_node, rb_node);		if (s < tmp->bio->bi_sector)			p = &(*p)->rb_left;		else			p = &(*p)->rb_right;	}	rb_link_node(&node->rb_node, parent, p);	rb_insert_color(&node->rb_node, &pd->bio_queue);	pd->bio_queue_size++;}/* * Add a bio to a single linked list defined by its head and tail pointers. */static void pkt_add_list_last(struct bio *bio, struct bio **list_head, struct bio **list_tail){	bio->bi_next = NULL;	if (*list_tail) {		BUG_ON((*list_head) == NULL);		(*list_tail)->bi_next = bio;		(*list_tail) = bio;	} else {		BUG_ON((*list_head) != NULL);		(*list_head) = bio;		(*list_tail) = bio;	}}/* * Remove and return the first bio from a single linked list defined by its * head and tail pointers. */static inline struct bio *pkt_get_list_first(struct bio **list_head, struct bio **list_tail){	struct bio *bio;	if (*list_head == NULL)		return NULL;	bio = *list_head;	*list_head = bio->bi_next;	if (*list_head == NULL)		*list_tail = NULL;	bio->bi_next = NULL;	return bio;}/* * 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){	struct request_queue *q = bdev_get_queue(pd->bdev);	struct request *rq;	int ret = 0;	rq = blk_get_request(q, (cgc->data_direction == CGC_DATA_WRITE) ?			     WRITE : READ, __GFP_WAIT);	if (cgc->buflen) {		if (blk_rq_map_kern(q, rq, cgc->buffer, cgc->buflen, __GFP_WAIT))			goto out;	}	rq->cmd_len = COMMAND_SIZE(cgc->cmd[0]);	memcpy(rq->cmd, cgc->cmd, CDROM_PACKET_SIZE);	if (sizeof(rq->cmd) > CDROM_PACKET_SIZE)		memset(rq->cmd + CDROM_PACKET_SIZE, 0, sizeof(rq->cmd) - CDROM_PACKET_SIZE);	rq->timeout = 60*HZ;	rq->cmd_type = REQ_TYPE_BLOCK_PC;	rq->cmd_flags |= REQ_HARDBARRIER;	if (cgc->quiet)		rq->cmd_flags |= REQ_QUIET;	blk_execute_rq(rq->q, pd->bdev->bd_disk, rq, 0);	if (rq->errors)		ret = -EIO;out:	blk_put_request(rq);	return ret;}/* * A generic sense dump / resolve mechanism should be implemented across * all ATAPI + SCSI devices. */static void pkt_dump_sense(struct packet_command *cgc){	static char *info[9] = { "No sense", "Recovered error", "Not ready",				 "Medium error", "Hardware error", "Illegal request",				 "Unit attention", "Data protect", "Blank check" };	int i;	struct request_sense *sense = cgc->sense;	printk(DRIVER_NAME":");	for (i = 0; i < CDROM_PACKET_SIZE; i++)		printk(" %02x", cgc->cmd[i]);	printk(" - ");	if (sense == NULL) {		printk("no sense\n");		return;	}	printk("sense %02x.%02x.%02x", sense->sense_key, sense->asc, sense->ascq);	if (sense->sense_key > 8) {		printk(" (INVALID)\n");		return;	}	printk(" (%s)\n", info[sense->sense_key]);}/* * flush the drive cache to media */static int pkt_flush_cache(struct pktcdvd_device *pd){	struct packet_command cgc;	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);	cgc.cmd[0] = GPCMD_FLUSH_CACHE;	cgc.quiet = 1;	/*	 * the IMMED bit -- we default to not setting it, although that	 * would allow a much faster close, this is safer	 */#if 0	cgc.cmd[1] = 1 << 1;#endif	return pkt_generic_packet(pd, &cgc);}/* * speed is given as the normal factor, e.g. 4 for 4x */static int pkt_set_speed(struct pktcdvd_device *pd, unsigned write_speed, unsigned read_speed){	struct packet_command cgc;	struct request_sense sense;	int ret;	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);	cgc.sense = &sense;	cgc.cmd[0] = GPCMD_SET_SPEED;	cgc.cmd[2] = (read_speed >> 8) & 0xff;	cgc.cmd[3] = read_speed & 0xff;	cgc.cmd[4] = (write_speed >> 8) & 0xff;	cgc.cmd[5] = write_speed & 0xff;	if ((ret = pkt_generic_packet(pd, &cgc)))		pkt_dump_sense(&cgc);	return ret;}/* * 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){	spin_lock(&pd->iosched.lock);	if (bio_data_dir(bio) == READ) {		pkt_add_list_last(bio, &pd->iosched.read_queue,				  &pd->iosched.read_queue_tail);	} else {		pkt_add_list_last(bio, &pd->iosched.write_queue,				  &pd->iosched.write_queue_tail);	}	spin_unlock(&pd->iosched.lock);	atomic_set(&pd->iosched.attention, 1);	wake_up(&pd->wqueue);}/* * 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){	if (atomic_read(&pd->iosched.attention) == 0)		return;	atomic_set(&pd->iosched.attention, 0);	for (;;) {		struct bio *bio;		int reads_queued, writes_queued;		spin_lock(&pd->iosched.lock);		reads_queued = (pd->iosched.read_queue != NULL);		writes_queued = (pd->iosched.write_queue != NULL);		spin_unlock(&pd->iosched.lock);		if (!reads_queued && !writes_queued)			break;		if (pd->iosched.writing) {			int need_write_seek = 1;			spin_lock(&pd->iosched.lock);			bio = pd->iosched.write_queue;			spin_unlock(&pd->iosched.lock);			if (bio && (bio->bi_sector == pd->iosched.last_write))				need_write_seek = 0;			if (need_write_seek && reads_queued) {				if (atomic_read(&pd->cdrw.pending_bios) > 0) {					VPRINTK(DRIVER_NAME": write, waiting\n");					break;				}				pkt_flush_cache(pd);				pd->iosched.writing = 0;			}		} else {			if (!reads_queued && writes_queued) {				if (atomic_read(&pd->cdrw.pending_bios) > 0) {					VPRINTK(DRIVER_NAME": read, waiting\n");					break;				}				pd->iosched.writing = 1;			}		}		spin_lock(&pd->iosched.lock);		if (pd->iosched.writing) {			bio = pkt_get_list_first(&pd->iosched.write_queue,						 &pd->iosched.write_queue_tail);		} else {			bio = pkt_get_list_first(&pd->iosched.read_queue,						 &pd->iosched.read_queue_tail);		}		spin_unlock(&pd->iosched.lock);		if (!bio)			continue;		if (bio_data_dir(bio) == READ)			pd->iosched.successive_reads += bio->bi_size >> 10;		else {			pd->iosched.successive_reads = 0;			pd->iosched.last_write = bio->bi_sector + bio_sectors(bio);		}		if (pd->iosched.successive_reads >= HI_SPEED_SWITCH) {			if (pd->read_speed == pd->write_speed) {				pd->read_speed = MAX_SPEED;				pkt_set_speed(pd, pd->write_speed, pd->read_speed);			}		} else {			if (pd->read_speed != pd->write_speed) {				pd->read_speed = pd->write_speed;				pkt_set_speed(pd, pd->write_speed, pd->read_speed);			}		}		atomic_inc(&pd->cdrw.pending_bios);		generic_make_request(bio);	}}/* * 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){	if ((pd->settings.size << 9) / CD_FRAMESIZE <= q->max_phys_segments) {		/*		 * The cdrom device can handle one segment/frame		 */		clear_bit(PACKET_MERGE_SEGS, &pd->flags);		return 0;	} else if ((pd->settings.size << 9) / PAGE_SIZE <= q->max_phys_segments) {		/*		 * We can handle this case at the expense of some extra memory		 * copies during write operations		 */		set_bit(PACKET_MERGE_SEGS, &pd->flags);		return 0;	} else {		printk(DRIVER_NAME": cdrom max_phys_segments too small\n");		return -EIO;	}}/* * Copy CD_FRAMESIZE bytes from src_bio into a destination page */static void pkt_copy_bio_data(struct bio *src_bio, int seg, int offs, struct page *dst_page, int dst_offs){	unsigned int copy_size = CD_FRAMESIZE;	while (copy_size > 0) {		struct bio_vec *src_bvl = bio_iovec_idx(src_bio, seg);		void *vfrom = kmap_atomic(src_bvl->bv_page, KM_USER0) +			src_bvl->bv_offset + offs;		void *vto = page_address(dst_page) + dst_offs;		int len = min_t(int, copy_size, src_bvl->bv_len - offs);		BUG_ON(len < 0);		memcpy(vto, vfrom, len);		kunmap_atomic(vfrom, KM_USER0);		seg++;		offs = 0;		dst_offs += len;		copy_size -= len;	}}/* * Copy all data for this packet to pkt->pages[], so that

⌨️ 快捷键说明

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