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