/*
* Linux driver for System z and s390 unit record devices
* (z/VM virtual punch, reader, printer)
*
* Copyright IBM Corp. 2001, 2009
* Authors: Malcolm Beattie <beattiem@uk.ibm.com>
* Michael Holzheu <holzheu@de.ibm.com>
* Frank Munzert <munzert@de.ibm.com>
*/
#define KMSG_COMPONENT "vmur"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <asm/cio.h>
#include <asm/ccwdev.h>
#include <asm/debug.h>
#include <asm/diag.h>
#include "vmur.h"
/*
* Driver overview
*
* Unit record device support is implemented as a character device driver.
* We can fit at least 16 bits into a device minor number and use the
* simple method of mapping a character device number with minor abcd
* to the unit record device with devno abcd.
* I/O to virtual unit record devices is handled as follows:
* Reads: Diagnose code 0x14 (input spool file manipulation)
* is used to read spool data page-wise.
* Writes: The CCW used is WRITE_CCW_CMD (0x01). The device's record length
* is available by reading sysfs attr reclen. Each write() to the device
* must specify an integral multiple (maximal 511) of reclen.
*/
static char ur_banner[] = "z/VM virtual unit record device driver";
MODULE_AUTHOR("IBM Corporation");
MODULE_DESCRIPTION("s390 z/VM virtual unit record device driver");
MODULE_LICENSE("GPL");
static dev_t ur_first_dev_maj_min;
static struct class *vmur_class;
static struct debug_info *vmur_dbf;
/* We put the device's record length (for writes) in the driver_info field */
static struct ccw_device_id ur_ids[] = {
{ CCWDEV_CU_DI(READER_PUNCH_DEVTYPE, 80) },
{ CCWDEV_CU_DI(PRINTER_DEVTYPE, 132) },
{ /* end of list */ }
};
MODULE_DEVICE_TABLE(ccw, ur_ids);
static int ur_probe(struct ccw_device *cdev);
static void ur_remove(struct ccw_device *cdev);
static int ur_set_online(struct ccw_device *cdev);
static int ur_set_offline(struct ccw_device *cdev);
static int ur_pm_suspend(struct ccw_device *cdev);
static struct ccw_driver ur_driver = {
.driver = {
.name = "vmur",
.owner = THIS_MODULE,
},
.ids = ur_ids,
.probe = ur_probe,
.remove = ur_remove,
.set_online = ur_set_online,
.set_offline = ur_set_offline,
.freeze = ur_pm_suspend,
.int_class = IRQIO_VMR,
};
static DEFINE_MUTEX(vmur_mutex);
/*
* Allocation, freeing, getting and putting of urdev structures
*
* Each ur device (urd) contains a reference to its corresponding ccw device
* (cdev) using the urd->cdev pointer. Each ccw device has a reference to the
* ur device using dev_get_drvdata(&cdev->dev) pointer.
*
* urd references:
* - ur_probe gets a urd reference, ur_remove drops the reference
* dev_get_drvdata(&cdev->dev)
* - ur_open gets a urd reference, ur_relase drops the reference
* (urf->urd)
*
* cdev references:
* - urdev_alloc get a cdev reference (urd->cdev)
* - urdev_free drops the cdev reference (urd->cdev)
*
* Setting and clearing of dev_get_drvdata(&cdev->dev) is protected by the ccwdev lock
*/
static struct urdev *urdev_alloc(struct ccw_device *cdev)
{
struct urdev *urd;
urd = kzalloc(sizeof(struct urdev), GFP_KERNEL);
if (!urd)
return NULL;
urd->reclen = cdev->id.driver_info;
ccw_device_get_id(cdev, &urd->dev_id);
mutex_init(&urd->io_mutex);
init_waitqueue_head(&urd->wait);
spin_lock_init(&urd->open_lock);
atomic_set(&urd->ref_count, 1);
urd->cdev = cdev;
get_device(&cdev->dev);
return urd;
}
static void urdev_free(struct urdev *urd)
{
TRACE("urdev_free: %p\n", urd);
if (urd->cdev)
put_device(&urd->cdev->dev);
kfree(urd);
}
static void urdev_get(struct urdev *urd)
{
atomic_inc(&urd->ref_count);
}
static struct urdev *urdev_get_from_cdev(struct ccw_device *cdev)
{
struct urdev *urd;
unsigned long flags;
spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
urd = dev_get_drvdata(&cdev->dev);
if (urd)
urdev_get(urd);
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
return urd;
}
static struct urdev *urdev_get_from_devno(u16 devno)
{
char bus_id[16];
struct ccw_device *cdev;
struct urdev *urd;
sprintf(bus_id, "0.0.%04x", devno);
cdev = get_ccwdev_by_busid(&ur_driver, bus_id);
if (