/* 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/blkdev.h>
#include <linux/hdreg.h>
#include <linux/genhd.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.1"
#define DRV_MODULE_RELDATE "February 13, 2013"
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 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 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 vdisk_mtype;
char disk_name[32];
};
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 = 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 block_device *bdev, struct hd_geometry *geo)
{
struct gendisk *disk = bdev->bd_disk;
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, fmode_t mode,
unsigned command, unsigned long argument)
{
int i;
struct gendisk *disk;
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:
disk = bdev->bd_disk;
if (bdev->bd_disk && (disk->flags & GENHD_FL_CD))
return 0;
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,
};
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 vi