[SPARC64]: Add Sun LDOM virtual disk driver.
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
4c521e422f
commit
667ef3c396
|
@ -423,6 +423,13 @@ config ATA_OVER_ETH
|
|||
This driver provides Support for ATA over Ethernet block
|
||||
devices like the Coraid EtherDrive (R) Storage Blade.
|
||||
|
||||
config SUNVDC
|
||||
tristate "Sun Virtual Disk Client support"
|
||||
depends on SUN_LDOMS
|
||||
help
|
||||
Support for virtual disk devices as a client under Sun
|
||||
Logical Domains.
|
||||
|
||||
source "drivers/s390/block/Kconfig"
|
||||
|
||||
endif # BLK_DEV
|
||||
|
|
|
@ -19,6 +19,7 @@ obj-$(CONFIG_BLK_CPQ_DA) += cpqarray.o
|
|||
obj-$(CONFIG_BLK_CPQ_CISS_DA) += cciss.o
|
||||
obj-$(CONFIG_BLK_DEV_DAC960) += DAC960.o
|
||||
obj-$(CONFIG_CDROM_PKTCDVD) += pktcdvd.o
|
||||
obj-$(CONFIG_SUNVDC) += sunvdc.o
|
||||
|
||||
obj-$(CONFIG_BLK_DEV_UMEM) += umem.o
|
||||
obj-$(CONFIG_BLK_DEV_NBD) += nbd.o
|
||||
|
|
|
@ -0,0 +1,970 @@
|
|||
/* sunvdc.c: Sun LDOM Virtual Disk Client.
|
||||
*
|
||||
* Copyright (C) 2007 David S. Miller <davem@davemloft.net>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/hdreg.h>
|
||||
#include <linux/genhd.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 <asm/vio.h>
|
||||
#include <asm/ldc.h>
|
||||
|
||||
#define DRV_MODULE_NAME "sunvdc"
|
||||
#define PFX DRV_MODULE_NAME ": "
|
||||
#define DRV_MODULE_VERSION "1.0"
|
||||
#define DRV_MODULE_RELDATE "June 25, 2007"
|
||||
|
||||
static char version[] __devinitdata =
|
||||
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 256
|
||||
|
||||
#define WAITING_FOR_LINK_UP 0x01
|
||||
#define WAITING_FOR_TX_SPACE 0x02
|
||||
#define WAITING_FOR_GEN_CMD 0x04
|
||||
#define WAITING_FOR_ANY -1
|
||||
|
||||
struct vdc_req_entry {
|
||||
struct request *req;
|
||||
};
|
||||
|
||||
struct vdc_port {
|
||||
struct vio_driver_state vio;
|
||||
|
||||
struct vdc *vp;
|
||||
|
||||
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;
|
||||
|
||||
/* The server fills these in for us in the disk attribute
|
||||
* ACK packet.
|
||||
*/
|
||||
u64 operations;
|
||||
u32 vdisk_size;
|
||||
u8 vdisk_type;
|
||||
u8 dev_no;
|
||||
|
||||
char disk_name[32];
|
||||
|
||||
struct vio_disk_geom geom;
|
||||
struct vio_disk_vtoc label;
|
||||
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static inline struct vdc_port *to_vdc_port(struct vio_driver_state *vio)
|
||||
{
|
||||
return container_of(vio, struct vdc_port, vio);
|
||||
}
|
||||
|
||||
struct vdc {
|
||||
/* Protects prot_list. */
|
||||
spinlock_t lock;
|
||||
|
||||
struct vio_dev *dev;
|
||||
|
||||
struct list_head port_list;
|
||||
};
|
||||
|
||||
/* Ordered from largest major to lowest */
|
||||
static struct vio_version vdc_versions[] = {
|
||||
{ .major = 1, .minor = 0 },
|
||||
};
|
||||
|
||||
#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 block_device *bdev, struct hd_geometry *geo)
|
||||
{
|
||||
struct gendisk *disk = bdev->bd_disk;
|
||||
struct vdc_port *port = disk->private_data;
|
||||
|
||||
geo->heads = (u8) port->geom.num_hd;
|
||||
geo->sectors = (u8) port->geom.num_sec;
|
||||
geo->cylinders = port->geom.num_cyl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct block_device_operations vdc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.getgeo = vdc_getgeo,
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
vdc_finish(vio, 0, WAITING_FOR_LINK_UP);
|
||||
}
|
||||
|
||||
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[%lu]\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[%lx] disk_size[%lu] disk_type[%x] "
|
||||
"xfer_mode[0x%x] blksz[%u] max_xfer[%lu]\n",
|
||||
pkt->tag.stype, pkt->operations,
|
||||
pkt->vdisk_size, pkt->vdisk_type,
|
||||
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_size = pkt->vdisk_size;
|
||||
port->vdisk_type = pkt->vdisk_type;
|
||||
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;
|
||||
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_request(struct request *req, int uptodate, int num_sectors)
|
||||
{
|
||||
if (end_that_request_first(req, uptodate, num_sectors))
|
||||
return;
|
||||
add_disk_randomness(req->rq_disk);
|
||||
end_that_request_last(req, uptodate);
|
||||
}
|
||||
|
||||
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 = (index + 1) & (VDC_TX_RING_SIZE - 1);
|
||||
|
||||
req = rqe->req;
|
||||
if (req == NULL) {
|
||||
vdc_end_special(port, desc);
|
||||
return;
|
||||
}
|
||||
|
||||
rqe->req = NULL;
|
||||
|
||||
vdc_end_request(req, !desc->status, desc->size >> 9);
|
||||
|
||||
if (blk_queue_stopped(port->disk->queue))
|
||||
blk_start_queue(port->disk->queue);
|
||||
}
|
||||
|
||||
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 ||
|
||||
event == LDC_EVENT_UP)) {
|
||||
vio_link_state_change(vio, event);
|
||||
spin_unlock_irqrestore(&vio->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
if (unlikely(event != LDC_EVENT_DATA_READY)) {
|
||||
printk(KERN_WARNING PFX "Unexpected LDC event %d\n", event);
|
||||
spin_unlock_irqrestore(&vio->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
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;
|
||||
} while (err == -EAGAIN);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __send_request(struct request *req)
|
||||
{
|
||||
struct vdc_port *port = req->rq_disk->private_data;
|
||||
struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING];
|
||||
struct scatterlist sg[port->ring_cookies];
|
||||
struct vdc_req_entry *rqe;
|
||||
struct vio_disk_desc *desc;
|
||||
unsigned int map_perm;
|
||||
int nsg, err, i;
|
||||
u64 len;
|
||||
u8 op;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
nsg = blk_rq_map_sg(req->q, req, sg);
|
||||
|
||||
len = 0;
|
||||
for (i = 0; i < nsg; i++)
|
||||
len += sg[i].length;
|
||||
|
||||
if (unlikely(vdc_tx_dring_avail(dr) < 1)) {
|
||||
blk_stop_queue(port->disk->queue);
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
rqe = &port->rq_arr[dr->prod];
|
||||
rqe->req = req;
|
||||
|
||||
desc->hdr.ack = VIO_ACK_ENABLE;
|
||||
desc->req_id = port->req_id;
|
||||
desc->operation = op;
|
||||
if (port->vdisk_type == VD_DISK_TYPE_DISK) {
|
||||
desc->slice = 2;
|
||||
} else {
|
||||
desc->slice = 0;
|
||||
}
|
||||
desc->status = ~0;
|
||||
desc->offset = (req->sector << 9) / port->vdisk_block_size;
|
||||
desc->size = len;
|
||||
desc->ncookies = err;
|
||||
|
||||
/* This has to be a non-SMP write barrier because we are writing
|
||||
* to memory which is shared with the peer LDOM.
|
||||
*/
|
||||
wmb();
|
||||
desc->hdr.state = VIO_DESC_READY;
|
||||
|
||||
err = __vdc_tx_trigger(port);
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR PFX "vdc_tx_trigger() failure, err=%d\n", err);
|
||||
} else {
|
||||
port->req_id++;
|
||||
dr->prod = (dr->prod + 1) & (VDC_TX_RING_SIZE - 1);
|
||||
}
|
||||
out:
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void do_vdc_request(request_queue_t *q)
|
||||
{
|
||||
while (1) {
|
||||
struct request *req = elv_next_request(q);
|
||||
|
||||
if (!req)
|
||||
break;
|
||||
|
||||
blkdev_dequeue_request(req);
|
||||
if (__send_request(req) < 0)
|
||||
vdc_end_request(req, 0, req->hard_nr_sectors);
|
||||
}
|
||||
}
|
||||
|
||||
static int generic_request(struct vdc_port *port, u8 op, void *buf, int len)
|
||||
{
|
||||
struct vio_dring_state *dr;
|
||||
struct vio_completion comp;
|
||||
struct vio_disk_desc *desc;
|
||||
unsigned int map_perm;
|
||||
unsigned long flags;
|
||||
int op_len, err;
|
||||
void *req_buf;
|
||||
|
||||
if (!(((u64)1 << ((u64)op - 1)) & port->operations))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
switch (op) {
|
||||
case VD_OP_BREAD:
|
||||
case VD_OP_BWRITE:
|
||||
default:
|
||||
return -EINVAL;
|
||||
|
||||
case VD_OP_FLUSH:
|
||||
op_len = 0;
|
||||
map_perm = 0;
|
||||
break;
|
||||
|
||||
case VD_OP_GET_WCE:
|
||||
op_len = sizeof(u32);
|
||||
map_perm = LDC_MAP_W;
|
||||
break;
|
||||
|
||||
case VD_OP_SET_WCE:
|
||||
op_len = sizeof(u32);
|
||||
map_perm = LDC_MAP_R;
|
||||
break;
|
||||
|
||||
case VD_OP_GET_VTOC:
|
||||
op_len = sizeof(struct vio_disk_vtoc);
|
||||
map_perm = LDC_MAP_W;
|
||||
break;
|
||||
|
||||
case VD_OP_SET_VTOC:
|
||||
op_len = sizeof(struct vio_disk_vtoc);
|
||||
map_perm = LDC_MAP_R;
|
||||
break;
|
||||
|
||||
case VD_OP_GET_DISKGEOM:
|
||||
op_len = sizeof(struct vio_disk_geom);
|
||||
map_perm = LDC_MAP_W;
|
||||
break;
|
||||
|
||||
case VD_OP_SET_DISKGEOM:
|
||||
op_len = sizeof(struct vio_disk_geom);
|
||||
map_perm = LDC_MAP_R;
|
||||
break;
|
||||
|
||||
case VD_OP_SCSICMD:
|
||||
op_len = 16;
|
||||
map_perm = LDC_MAP_RW;
|
||||
break;
|
||||
|
||||
case VD_OP_GET_DEVID:
|
||||
op_len = sizeof(struct vio_disk_devid);
|
||||
map_perm = LDC_MAP_W;
|
||||
break;
|
||||
|
||||
case VD_OP_GET_EFI:
|
||||
case VD_OP_SET_EFI:
|
||||
return -EOPNOTSUPP;
|
||||
break;
|
||||
};
|
||||
|
||||
map_perm |= LDC_MAP_SHADOW | LDC_MAP_DIRECT | LDC_MAP_IO;
|
||||
|
||||
op_len = (op_len + 7) & ~7;
|
||||
req_buf = kzalloc(op_len, GFP_KERNEL);
|
||||
if (!req_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (len > op_len)
|
||||
len = op_len;
|
||||
|
||||
if (map_perm & LDC_MAP_R)
|
||||
memcpy(req_buf, buf, len);
|
||||
|
||||
spin_lock_irqsave(&port->vio.lock, flags);
|
||||
|
||||
dr = &port->vio.drings[VIO_DRIVER_TX_RING];
|
||||
|
||||
/* XXX If we want to use this code generically we have to
|
||||
* XXX handle TX ring exhaustion etc.
|
||||
*/
|
||||
desc = vio_dring_cur(dr);
|
||||
|
||||
err = ldc_map_single(port->vio.lp, req_buf, op_len,
|
||||
desc->cookies, port->ring_cookies,
|
||||
map_perm);
|
||||
if (err < 0) {
|
||||
spin_unlock_irqrestore(&port->vio.lock, flags);
|
||||
kfree(req_buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
init_completion(&comp.com);
|
||||
comp.waiting_for = WAITING_FOR_GEN_CMD;
|
||||
port->vio.cmp = ∁
|
||||
|
||||
desc->hdr.ack = VIO_ACK_ENABLE;
|
||||
desc->req_id = port->req_id;
|
||||
desc->operation = op;
|
||||
desc->slice = 0;
|
||||
desc->status = ~0;
|
||||
desc->offset = 0;
|
||||
desc->size = op_len;
|
||||
desc->ncookies = err;
|
||||
|
||||
/* This has to be a non-SMP write barrier because we are writing
|
||||
* to memory which is shared with the peer LDOM.
|
||||
*/
|
||||
wmb();
|
||||
desc->hdr.state = VIO_DESC_READY;
|
||||
|
||||
err = __vdc_tx_trigger(port);
|
||||
if (err >= 0) {
|
||||
port->req_id++;
|
||||
dr->prod = (dr->prod + 1) & (VDC_TX_RING_SIZE - 1);
|
||||
spin_unlock_irqrestore(&port->vio.lock, flags);
|
||||
|
||||
wait_for_completion(&comp.com);
|
||||
err = comp.err;
|
||||
} else {
|
||||
port->vio.cmp = NULL;
|
||||
spin_unlock_irqrestore(&port->vio.lock, flags);
|
||||
}
|
||||
|
||||
if (map_perm & LDC_MAP_W)
|
||||
memcpy(buf, req_buf, len);
|
||||
|
||||
kfree(req_buf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devinit vdc_alloc_tx_ring(struct vdc_port *port)
|
||||
{
|
||||
struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING];
|
||||
unsigned long len, entry_size;
|
||||
int ncookies;
|
||||
void *dring;
|
||||
|
||||
entry_size = sizeof(struct vio_disk_desc) +
|
||||
(sizeof(struct ldc_trans_cookie) * port->ring_cookies);
|
||||
len = (VDC_TX_RING_SIZE * entry_size);
|
||||
|
||||
ncookies = VIO_MAX_RING_COOKIES;
|
||||
dring = ldc_alloc_exp_dring(port->vio.lp, len,
|
||||
dr->cookies, &ncookies,
|
||||
(LDC_MAP_SHADOW |
|
||||
LDC_MAP_DIRECT |
|
||||
LDC_MAP_RW));
|
||||
if (IS_ERR(dring))
|
||||
return PTR_ERR(dring);
|
||||
|
||||
dr->base = dring;
|
||||
dr->entry_size = entry_size;
|
||||
dr->num_entries = VDC_TX_RING_SIZE;
|
||||
dr->prod = dr->cons = 0;
|
||||
dr->pending = VDC_TX_RING_SIZE;
|
||||
dr->ncookies = ncookies;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vdc_free_tx_ring(struct vdc_port *port)
|
||||
{
|
||||
struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING];
|
||||
|
||||
if (dr->base) {
|
||||
ldc_free_exp_dring(port->vio.lp, dr->base,
|
||||
(dr->entry_size * dr->num_entries),
|
||||
dr->cookies, dr->ncookies);
|
||||
dr->base = NULL;
|
||||
dr->entry_size = 0;
|
||||
dr->num_entries = 0;
|
||||
dr->pending = 0;
|
||||
dr->ncookies = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int probe_disk(struct vdc_port *port)
|
||||
{
|
||||
struct vio_completion comp;
|
||||
struct request_queue *q;
|
||||
struct gendisk *g;
|
||||
int err;
|
||||
|
||||
init_completion(&comp.com);
|
||||
comp.err = 0;
|
||||
comp.waiting_for = WAITING_FOR_LINK_UP;
|
||||
port->vio.cmp = ∁
|
||||
|
||||
vio_port_up(&port->vio);
|
||||
|
||||
wait_for_completion(&comp.com);
|
||||
if (comp.err)
|
||||
return comp.err;
|
||||
|
||||
err = generic_request(port, VD_OP_GET_VTOC,
|
||||
&port->label, sizeof(port->label));
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR PFX "VD_OP_GET_VTOC returns error %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = generic_request(port, VD_OP_GET_DISKGEOM,
|
||||
&port->geom, sizeof(port->geom));
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR PFX "VD_OP_GET_DISKGEOM returns "
|
||||
"error %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
port->vdisk_size = ((u64)port->geom.num_cyl *
|
||||
(u64)port->geom.num_hd *
|
||||
(u64)port->geom.num_sec);
|
||||
|
||||
q = blk_init_queue(do_vdc_request, &port->vio.lock);
|
||||
if (!q) {
|
||||
printk(KERN_ERR PFX "%s: Could not allocate queue.\n",
|
||||
port->vio.name);
|
||||
return -ENOMEM;
|
||||
}
|
||||
g = alloc_disk(1 << PARTITION_SHIFT);
|
||||
if (!g) {
|
||||
printk(KERN_ERR PFX "%s: Could not allocate gendisk.\n",
|
||||
port->vio.name);
|
||||
blk_cleanup_queue(q);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
port->disk = g;
|
||||
|
||||
blk_queue_max_hw_segments(q, port->ring_cookies);
|
||||
blk_queue_max_phys_segments(q, port->ring_cookies);
|
||||
blk_queue_max_sectors(q, port->max_xfer_size);
|
||||
g->major = vdc_major;
|
||||
g->first_minor = port->dev_no << PARTITION_SHIFT;
|
||||
strcpy(g->disk_name, port->disk_name);
|
||||
|
||||
g->fops = &vdc_fops;
|
||||
g->queue = q;
|
||||
g->private_data = port;
|
||||
g->driverfs_dev = &port->vio.vdev->dev;
|
||||
|
||||
set_capacity(g, port->vdisk_size);
|
||||
|
||||
printk(KERN_INFO PFX "%s: %u sectors (%u MB)\n",
|
||||
g->disk_name,
|
||||
port->vdisk_size, (port->vdisk_size >> (20 - 9)));
|
||||
|
||||
add_disk(g);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ldc_channel_config vdc_ldc_cfg = {
|
||||
.event = vdc_event,
|
||||
.mtu = 64,
|
||||
.mode = LDC_MODE_UNRELIABLE,
|
||||
};
|
||||
|
||||
static struct vio_driver_ops vdc_vio_ops = {
|
||||
.send_attr = vdc_send_attr,
|
||||
.handle_attr = vdc_handle_attr,
|
||||
.handshake_complete = vdc_handshake_complete,
|
||||
};
|
||||
|
||||
static int __devinit vdc_port_probe(struct vio_dev *vdev,
|
||||
const struct vio_device_id *id)
|
||||
{
|
||||
struct mdesc_node *endp;
|
||||
struct vdc_port *port;
|
||||
unsigned long flags;
|
||||
struct vdc *vp;
|
||||
const u64 *port_id;
|
||||
int err;
|
||||
|
||||
vp = dev_get_drvdata(vdev->dev.parent);
|
||||
if (!vp) {
|
||||
printk(KERN_ERR PFX "Cannot find port parent vdc.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
endp = vio_find_endpoint(vdev);
|
||||
if (!endp) {
|
||||
printk(KERN_ERR PFX "Port lacks channel-endpoint.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
port_id = md_get_property(vdev->mp, "id", NULL);
|
||||
if (!port_id) {
|
||||
printk(KERN_ERR PFX "Port lacks id property.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if ((*port_id << PARTITION_SHIFT) & ~(u64)MINORMASK) {
|
||||
printk(KERN_ERR PFX "Port id [%lu] too large.\n", *port_id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
port = kzalloc(sizeof(*port), GFP_KERNEL);
|
||||
if (!port) {
|
||||
printk(KERN_ERR PFX "Cannot allocate vdc_port.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
port->vp = vp;
|
||||
port->dev_no = *port_id;
|
||||
|
||||
if (port->dev_no >= 26)
|
||||
snprintf(port->disk_name, sizeof(port->disk_name),
|
||||
VDCBLK_NAME "%c%c",
|
||||
'a' + (port->dev_no / 26) - 1,
|
||||
'a' + (port->dev_no % 26));
|
||||
else
|
||||
snprintf(port->disk_name, sizeof(port->disk_name),
|
||||
VDCBLK_NAME "%c", 'a' + (port->dev_no % 26));
|
||||
|
||||
err = vio_driver_init(&port->vio, vdev, VDEV_DISK, endp,
|
||||
vdc_versions, ARRAY_SIZE(vdc_versions),
|
||||
&vdc_vio_ops, port->disk_name);
|
||||
if (err)
|
||||
goto err_out_free_port;
|
||||
|
||||
port->vdisk_block_size = 512;
|
||||
port->max_xfer_size = ((128 * 1024) / port->vdisk_block_size);
|
||||
port->ring_cookies = ((port->max_xfer_size *
|
||||
port->vdisk_block_size) / PAGE_SIZE) + 2;
|
||||
|
||||
err = vio_ldc_alloc(&port->vio, &vdc_ldc_cfg, port);
|
||||
if (err)
|
||||
goto err_out_free_port;
|
||||
|
||||
err = vdc_alloc_tx_ring(port);
|
||||
if (err)
|
||||
goto err_out_free_ldc;
|
||||
|
||||
err = probe_disk(port);
|
||||
if (err)
|
||||
goto err_out_free_tx_ring;
|
||||
|
||||
INIT_LIST_HEAD(&port->list);
|
||||
|
||||
spin_lock_irqsave(&vp->lock, flags);
|
||||
list_add(&port->list, &vp->port_list);
|
||||
spin_unlock_irqrestore(&vp->lock, flags);
|
||||
|
||||
dev_set_drvdata(&vdev->dev, port);
|
||||
|
||||
return 0;
|
||||
|
||||
err_out_free_tx_ring:
|
||||
vdc_free_tx_ring(port);
|
||||
|
||||
err_out_free_ldc:
|
||||
vio_ldc_free(&port->vio);
|
||||
|
||||
err_out_free_port:
|
||||
kfree(port);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int vdc_port_remove(struct vio_dev *vdev)
|
||||
{
|
||||
struct vdc_port *port = dev_get_drvdata(&vdev->dev);
|
||||
|
||||
if (port) {
|
||||
del_timer_sync(&port->vio.timer);
|
||||
|
||||
vdc_free_tx_ring(port);
|
||||
vio_ldc_free(&port->vio);
|
||||
|
||||
dev_set_drvdata(&vdev->dev, NULL);
|
||||
|
||||
kfree(port);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct vio_device_id vdc_port_match[] = {
|
||||
{
|
||||
.type = "vdc-port",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(vio, vdc_match);
|
||||
|
||||
static struct vio_driver vdc_port_driver = {
|
||||
.id_table = vdc_port_match,
|
||||
.probe = vdc_port_probe,
|
||||
.remove = vdc_port_remove,
|
||||
.driver = {
|
||||
.name = "vdc_port",
|
||||
.owner = THIS_MODULE,
|
||||
}
|
||||
};
|
||||
|
||||
static int __devinit vdc_probe(struct vio_dev *vdev,
|
||||
const struct vio_device_id *id)
|
||||
{
|
||||
static int vdc_version_printed;
|
||||
struct vdc *vp;
|
||||
|
||||
if (vdc_version_printed++ == 0)
|
||||
printk(KERN_INFO "%s", version);
|
||||
|
||||
vp = kzalloc(sizeof(struct vdc), GFP_KERNEL);
|
||||
if (!vp)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&vp->lock);
|
||||
vp->dev = vdev;
|
||||
INIT_LIST_HEAD(&vp->port_list);
|
||||
|
||||
dev_set_drvdata(&vdev->dev, vp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vdc_remove(struct vio_dev *vdev)
|
||||
{
|
||||
|
||||
struct vdc *vp = dev_get_drvdata(&vdev->dev);
|
||||
|
||||
if (vp) {
|
||||
kfree(vp);
|
||||
dev_set_drvdata(&vdev->dev, NULL);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct vio_device_id vdc_match[] = {
|
||||
{
|
||||
.type = "block",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(vio, vdc_match);
|
||||
|
||||
static struct vio_driver vdc_driver = {
|
||||
.id_table = vdc_match,
|
||||
.probe = vdc_probe,
|
||||
.remove = vdc_remove,
|
||||
.driver = {
|
||||
.name = "vdc",
|
||||
.owner = THIS_MODULE,
|
||||
}
|
||||
};
|
||||
|
||||
static int __init vdc_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = register_blkdev(0, VDCBLK_NAME);
|
||||
if (err < 0)
|
||||
goto out_err;
|
||||
|
||||
vdc_major = err;
|
||||
err = vio_register_driver(&vdc_driver);
|
||||
if (err)
|
||||
goto out_unregister_blkdev;
|
||||
|
||||
err = vio_register_driver(&vdc_port_driver);
|
||||
if (err)
|
||||
goto out_unregister_vdc;
|
||||
|
||||
return 0;
|
||||
|
||||
out_unregister_vdc:
|
||||
vio_unregister_driver(&vdc_driver);
|
||||
|
||||
out_unregister_blkdev:
|
||||
unregister_blkdev(vdc_major, VDCBLK_NAME);
|
||||
vdc_major = 0;
|
||||
|
||||
out_err:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit vdc_exit(void)
|
||||
{
|
||||
vio_unregister_driver(&vdc_port_driver);
|
||||
vio_unregister_driver(&vdc_driver);
|
||||
unregister_blkdev(vdc_major, VDCBLK_NAME);
|
||||
}
|
||||
|
||||
module_init(vdc_init);
|
||||
module_exit(vdc_exit);
|
Loading…
Reference in New Issue