1252 lines · 28.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* sunvdc.c: Sun LDOM Virtual Disk Client. * * Copyright (C) 2007, 2008 David S. Miller <davem@davemloft.net> */ #include <linux/module.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/blk-mq.h>#include <linux/hdreg.h>#include <linux/cdrom.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/completion.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/list.h>#include <linux/scatterlist.h> #include <asm/vio.h>#include <asm/ldc.h> #define DRV_MODULE_NAME		"sunvdc"#define PFX DRV_MODULE_NAME	": "#define DRV_MODULE_VERSION	"1.2"#define DRV_MODULE_RELDATE	"November 24, 2014" static char version[] =	DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n";MODULE_AUTHOR("David S. Miller <davem@davemloft.net>");MODULE_DESCRIPTION("Sun LDOM virtual disk client driver");MODULE_LICENSE("GPL");MODULE_VERSION(DRV_MODULE_VERSION); #define VDC_TX_RING_SIZE	512#define VDC_DEFAULT_BLK_SIZE	512 #define MAX_XFER_BLKS		(128 * 1024)#define MAX_XFER_SIZE		(MAX_XFER_BLKS / VDC_DEFAULT_BLK_SIZE)#define MAX_RING_COOKIES	((MAX_XFER_BLKS / PAGE_SIZE) + 2) #define WAITING_FOR_LINK_UP	0x01#define WAITING_FOR_TX_SPACE	0x02#define WAITING_FOR_GEN_CMD	0x04#define WAITING_FOR_ANY		-1 #define	VDC_MAX_RETRIES	10 static struct workqueue_struct *sunvdc_wq; struct vdc_req_entry {	struct request		*req;}; struct vdc_port {	struct vio_driver_state	vio; 	struct gendisk		*disk; 	struct vdc_completion	*cmp; 	u64			req_id;	u64			seq;	struct vdc_req_entry	rq_arr[VDC_TX_RING_SIZE]; 	unsigned long		ring_cookies; 	u64			max_xfer_size;	u32			vdisk_block_size;	u32			drain; 	u64			ldc_timeout;	struct delayed_work	ldc_reset_timer_work;	struct work_struct	ldc_reset_work; 	/* The server fills these in for us in the disk attribute	 * ACK packet.	 */	u64			operations;	u32			vdisk_size;	u8			vdisk_type;	u8			vdisk_mtype;	u32			vdisk_phys_blksz; 	struct blk_mq_tag_set	tag_set; 	char			disk_name[32];}; static void vdc_ldc_reset(struct vdc_port *port);static void vdc_ldc_reset_work(struct work_struct *work);static void vdc_ldc_reset_timer_work(struct work_struct *work); static inline struct vdc_port *to_vdc_port(struct vio_driver_state *vio){	return container_of(vio, struct vdc_port, vio);} /* Ordered from largest major to lowest */static struct vio_version vdc_versions[] = {	{ .major = 1, .minor = 2 },	{ .major = 1, .minor = 1 },	{ .major = 1, .minor = 0 },}; static inline int vdc_version_supported(struct vdc_port *port,					u16 major, u16 minor){	return port->vio.ver.major == major && port->vio.ver.minor >= minor;} #define VDCBLK_NAME	"vdisk"static int vdc_major;#define PARTITION_SHIFT	3 static inline u32 vdc_tx_dring_avail(struct vio_dring_state *dr){	return vio_dring_avail(dr, VDC_TX_RING_SIZE);} static int vdc_getgeo(struct gendisk *disk, struct hd_geometry *geo){	sector_t nsect = get_capacity(disk);	sector_t cylinders = nsect; 	geo->heads = 0xff;	geo->sectors = 0x3f;	sector_div(cylinders, geo->heads * geo->sectors);	geo->cylinders = cylinders;	if ((sector_t)(geo->cylinders + 1) * geo->heads * geo->sectors < nsect)		geo->cylinders = 0xffff; 	return 0;} /* Add ioctl/CDROM_GET_CAPABILITY to support cdrom_id in udev * when vdisk_mtype is VD_MEDIA_TYPE_CD or VD_MEDIA_TYPE_DVD. * Needed to be able to install inside an ldom from an iso image. */static int vdc_ioctl(struct block_device *bdev, blk_mode_t mode,		     unsigned command, unsigned long argument){	struct vdc_port *port = bdev->bd_disk->private_data;	int i; 	switch (command) {	case CDROMMULTISESSION:		pr_debug(PFX "Multisession CDs not supported\n");		for (i = 0; i < sizeof(struct cdrom_multisession); i++)			if (put_user(0, (char __user *)(argument + i)))				return -EFAULT;		return 0; 	case CDROM_GET_CAPABILITY:		if (!vdc_version_supported(port, 1, 1))			return -EINVAL;		switch (port->vdisk_mtype) {		case VD_MEDIA_TYPE_CD:		case VD_MEDIA_TYPE_DVD:			return 0;		default:			return -EINVAL;		}	default:		pr_debug(PFX "ioctl %08x not supported\n", command);		return -EINVAL;	}} static const struct block_device_operations vdc_fops = {	.owner		= THIS_MODULE,	.getgeo		= vdc_getgeo,	.ioctl		= vdc_ioctl,	.compat_ioctl	= blkdev_compat_ptr_ioctl,}; static void vdc_blk_queue_start(struct vdc_port *port){	struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; 	/* restart blk queue when ring is half emptied. also called after	 * handshake completes, so check for initial handshake before we've	 * allocated a disk.	 */	if (port->disk && vdc_tx_dring_avail(dr) * 100 / VDC_TX_RING_SIZE >= 50)		blk_mq_start_stopped_hw_queues(port->disk->queue, true);} static void vdc_finish(struct vio_driver_state *vio, int err, int waiting_for){	if (vio->cmp &&	    (waiting_for == -1 ||	     vio->cmp->waiting_for == waiting_for)) {		vio->cmp->err = err;		complete(&vio->cmp->com);		vio->cmp = NULL;	}} static void vdc_handshake_complete(struct vio_driver_state *vio){	struct vdc_port *port = to_vdc_port(vio); 	cancel_delayed_work(&port->ldc_reset_timer_work);	vdc_finish(vio, 0, WAITING_FOR_LINK_UP);	vdc_blk_queue_start(port);} static int vdc_handle_unknown(struct vdc_port *port, void *arg){	struct vio_msg_tag *pkt = arg; 	printk(KERN_ERR PFX "Received unknown msg [%02x:%02x:%04x:%08x]\n",	       pkt->type, pkt->stype, pkt->stype_env, pkt->sid);	printk(KERN_ERR PFX "Resetting connection.\n"); 	ldc_disconnect(port->vio.lp); 	return -ECONNRESET;} static int vdc_send_attr(struct vio_driver_state *vio){	struct vdc_port *port = to_vdc_port(vio);	struct vio_disk_attr_info pkt; 	memset(&pkt, 0, sizeof(pkt)); 	pkt.tag.type = VIO_TYPE_CTRL;	pkt.tag.stype = VIO_SUBTYPE_INFO;	pkt.tag.stype_env = VIO_ATTR_INFO;	pkt.tag.sid = vio_send_sid(vio); 	pkt.xfer_mode = VIO_DRING_MODE;	pkt.vdisk_block_size = port->vdisk_block_size;	pkt.max_xfer_size = port->max_xfer_size; 	viodbg(HS, "SEND ATTR xfer_mode[0x%x] blksz[%u] max_xfer[%llu]\n",	       pkt.xfer_mode, pkt.vdisk_block_size, pkt.max_xfer_size); 	return vio_ldc_send(&port->vio, &pkt, sizeof(pkt));} static int vdc_handle_attr(struct vio_driver_state *vio, void *arg){	struct vdc_port *port = to_vdc_port(vio);	struct vio_disk_attr_info *pkt = arg; 	viodbg(HS, "GOT ATTR stype[0x%x] ops[%llx] disk_size[%llu] disk_type[%x] "	       "mtype[0x%x] xfer_mode[0x%x] blksz[%u] max_xfer[%llu]\n",	       pkt->tag.stype, pkt->operations,	       pkt->vdisk_size, pkt->vdisk_type, pkt->vdisk_mtype,	       pkt->xfer_mode, pkt->vdisk_block_size,	       pkt->max_xfer_size); 	if (pkt->tag.stype == VIO_SUBTYPE_ACK) {		switch (pkt->vdisk_type) {		case VD_DISK_TYPE_DISK:		case VD_DISK_TYPE_SLICE:			break; 		default:			printk(KERN_ERR PFX "%s: Bogus vdisk_type 0x%x\n",			       vio->name, pkt->vdisk_type);			return -ECONNRESET;		} 		if (pkt->vdisk_block_size > port->vdisk_block_size) {			printk(KERN_ERR PFX "%s: BLOCK size increased "			       "%u --> %u\n",			       vio->name,			       port->vdisk_block_size, pkt->vdisk_block_size);			return -ECONNRESET;		} 		port->operations = pkt->operations;		port->vdisk_type = pkt->vdisk_type;		if (vdc_version_supported(port, 1, 1)) {			port->vdisk_size = pkt->vdisk_size;			port->vdisk_mtype = pkt->vdisk_mtype;		}		if (pkt->max_xfer_size < port->max_xfer_size)			port->max_xfer_size = pkt->max_xfer_size;		port->vdisk_block_size = pkt->vdisk_block_size; 		port->vdisk_phys_blksz = VDC_DEFAULT_BLK_SIZE;		if (vdc_version_supported(port, 1, 2))			port->vdisk_phys_blksz = pkt->phys_block_size; 		return 0;	} else {		printk(KERN_ERR PFX "%s: Attribute NACK\n", vio->name); 		return -ECONNRESET;	}} static void vdc_end_special(struct vdc_port *port, struct vio_disk_desc *desc){	int err = desc->status; 	vdc_finish(&port->vio, -err, WAITING_FOR_GEN_CMD);} static void vdc_end_one(struct vdc_port *port, struct vio_dring_state *dr,			unsigned int index){	struct vio_disk_desc *desc = vio_dring_entry(dr, index);	struct vdc_req_entry *rqe = &port->rq_arr[index];	struct request *req; 	if (unlikely(desc->hdr.state != VIO_DESC_DONE))		return; 	ldc_unmap(port->vio.lp, desc->cookies, desc->ncookies);	desc->hdr.state = VIO_DESC_FREE;	dr->cons = vio_dring_next(dr, index); 	req = rqe->req;	if (req == NULL) {		vdc_end_special(port, desc);		return;	} 	rqe->req = NULL; 	blk_mq_end_request(req, desc->status ? BLK_STS_IOERR : 0); 	vdc_blk_queue_start(port);} static int vdc_ack(struct vdc_port *port, void *msgbuf){	struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING];	struct vio_dring_data *pkt = msgbuf; 	if (unlikely(pkt->dring_ident != dr->ident ||		     pkt->start_idx != pkt->end_idx ||		     pkt->start_idx >= VDC_TX_RING_SIZE))		return 0; 	vdc_end_one(port, dr, pkt->start_idx); 	return 0;} static int vdc_nack(struct vdc_port *port, void *msgbuf){	/* XXX Implement me XXX */	return 0;} static void vdc_event(void *arg, int event){	struct vdc_port *port = arg;	struct vio_driver_state *vio = &port->vio;	unsigned long flags;	int err; 	spin_lock_irqsave(&vio->lock, flags); 	if (unlikely(event == LDC_EVENT_RESET)) {		vio_link_state_change(vio, event);		queue_work(sunvdc_wq, &port->ldc_reset_work);		goto out;	} 	if (unlikely(event == LDC_EVENT_UP)) {		vio_link_state_change(vio, event);		goto out;	} 	if (unlikely(event != LDC_EVENT_DATA_READY)) {		pr_warn(PFX "Unexpected LDC event %d\n", event);		goto out;	} 	err = 0;	while (1) {		union {			struct vio_msg_tag tag;			u64 raw[8];		} msgbuf; 		err = ldc_read(vio->lp, &msgbuf, sizeof(msgbuf));		if (unlikely(err < 0)) {			if (err == -ECONNRESET)				vio_conn_reset(vio);			break;		}		if (err == 0)			break;		viodbg(DATA, "TAG [%02x:%02x:%04x:%08x]\n",		       msgbuf.tag.type,		       msgbuf.tag.stype,		       msgbuf.tag.stype_env,		       msgbuf.tag.sid);		err = vio_validate_sid(vio, &msgbuf.tag);		if (err < 0)			break; 		if (likely(msgbuf.tag.type == VIO_TYPE_DATA)) {			if (msgbuf.tag.stype == VIO_SUBTYPE_ACK)				err = vdc_ack(port, &msgbuf);			else if (msgbuf.tag.stype == VIO_SUBTYPE_NACK)				err = vdc_nack(port, &msgbuf);			else				err = vdc_handle_unknown(port, &msgbuf);		} else if (msgbuf.tag.type == VIO_TYPE_CTRL) {			err = vio_control_pkt_engine(vio, &msgbuf);		} else {			err = vdc_handle_unknown(port, &msgbuf);		}		if (err < 0)			break;	}	if (err < 0)		vdc_finish(&port->vio, err, WAITING_FOR_ANY);out:	spin_unlock_irqrestore(&vio->lock, flags);} static int __vdc_tx_trigger(struct vdc_port *port){	struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING];	struct vio_dring_data hdr = {		.tag = {			.type		= VIO_TYPE_DATA,			.stype		= VIO_SUBTYPE_INFO,			.stype_env	= VIO_DRING_DATA,			.sid		= vio_send_sid(&port->vio),		},		.dring_ident		= dr->ident,		.start_idx		= dr->prod,		.end_idx		= dr->prod,	};	int err, delay;	int retries = 0; 	hdr.seq = dr->snd_nxt;	delay = 1;	do {		err = vio_ldc_send(&port->vio, &hdr, sizeof(hdr));		if (err > 0) {			dr->snd_nxt++;			break;		}		udelay(delay);		if ((delay <<= 1) > 128)			delay = 128;		if (retries++ > VDC_MAX_RETRIES)			break;	} while (err == -EAGAIN); 	if (err == -ENOTCONN)		vdc_ldc_reset(port);	return err;} static int __send_request(struct request *req){	struct vdc_port *port = req->q->disk->private_data;	struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING];	struct scatterlist sg[MAX_RING_COOKIES];	struct vdc_req_entry *rqe;	struct vio_disk_desc *desc;	unsigned int map_perm;	int nsg, err, i;	u64 len;	u8 op; 	if (WARN_ON(port->ring_cookies > MAX_RING_COOKIES))		return -EINVAL; 	map_perm = LDC_MAP_SHADOW | LDC_MAP_DIRECT | LDC_MAP_IO; 	if (rq_data_dir(req) == READ) {		map_perm |= LDC_MAP_W;		op = VD_OP_BREAD;	} else {		map_perm |= LDC_MAP_R;		op = VD_OP_BWRITE;	} 	sg_init_table(sg, port->ring_cookies);	nsg = blk_rq_map_sg(req, sg); 	len = 0;	for (i = 0; i < nsg; i++)		len += sg[i].length; 	desc = vio_dring_cur(dr); 	err = ldc_map_sg(port->vio.lp, sg, nsg,			 desc->cookies, port->ring_cookies,			 map_perm);	if (err < 0) {		printk(KERN_ERR PFX "ldc_map_sg() failure, err=%d.\n", err);		return err;

Lines 1–500 of 1252.