162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Linux driver for System z and s390 unit record devices 462306a36Sopenharmony_ci * (z/VM virtual punch, reader, printer) 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright IBM Corp. 2001, 2009 762306a36Sopenharmony_ci * Authors: Malcolm Beattie <beattiem@uk.ibm.com> 862306a36Sopenharmony_ci * Michael Holzheu <holzheu@de.ibm.com> 962306a36Sopenharmony_ci * Frank Munzert <munzert@de.ibm.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define KMSG_COMPONENT "vmur" 1362306a36Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/cdev.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/kobject.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <linux/uaccess.h> 2162306a36Sopenharmony_ci#include <asm/cio.h> 2262306a36Sopenharmony_ci#include <asm/ccwdev.h> 2362306a36Sopenharmony_ci#include <asm/debug.h> 2462306a36Sopenharmony_ci#include <asm/diag.h> 2562306a36Sopenharmony_ci#include <asm/scsw.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include "vmur.h" 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* 3062306a36Sopenharmony_ci * Driver overview 3162306a36Sopenharmony_ci * 3262306a36Sopenharmony_ci * Unit record device support is implemented as a character device driver. 3362306a36Sopenharmony_ci * We can fit at least 16 bits into a device minor number and use the 3462306a36Sopenharmony_ci * simple method of mapping a character device number with minor abcd 3562306a36Sopenharmony_ci * to the unit record device with devno abcd. 3662306a36Sopenharmony_ci * I/O to virtual unit record devices is handled as follows: 3762306a36Sopenharmony_ci * Reads: Diagnose code 0x14 (input spool file manipulation) 3862306a36Sopenharmony_ci * is used to read spool data page-wise. 3962306a36Sopenharmony_ci * Writes: The CCW used is WRITE_CCW_CMD (0x01). The device's record length 4062306a36Sopenharmony_ci * is available by reading sysfs attr reclen. Each write() to the device 4162306a36Sopenharmony_ci * must specify an integral multiple (maximal 511) of reclen. 4262306a36Sopenharmony_ci */ 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic char ur_banner[] = "z/VM virtual unit record device driver"; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ciMODULE_AUTHOR("IBM Corporation"); 4762306a36Sopenharmony_ciMODULE_DESCRIPTION("s390 z/VM virtual unit record device driver"); 4862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic dev_t ur_first_dev_maj_min; 5162306a36Sopenharmony_cistatic struct class *vmur_class; 5262306a36Sopenharmony_cistatic struct debug_info *vmur_dbf; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/* We put the device's record length (for writes) in the driver_info field */ 5562306a36Sopenharmony_cistatic struct ccw_device_id ur_ids[] = { 5662306a36Sopenharmony_ci { CCWDEV_CU_DI(READER_PUNCH_DEVTYPE, 80) }, 5762306a36Sopenharmony_ci { CCWDEV_CU_DI(PRINTER_DEVTYPE, 132) }, 5862306a36Sopenharmony_ci { /* end of list */ } 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(ccw, ur_ids); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic int ur_probe(struct ccw_device *cdev); 6462306a36Sopenharmony_cistatic void ur_remove(struct ccw_device *cdev); 6562306a36Sopenharmony_cistatic int ur_set_online(struct ccw_device *cdev); 6662306a36Sopenharmony_cistatic int ur_set_offline(struct ccw_device *cdev); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic struct ccw_driver ur_driver = { 6962306a36Sopenharmony_ci .driver = { 7062306a36Sopenharmony_ci .name = "vmur", 7162306a36Sopenharmony_ci .owner = THIS_MODULE, 7262306a36Sopenharmony_ci }, 7362306a36Sopenharmony_ci .ids = ur_ids, 7462306a36Sopenharmony_ci .probe = ur_probe, 7562306a36Sopenharmony_ci .remove = ur_remove, 7662306a36Sopenharmony_ci .set_online = ur_set_online, 7762306a36Sopenharmony_ci .set_offline = ur_set_offline, 7862306a36Sopenharmony_ci .int_class = IRQIO_VMR, 7962306a36Sopenharmony_ci}; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic DEFINE_MUTEX(vmur_mutex); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic void ur_uevent(struct work_struct *ws); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci/* 8662306a36Sopenharmony_ci * Allocation, freeing, getting and putting of urdev structures 8762306a36Sopenharmony_ci * 8862306a36Sopenharmony_ci * Each ur device (urd) contains a reference to its corresponding ccw device 8962306a36Sopenharmony_ci * (cdev) using the urd->cdev pointer. Each ccw device has a reference to the 9062306a36Sopenharmony_ci * ur device using dev_get_drvdata(&cdev->dev) pointer. 9162306a36Sopenharmony_ci * 9262306a36Sopenharmony_ci * urd references: 9362306a36Sopenharmony_ci * - ur_probe gets a urd reference, ur_remove drops the reference 9462306a36Sopenharmony_ci * dev_get_drvdata(&cdev->dev) 9562306a36Sopenharmony_ci * - ur_open gets a urd reference, ur_release drops the reference 9662306a36Sopenharmony_ci * (urf->urd) 9762306a36Sopenharmony_ci * 9862306a36Sopenharmony_ci * cdev references: 9962306a36Sopenharmony_ci * - urdev_alloc get a cdev reference (urd->cdev) 10062306a36Sopenharmony_ci * - urdev_free drops the cdev reference (urd->cdev) 10162306a36Sopenharmony_ci * 10262306a36Sopenharmony_ci * Setting and clearing of dev_get_drvdata(&cdev->dev) is protected by the ccwdev lock 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_cistatic struct urdev *urdev_alloc(struct ccw_device *cdev) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct urdev *urd; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci urd = kzalloc(sizeof(struct urdev), GFP_KERNEL); 10962306a36Sopenharmony_ci if (!urd) 11062306a36Sopenharmony_ci return NULL; 11162306a36Sopenharmony_ci urd->reclen = cdev->id.driver_info; 11262306a36Sopenharmony_ci ccw_device_get_id(cdev, &urd->dev_id); 11362306a36Sopenharmony_ci mutex_init(&urd->io_mutex); 11462306a36Sopenharmony_ci init_waitqueue_head(&urd->wait); 11562306a36Sopenharmony_ci INIT_WORK(&urd->uevent_work, ur_uevent); 11662306a36Sopenharmony_ci spin_lock_init(&urd->open_lock); 11762306a36Sopenharmony_ci refcount_set(&urd->ref_count, 1); 11862306a36Sopenharmony_ci urd->cdev = cdev; 11962306a36Sopenharmony_ci get_device(&cdev->dev); 12062306a36Sopenharmony_ci return urd; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic void urdev_free(struct urdev *urd) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci TRACE("urdev_free: %p\n", urd); 12662306a36Sopenharmony_ci if (urd->cdev) 12762306a36Sopenharmony_ci put_device(&urd->cdev->dev); 12862306a36Sopenharmony_ci kfree(urd); 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic void urdev_get(struct urdev *urd) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci refcount_inc(&urd->ref_count); 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic struct urdev *urdev_get_from_cdev(struct ccw_device *cdev) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct urdev *urd; 13962306a36Sopenharmony_ci unsigned long flags; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci spin_lock_irqsave(get_ccwdev_lock(cdev), flags); 14262306a36Sopenharmony_ci urd = dev_get_drvdata(&cdev->dev); 14362306a36Sopenharmony_ci if (urd) 14462306a36Sopenharmony_ci urdev_get(urd); 14562306a36Sopenharmony_ci spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); 14662306a36Sopenharmony_ci return urd; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic struct urdev *urdev_get_from_devno(u16 devno) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci char bus_id[16]; 15262306a36Sopenharmony_ci struct ccw_device *cdev; 15362306a36Sopenharmony_ci struct urdev *urd; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci sprintf(bus_id, "0.0.%04x", devno); 15662306a36Sopenharmony_ci cdev = get_ccwdev_by_busid(&ur_driver, bus_id); 15762306a36Sopenharmony_ci if (!cdev) 15862306a36Sopenharmony_ci return NULL; 15962306a36Sopenharmony_ci urd = urdev_get_from_cdev(cdev); 16062306a36Sopenharmony_ci put_device(&cdev->dev); 16162306a36Sopenharmony_ci return urd; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic void urdev_put(struct urdev *urd) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci if (refcount_dec_and_test(&urd->ref_count)) 16762306a36Sopenharmony_ci urdev_free(urd); 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci/* 17162306a36Sopenharmony_ci * Low-level functions to do I/O to a ur device. 17262306a36Sopenharmony_ci * alloc_chan_prog 17362306a36Sopenharmony_ci * free_chan_prog 17462306a36Sopenharmony_ci * do_ur_io 17562306a36Sopenharmony_ci * ur_int_handler 17662306a36Sopenharmony_ci * 17762306a36Sopenharmony_ci * alloc_chan_prog allocates and builds the channel program 17862306a36Sopenharmony_ci * free_chan_prog frees memory of the channel program 17962306a36Sopenharmony_ci * 18062306a36Sopenharmony_ci * do_ur_io issues the channel program to the device and blocks waiting 18162306a36Sopenharmony_ci * on a completion event it publishes at urd->io_done. The function 18262306a36Sopenharmony_ci * serialises itself on the device's mutex so that only one I/O 18362306a36Sopenharmony_ci * is issued at a time (and that I/O is synchronous). 18462306a36Sopenharmony_ci * 18562306a36Sopenharmony_ci * ur_int_handler catches the "I/O done" interrupt, writes the 18662306a36Sopenharmony_ci * subchannel status word into the scsw member of the urdev structure 18762306a36Sopenharmony_ci * and complete()s the io_done to wake the waiting do_ur_io. 18862306a36Sopenharmony_ci * 18962306a36Sopenharmony_ci * The caller of do_ur_io is responsible for kfree()ing the channel program 19062306a36Sopenharmony_ci * address pointer that alloc_chan_prog returned. 19162306a36Sopenharmony_ci */ 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic void free_chan_prog(struct ccw1 *cpa) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci struct ccw1 *ptr = cpa; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci while (ptr->cda) { 19862306a36Sopenharmony_ci kfree((void *)(addr_t) ptr->cda); 19962306a36Sopenharmony_ci ptr++; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci kfree(cpa); 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci/* 20562306a36Sopenharmony_ci * alloc_chan_prog 20662306a36Sopenharmony_ci * The channel program we use is write commands chained together 20762306a36Sopenharmony_ci * with a final NOP CCW command-chained on (which ensures that CE and DE 20862306a36Sopenharmony_ci * are presented together in a single interrupt instead of as separate 20962306a36Sopenharmony_ci * interrupts unless an incorrect length indication kicks in first). The 21062306a36Sopenharmony_ci * data length in each CCW is reclen. 21162306a36Sopenharmony_ci */ 21262306a36Sopenharmony_cistatic struct ccw1 *alloc_chan_prog(const char __user *ubuf, int rec_count, 21362306a36Sopenharmony_ci int reclen) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci struct ccw1 *cpa; 21662306a36Sopenharmony_ci void *kbuf; 21762306a36Sopenharmony_ci int i; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci TRACE("alloc_chan_prog(%p, %i, %i)\n", ubuf, rec_count, reclen); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* 22262306a36Sopenharmony_ci * We chain a NOP onto the writes to force CE+DE together. 22362306a36Sopenharmony_ci * That means we allocate room for CCWs to cover count/reclen 22462306a36Sopenharmony_ci * records plus a NOP. 22562306a36Sopenharmony_ci */ 22662306a36Sopenharmony_ci cpa = kcalloc(rec_count + 1, sizeof(struct ccw1), 22762306a36Sopenharmony_ci GFP_KERNEL | GFP_DMA); 22862306a36Sopenharmony_ci if (!cpa) 22962306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci for (i = 0; i < rec_count; i++) { 23262306a36Sopenharmony_ci cpa[i].cmd_code = WRITE_CCW_CMD; 23362306a36Sopenharmony_ci cpa[i].flags = CCW_FLAG_CC | CCW_FLAG_SLI; 23462306a36Sopenharmony_ci cpa[i].count = reclen; 23562306a36Sopenharmony_ci kbuf = kmalloc(reclen, GFP_KERNEL | GFP_DMA); 23662306a36Sopenharmony_ci if (!kbuf) { 23762306a36Sopenharmony_ci free_chan_prog(cpa); 23862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci cpa[i].cda = (u32)(addr_t) kbuf; 24162306a36Sopenharmony_ci if (copy_from_user(kbuf, ubuf, reclen)) { 24262306a36Sopenharmony_ci free_chan_prog(cpa); 24362306a36Sopenharmony_ci return ERR_PTR(-EFAULT); 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci ubuf += reclen; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci /* The following NOP CCW forces CE+DE to be presented together */ 24862306a36Sopenharmony_ci cpa[i].cmd_code = CCW_CMD_NOOP; 24962306a36Sopenharmony_ci return cpa; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic int do_ur_io(struct urdev *urd, struct ccw1 *cpa) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci int rc; 25562306a36Sopenharmony_ci struct ccw_device *cdev = urd->cdev; 25662306a36Sopenharmony_ci DECLARE_COMPLETION_ONSTACK(event); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci TRACE("do_ur_io: cpa=%p\n", cpa); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci rc = mutex_lock_interruptible(&urd->io_mutex); 26162306a36Sopenharmony_ci if (rc) 26262306a36Sopenharmony_ci return rc; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci urd->io_done = &event; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci spin_lock_irq(get_ccwdev_lock(cdev)); 26762306a36Sopenharmony_ci rc = ccw_device_start(cdev, cpa, 1, 0, 0); 26862306a36Sopenharmony_ci spin_unlock_irq(get_ccwdev_lock(cdev)); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci TRACE("do_ur_io: ccw_device_start returned %d\n", rc); 27162306a36Sopenharmony_ci if (rc) 27262306a36Sopenharmony_ci goto out; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci wait_for_completion(&event); 27562306a36Sopenharmony_ci TRACE("do_ur_io: I/O complete\n"); 27662306a36Sopenharmony_ci rc = 0; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ciout: 27962306a36Sopenharmony_ci mutex_unlock(&urd->io_mutex); 28062306a36Sopenharmony_ci return rc; 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic void ur_uevent(struct work_struct *ws) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci struct urdev *urd = container_of(ws, struct urdev, uevent_work); 28662306a36Sopenharmony_ci char *envp[] = { 28762306a36Sopenharmony_ci "EVENT=unsol_de", /* Unsolicited device-end interrupt */ 28862306a36Sopenharmony_ci NULL 28962306a36Sopenharmony_ci }; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci kobject_uevent_env(&urd->cdev->dev.kobj, KOBJ_CHANGE, envp); 29262306a36Sopenharmony_ci urdev_put(urd); 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci/* 29662306a36Sopenharmony_ci * ur interrupt handler, called from the ccw_device layer 29762306a36Sopenharmony_ci */ 29862306a36Sopenharmony_cistatic void ur_int_handler(struct ccw_device *cdev, unsigned long intparm, 29962306a36Sopenharmony_ci struct irb *irb) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci struct urdev *urd; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (!IS_ERR(irb)) { 30462306a36Sopenharmony_ci TRACE("ur_int_handler: intparm=0x%lx cstat=%02x dstat=%02x res=%u\n", 30562306a36Sopenharmony_ci intparm, irb->scsw.cmd.cstat, irb->scsw.cmd.dstat, 30662306a36Sopenharmony_ci irb->scsw.cmd.count); 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci urd = dev_get_drvdata(&cdev->dev); 30962306a36Sopenharmony_ci if (!intparm) { 31062306a36Sopenharmony_ci TRACE("ur_int_handler: unsolicited interrupt\n"); 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci if (scsw_dstat(&irb->scsw) & DEV_STAT_DEV_END) { 31362306a36Sopenharmony_ci /* 31462306a36Sopenharmony_ci * Userspace might be interested in a transition to 31562306a36Sopenharmony_ci * device-ready state. 31662306a36Sopenharmony_ci */ 31762306a36Sopenharmony_ci urdev_get(urd); 31862306a36Sopenharmony_ci schedule_work(&urd->uevent_work); 31962306a36Sopenharmony_ci } 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci return; 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci /* On special conditions irb is an error pointer */ 32462306a36Sopenharmony_ci if (IS_ERR(irb)) 32562306a36Sopenharmony_ci urd->io_request_rc = PTR_ERR(irb); 32662306a36Sopenharmony_ci else if (irb->scsw.cmd.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END)) 32762306a36Sopenharmony_ci urd->io_request_rc = 0; 32862306a36Sopenharmony_ci else 32962306a36Sopenharmony_ci urd->io_request_rc = -EIO; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci complete(urd->io_done); 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci/* 33562306a36Sopenharmony_ci * reclen sysfs attribute - The record length to be used for write CCWs 33662306a36Sopenharmony_ci */ 33762306a36Sopenharmony_cistatic ssize_t ur_attr_reclen_show(struct device *dev, 33862306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci struct urdev *urd; 34162306a36Sopenharmony_ci int rc; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci urd = urdev_get_from_cdev(to_ccwdev(dev)); 34462306a36Sopenharmony_ci if (!urd) 34562306a36Sopenharmony_ci return -ENODEV; 34662306a36Sopenharmony_ci rc = sprintf(buf, "%zu\n", urd->reclen); 34762306a36Sopenharmony_ci urdev_put(urd); 34862306a36Sopenharmony_ci return rc; 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cistatic DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic int ur_create_attributes(struct device *dev) 35462306a36Sopenharmony_ci{ 35562306a36Sopenharmony_ci return device_create_file(dev, &dev_attr_reclen); 35662306a36Sopenharmony_ci} 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_cistatic void ur_remove_attributes(struct device *dev) 35962306a36Sopenharmony_ci{ 36062306a36Sopenharmony_ci device_remove_file(dev, &dev_attr_reclen); 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci/* 36462306a36Sopenharmony_ci * diagnose code 0x210 - retrieve device information 36562306a36Sopenharmony_ci * cc=0 normal completion, we have a real device 36662306a36Sopenharmony_ci * cc=1 CP paging error 36762306a36Sopenharmony_ci * cc=2 The virtual device exists, but is not associated with a real device 36862306a36Sopenharmony_ci * cc=3 Invalid device address, or the virtual device does not exist 36962306a36Sopenharmony_ci */ 37062306a36Sopenharmony_cistatic int get_urd_class(struct urdev *urd) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci static struct diag210 ur_diag210; 37362306a36Sopenharmony_ci int cc; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci ur_diag210.vrdcdvno = urd->dev_id.devno; 37662306a36Sopenharmony_ci ur_diag210.vrdclen = sizeof(struct diag210); 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci cc = diag210(&ur_diag210); 37962306a36Sopenharmony_ci switch (cc) { 38062306a36Sopenharmony_ci case 0: 38162306a36Sopenharmony_ci return -EOPNOTSUPP; 38262306a36Sopenharmony_ci case 2: 38362306a36Sopenharmony_ci return ur_diag210.vrdcvcla; /* virtual device class */ 38462306a36Sopenharmony_ci case 3: 38562306a36Sopenharmony_ci return -ENODEV; 38662306a36Sopenharmony_ci default: 38762306a36Sopenharmony_ci return -EIO; 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci} 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci/* 39262306a36Sopenharmony_ci * Allocation and freeing of urfile structures 39362306a36Sopenharmony_ci */ 39462306a36Sopenharmony_cistatic struct urfile *urfile_alloc(struct urdev *urd) 39562306a36Sopenharmony_ci{ 39662306a36Sopenharmony_ci struct urfile *urf; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci urf = kzalloc(sizeof(struct urfile), GFP_KERNEL); 39962306a36Sopenharmony_ci if (!urf) 40062306a36Sopenharmony_ci return NULL; 40162306a36Sopenharmony_ci urf->urd = urd; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci TRACE("urfile_alloc: urd=%p urf=%p rl=%zu\n", urd, urf, 40462306a36Sopenharmony_ci urf->dev_reclen); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci return urf; 40762306a36Sopenharmony_ci} 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_cistatic void urfile_free(struct urfile *urf) 41062306a36Sopenharmony_ci{ 41162306a36Sopenharmony_ci TRACE("urfile_free: urf=%p urd=%p\n", urf, urf->urd); 41262306a36Sopenharmony_ci kfree(urf); 41362306a36Sopenharmony_ci} 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci/* 41662306a36Sopenharmony_ci * The fops implementation of the character device driver 41762306a36Sopenharmony_ci */ 41862306a36Sopenharmony_cistatic ssize_t do_write(struct urdev *urd, const char __user *udata, 41962306a36Sopenharmony_ci size_t count, size_t reclen, loff_t *ppos) 42062306a36Sopenharmony_ci{ 42162306a36Sopenharmony_ci struct ccw1 *cpa; 42262306a36Sopenharmony_ci int rc; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci cpa = alloc_chan_prog(udata, count / reclen, reclen); 42562306a36Sopenharmony_ci if (IS_ERR(cpa)) 42662306a36Sopenharmony_ci return PTR_ERR(cpa); 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci rc = do_ur_io(urd, cpa); 42962306a36Sopenharmony_ci if (rc) 43062306a36Sopenharmony_ci goto fail_kfree_cpa; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci if (urd->io_request_rc) { 43362306a36Sopenharmony_ci rc = urd->io_request_rc; 43462306a36Sopenharmony_ci goto fail_kfree_cpa; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci *ppos += count; 43762306a36Sopenharmony_ci rc = count; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_cifail_kfree_cpa: 44062306a36Sopenharmony_ci free_chan_prog(cpa); 44162306a36Sopenharmony_ci return rc; 44262306a36Sopenharmony_ci} 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_cistatic ssize_t ur_write(struct file *file, const char __user *udata, 44562306a36Sopenharmony_ci size_t count, loff_t *ppos) 44662306a36Sopenharmony_ci{ 44762306a36Sopenharmony_ci struct urfile *urf = file->private_data; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci TRACE("ur_write: count=%zu\n", count); 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci if (count == 0) 45262306a36Sopenharmony_ci return 0; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci if (count % urf->dev_reclen) 45562306a36Sopenharmony_ci return -EINVAL; /* count must be a multiple of reclen */ 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci if (count > urf->dev_reclen * MAX_RECS_PER_IO) 45862306a36Sopenharmony_ci count = urf->dev_reclen * MAX_RECS_PER_IO; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci return do_write(urf->urd, udata, count, urf->dev_reclen, ppos); 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci/* 46462306a36Sopenharmony_ci * diagnose code 0x14 subcode 0x0028 - position spool file to designated 46562306a36Sopenharmony_ci * record 46662306a36Sopenharmony_ci * cc=0 normal completion 46762306a36Sopenharmony_ci * cc=2 no file active on the virtual reader or device not ready 46862306a36Sopenharmony_ci * cc=3 record specified is beyond EOF 46962306a36Sopenharmony_ci */ 47062306a36Sopenharmony_cistatic int diag_position_to_record(int devno, int record) 47162306a36Sopenharmony_ci{ 47262306a36Sopenharmony_ci int cc; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci cc = diag14(record, devno, 0x28); 47562306a36Sopenharmony_ci switch (cc) { 47662306a36Sopenharmony_ci case 0: 47762306a36Sopenharmony_ci return 0; 47862306a36Sopenharmony_ci case 2: 47962306a36Sopenharmony_ci return -ENOMEDIUM; 48062306a36Sopenharmony_ci case 3: 48162306a36Sopenharmony_ci return -ENODATA; /* position beyond end of file */ 48262306a36Sopenharmony_ci default: 48362306a36Sopenharmony_ci return -EIO; 48462306a36Sopenharmony_ci } 48562306a36Sopenharmony_ci} 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci/* 48862306a36Sopenharmony_ci * diagnose code 0x14 subcode 0x0000 - read next spool file buffer 48962306a36Sopenharmony_ci * cc=0 normal completion 49062306a36Sopenharmony_ci * cc=1 EOF reached 49162306a36Sopenharmony_ci * cc=2 no file active on the virtual reader, and no file eligible 49262306a36Sopenharmony_ci * cc=3 file already active on the virtual reader or specified virtual 49362306a36Sopenharmony_ci * reader does not exist or is not a reader 49462306a36Sopenharmony_ci */ 49562306a36Sopenharmony_cistatic int diag_read_file(int devno, char *buf) 49662306a36Sopenharmony_ci{ 49762306a36Sopenharmony_ci int cc; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci cc = diag14((unsigned long) buf, devno, 0x00); 50062306a36Sopenharmony_ci switch (cc) { 50162306a36Sopenharmony_ci case 0: 50262306a36Sopenharmony_ci return 0; 50362306a36Sopenharmony_ci case 1: 50462306a36Sopenharmony_ci return -ENODATA; 50562306a36Sopenharmony_ci case 2: 50662306a36Sopenharmony_ci return -ENOMEDIUM; 50762306a36Sopenharmony_ci default: 50862306a36Sopenharmony_ci return -EIO; 50962306a36Sopenharmony_ci } 51062306a36Sopenharmony_ci} 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_cistatic ssize_t diag14_read(struct file *file, char __user *ubuf, size_t count, 51362306a36Sopenharmony_ci loff_t *offs) 51462306a36Sopenharmony_ci{ 51562306a36Sopenharmony_ci size_t len, copied, res; 51662306a36Sopenharmony_ci char *buf; 51762306a36Sopenharmony_ci int rc; 51862306a36Sopenharmony_ci u16 reclen; 51962306a36Sopenharmony_ci struct urdev *urd; 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci urd = ((struct urfile *) file->private_data)->urd; 52262306a36Sopenharmony_ci reclen = ((struct urfile *) file->private_data)->file_reclen; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci rc = diag_position_to_record(urd->dev_id.devno, *offs / PAGE_SIZE + 1); 52562306a36Sopenharmony_ci if (rc == -ENODATA) 52662306a36Sopenharmony_ci return 0; 52762306a36Sopenharmony_ci if (rc) 52862306a36Sopenharmony_ci return rc; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci len = min((size_t) PAGE_SIZE, count); 53162306a36Sopenharmony_ci buf = (char *) __get_free_page(GFP_KERNEL | GFP_DMA); 53262306a36Sopenharmony_ci if (!buf) 53362306a36Sopenharmony_ci return -ENOMEM; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci copied = 0; 53662306a36Sopenharmony_ci res = (size_t) (*offs % PAGE_SIZE); 53762306a36Sopenharmony_ci do { 53862306a36Sopenharmony_ci rc = diag_read_file(urd->dev_id.devno, buf); 53962306a36Sopenharmony_ci if (rc == -ENODATA) { 54062306a36Sopenharmony_ci break; 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci if (rc) 54362306a36Sopenharmony_ci goto fail; 54462306a36Sopenharmony_ci if (reclen && (copied == 0) && (*offs < PAGE_SIZE)) 54562306a36Sopenharmony_ci *((u16 *) &buf[FILE_RECLEN_OFFSET]) = reclen; 54662306a36Sopenharmony_ci len = min(count - copied, PAGE_SIZE - res); 54762306a36Sopenharmony_ci if (copy_to_user(ubuf + copied, buf + res, len)) { 54862306a36Sopenharmony_ci rc = -EFAULT; 54962306a36Sopenharmony_ci goto fail; 55062306a36Sopenharmony_ci } 55162306a36Sopenharmony_ci res = 0; 55262306a36Sopenharmony_ci copied += len; 55362306a36Sopenharmony_ci } while (copied != count); 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci *offs += copied; 55662306a36Sopenharmony_ci rc = copied; 55762306a36Sopenharmony_cifail: 55862306a36Sopenharmony_ci free_page((unsigned long) buf); 55962306a36Sopenharmony_ci return rc; 56062306a36Sopenharmony_ci} 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_cistatic ssize_t ur_read(struct file *file, char __user *ubuf, size_t count, 56362306a36Sopenharmony_ci loff_t *offs) 56462306a36Sopenharmony_ci{ 56562306a36Sopenharmony_ci struct urdev *urd; 56662306a36Sopenharmony_ci int rc; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci TRACE("ur_read: count=%zu ppos=%li\n", count, (unsigned long) *offs); 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci if (count == 0) 57162306a36Sopenharmony_ci return 0; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci urd = ((struct urfile *) file->private_data)->urd; 57462306a36Sopenharmony_ci rc = mutex_lock_interruptible(&urd->io_mutex); 57562306a36Sopenharmony_ci if (rc) 57662306a36Sopenharmony_ci return rc; 57762306a36Sopenharmony_ci rc = diag14_read(file, ubuf, count, offs); 57862306a36Sopenharmony_ci mutex_unlock(&urd->io_mutex); 57962306a36Sopenharmony_ci return rc; 58062306a36Sopenharmony_ci} 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci/* 58362306a36Sopenharmony_ci * diagnose code 0x14 subcode 0x0fff - retrieve next file descriptor 58462306a36Sopenharmony_ci * cc=0 normal completion 58562306a36Sopenharmony_ci * cc=1 no files on reader queue or no subsequent file 58662306a36Sopenharmony_ci * cc=2 spid specified is invalid 58762306a36Sopenharmony_ci */ 58862306a36Sopenharmony_cistatic int diag_read_next_file_info(struct file_control_block *buf, int spid) 58962306a36Sopenharmony_ci{ 59062306a36Sopenharmony_ci int cc; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci cc = diag14((unsigned long) buf, spid, 0xfff); 59362306a36Sopenharmony_ci switch (cc) { 59462306a36Sopenharmony_ci case 0: 59562306a36Sopenharmony_ci return 0; 59662306a36Sopenharmony_ci default: 59762306a36Sopenharmony_ci return -ENODATA; 59862306a36Sopenharmony_ci } 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_cistatic int verify_uri_device(struct urdev *urd) 60262306a36Sopenharmony_ci{ 60362306a36Sopenharmony_ci struct file_control_block *fcb; 60462306a36Sopenharmony_ci char *buf; 60562306a36Sopenharmony_ci int rc; 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci fcb = kmalloc(sizeof(*fcb), GFP_KERNEL | GFP_DMA); 60862306a36Sopenharmony_ci if (!fcb) 60962306a36Sopenharmony_ci return -ENOMEM; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci /* check for empty reader device (beginning of chain) */ 61262306a36Sopenharmony_ci rc = diag_read_next_file_info(fcb, 0); 61362306a36Sopenharmony_ci if (rc) 61462306a36Sopenharmony_ci goto fail_free_fcb; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci /* if file is in hold status, we do not read it */ 61762306a36Sopenharmony_ci if (fcb->file_stat & (FLG_SYSTEM_HOLD | FLG_USER_HOLD)) { 61862306a36Sopenharmony_ci rc = -EPERM; 61962306a36Sopenharmony_ci goto fail_free_fcb; 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci /* open file on virtual reader */ 62362306a36Sopenharmony_ci buf = (char *) __get_free_page(GFP_KERNEL | GFP_DMA); 62462306a36Sopenharmony_ci if (!buf) { 62562306a36Sopenharmony_ci rc = -ENOMEM; 62662306a36Sopenharmony_ci goto fail_free_fcb; 62762306a36Sopenharmony_ci } 62862306a36Sopenharmony_ci rc = diag_read_file(urd->dev_id.devno, buf); 62962306a36Sopenharmony_ci if ((rc != 0) && (rc != -ENODATA)) /* EOF does not hurt */ 63062306a36Sopenharmony_ci goto fail_free_buf; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci /* check if the file on top of the queue is open now */ 63362306a36Sopenharmony_ci rc = diag_read_next_file_info(fcb, 0); 63462306a36Sopenharmony_ci if (rc) 63562306a36Sopenharmony_ci goto fail_free_buf; 63662306a36Sopenharmony_ci if (!(fcb->file_stat & FLG_IN_USE)) { 63762306a36Sopenharmony_ci rc = -EMFILE; 63862306a36Sopenharmony_ci goto fail_free_buf; 63962306a36Sopenharmony_ci } 64062306a36Sopenharmony_ci rc = 0; 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_cifail_free_buf: 64362306a36Sopenharmony_ci free_page((unsigned long) buf); 64462306a36Sopenharmony_cifail_free_fcb: 64562306a36Sopenharmony_ci kfree(fcb); 64662306a36Sopenharmony_ci return rc; 64762306a36Sopenharmony_ci} 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_cistatic int verify_device(struct urdev *urd) 65062306a36Sopenharmony_ci{ 65162306a36Sopenharmony_ci switch (urd->class) { 65262306a36Sopenharmony_ci case DEV_CLASS_UR_O: 65362306a36Sopenharmony_ci return 0; /* no check needed here */ 65462306a36Sopenharmony_ci case DEV_CLASS_UR_I: 65562306a36Sopenharmony_ci return verify_uri_device(urd); 65662306a36Sopenharmony_ci default: 65762306a36Sopenharmony_ci return -EOPNOTSUPP; 65862306a36Sopenharmony_ci } 65962306a36Sopenharmony_ci} 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_cistatic int get_uri_file_reclen(struct urdev *urd) 66262306a36Sopenharmony_ci{ 66362306a36Sopenharmony_ci struct file_control_block *fcb; 66462306a36Sopenharmony_ci int rc; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci fcb = kmalloc(sizeof(*fcb), GFP_KERNEL | GFP_DMA); 66762306a36Sopenharmony_ci if (!fcb) 66862306a36Sopenharmony_ci return -ENOMEM; 66962306a36Sopenharmony_ci rc = diag_read_next_file_info(fcb, 0); 67062306a36Sopenharmony_ci if (rc) 67162306a36Sopenharmony_ci goto fail_free; 67262306a36Sopenharmony_ci if (fcb->file_stat & FLG_CP_DUMP) 67362306a36Sopenharmony_ci rc = 0; 67462306a36Sopenharmony_ci else 67562306a36Sopenharmony_ci rc = fcb->rec_len; 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_cifail_free: 67862306a36Sopenharmony_ci kfree(fcb); 67962306a36Sopenharmony_ci return rc; 68062306a36Sopenharmony_ci} 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_cistatic int get_file_reclen(struct urdev *urd) 68362306a36Sopenharmony_ci{ 68462306a36Sopenharmony_ci switch (urd->class) { 68562306a36Sopenharmony_ci case DEV_CLASS_UR_O: 68662306a36Sopenharmony_ci return 0; 68762306a36Sopenharmony_ci case DEV_CLASS_UR_I: 68862306a36Sopenharmony_ci return get_uri_file_reclen(urd); 68962306a36Sopenharmony_ci default: 69062306a36Sopenharmony_ci return -EOPNOTSUPP; 69162306a36Sopenharmony_ci } 69262306a36Sopenharmony_ci} 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_cistatic int ur_open(struct inode *inode, struct file *file) 69562306a36Sopenharmony_ci{ 69662306a36Sopenharmony_ci u16 devno; 69762306a36Sopenharmony_ci struct urdev *urd; 69862306a36Sopenharmony_ci struct urfile *urf; 69962306a36Sopenharmony_ci unsigned short accmode; 70062306a36Sopenharmony_ci int rc; 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci accmode = file->f_flags & O_ACCMODE; 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci if (accmode == O_RDWR) 70562306a36Sopenharmony_ci return -EACCES; 70662306a36Sopenharmony_ci /* 70762306a36Sopenharmony_ci * We treat the minor number as the devno of the ur device 70862306a36Sopenharmony_ci * to find in the driver tree. 70962306a36Sopenharmony_ci */ 71062306a36Sopenharmony_ci devno = iminor(file_inode(file)); 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci urd = urdev_get_from_devno(devno); 71362306a36Sopenharmony_ci if (!urd) { 71462306a36Sopenharmony_ci rc = -ENXIO; 71562306a36Sopenharmony_ci goto out; 71662306a36Sopenharmony_ci } 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci spin_lock(&urd->open_lock); 71962306a36Sopenharmony_ci while (urd->open_flag) { 72062306a36Sopenharmony_ci spin_unlock(&urd->open_lock); 72162306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) { 72262306a36Sopenharmony_ci rc = -EBUSY; 72362306a36Sopenharmony_ci goto fail_put; 72462306a36Sopenharmony_ci } 72562306a36Sopenharmony_ci if (wait_event_interruptible(urd->wait, urd->open_flag == 0)) { 72662306a36Sopenharmony_ci rc = -ERESTARTSYS; 72762306a36Sopenharmony_ci goto fail_put; 72862306a36Sopenharmony_ci } 72962306a36Sopenharmony_ci spin_lock(&urd->open_lock); 73062306a36Sopenharmony_ci } 73162306a36Sopenharmony_ci urd->open_flag++; 73262306a36Sopenharmony_ci spin_unlock(&urd->open_lock); 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci TRACE("ur_open\n"); 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci if (((accmode == O_RDONLY) && (urd->class != DEV_CLASS_UR_I)) || 73762306a36Sopenharmony_ci ((accmode == O_WRONLY) && (urd->class != DEV_CLASS_UR_O))) { 73862306a36Sopenharmony_ci TRACE("ur_open: unsupported dev class (%d)\n", urd->class); 73962306a36Sopenharmony_ci rc = -EACCES; 74062306a36Sopenharmony_ci goto fail_unlock; 74162306a36Sopenharmony_ci } 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci rc = verify_device(urd); 74462306a36Sopenharmony_ci if (rc) 74562306a36Sopenharmony_ci goto fail_unlock; 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci urf = urfile_alloc(urd); 74862306a36Sopenharmony_ci if (!urf) { 74962306a36Sopenharmony_ci rc = -ENOMEM; 75062306a36Sopenharmony_ci goto fail_unlock; 75162306a36Sopenharmony_ci } 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci urf->dev_reclen = urd->reclen; 75462306a36Sopenharmony_ci rc = get_file_reclen(urd); 75562306a36Sopenharmony_ci if (rc < 0) 75662306a36Sopenharmony_ci goto fail_urfile_free; 75762306a36Sopenharmony_ci urf->file_reclen = rc; 75862306a36Sopenharmony_ci file->private_data = urf; 75962306a36Sopenharmony_ci return 0; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_cifail_urfile_free: 76262306a36Sopenharmony_ci urfile_free(urf); 76362306a36Sopenharmony_cifail_unlock: 76462306a36Sopenharmony_ci spin_lock(&urd->open_lock); 76562306a36Sopenharmony_ci urd->open_flag--; 76662306a36Sopenharmony_ci spin_unlock(&urd->open_lock); 76762306a36Sopenharmony_cifail_put: 76862306a36Sopenharmony_ci urdev_put(urd); 76962306a36Sopenharmony_ciout: 77062306a36Sopenharmony_ci return rc; 77162306a36Sopenharmony_ci} 77262306a36Sopenharmony_ci 77362306a36Sopenharmony_cistatic int ur_release(struct inode *inode, struct file *file) 77462306a36Sopenharmony_ci{ 77562306a36Sopenharmony_ci struct urfile *urf = file->private_data; 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci TRACE("ur_release\n"); 77862306a36Sopenharmony_ci spin_lock(&urf->urd->open_lock); 77962306a36Sopenharmony_ci urf->urd->open_flag--; 78062306a36Sopenharmony_ci spin_unlock(&urf->urd->open_lock); 78162306a36Sopenharmony_ci wake_up_interruptible(&urf->urd->wait); 78262306a36Sopenharmony_ci urdev_put(urf->urd); 78362306a36Sopenharmony_ci urfile_free(urf); 78462306a36Sopenharmony_ci return 0; 78562306a36Sopenharmony_ci} 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_cistatic loff_t ur_llseek(struct file *file, loff_t offset, int whence) 78862306a36Sopenharmony_ci{ 78962306a36Sopenharmony_ci if ((file->f_flags & O_ACCMODE) != O_RDONLY) 79062306a36Sopenharmony_ci return -ESPIPE; /* seek allowed only for reader */ 79162306a36Sopenharmony_ci if (offset % PAGE_SIZE) 79262306a36Sopenharmony_ci return -ESPIPE; /* only multiples of 4K allowed */ 79362306a36Sopenharmony_ci return no_seek_end_llseek(file, offset, whence); 79462306a36Sopenharmony_ci} 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_cistatic const struct file_operations ur_fops = { 79762306a36Sopenharmony_ci .owner = THIS_MODULE, 79862306a36Sopenharmony_ci .open = ur_open, 79962306a36Sopenharmony_ci .release = ur_release, 80062306a36Sopenharmony_ci .read = ur_read, 80162306a36Sopenharmony_ci .write = ur_write, 80262306a36Sopenharmony_ci .llseek = ur_llseek, 80362306a36Sopenharmony_ci}; 80462306a36Sopenharmony_ci 80562306a36Sopenharmony_ci/* 80662306a36Sopenharmony_ci * ccw_device infrastructure: 80762306a36Sopenharmony_ci * ur_probe creates the struct urdev (with refcount = 1), the device 80862306a36Sopenharmony_ci * attributes, sets up the interrupt handler and validates the virtual 80962306a36Sopenharmony_ci * unit record device. 81062306a36Sopenharmony_ci * ur_remove removes the device attributes and drops the reference to 81162306a36Sopenharmony_ci * struct urdev. 81262306a36Sopenharmony_ci * 81362306a36Sopenharmony_ci * ur_probe, ur_remove, ur_set_online and ur_set_offline are serialized 81462306a36Sopenharmony_ci * by the vmur_mutex lock. 81562306a36Sopenharmony_ci * 81662306a36Sopenharmony_ci * urd->char_device is used as indication that the online function has 81762306a36Sopenharmony_ci * been completed successfully. 81862306a36Sopenharmony_ci */ 81962306a36Sopenharmony_cistatic int ur_probe(struct ccw_device *cdev) 82062306a36Sopenharmony_ci{ 82162306a36Sopenharmony_ci struct urdev *urd; 82262306a36Sopenharmony_ci int rc; 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_ci TRACE("ur_probe: cdev=%p\n", cdev); 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ci mutex_lock(&vmur_mutex); 82762306a36Sopenharmony_ci urd = urdev_alloc(cdev); 82862306a36Sopenharmony_ci if (!urd) { 82962306a36Sopenharmony_ci rc = -ENOMEM; 83062306a36Sopenharmony_ci goto fail_unlock; 83162306a36Sopenharmony_ci } 83262306a36Sopenharmony_ci 83362306a36Sopenharmony_ci rc = ur_create_attributes(&cdev->dev); 83462306a36Sopenharmony_ci if (rc) { 83562306a36Sopenharmony_ci rc = -ENOMEM; 83662306a36Sopenharmony_ci goto fail_urdev_put; 83762306a36Sopenharmony_ci } 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_ci /* validate virtual unit record device */ 84062306a36Sopenharmony_ci urd->class = get_urd_class(urd); 84162306a36Sopenharmony_ci if (urd->class < 0) { 84262306a36Sopenharmony_ci rc = urd->class; 84362306a36Sopenharmony_ci goto fail_remove_attr; 84462306a36Sopenharmony_ci } 84562306a36Sopenharmony_ci if ((urd->class != DEV_CLASS_UR_I) && (urd->class != DEV_CLASS_UR_O)) { 84662306a36Sopenharmony_ci rc = -EOPNOTSUPP; 84762306a36Sopenharmony_ci goto fail_remove_attr; 84862306a36Sopenharmony_ci } 84962306a36Sopenharmony_ci spin_lock_irq(get_ccwdev_lock(cdev)); 85062306a36Sopenharmony_ci dev_set_drvdata(&cdev->dev, urd); 85162306a36Sopenharmony_ci cdev->handler = ur_int_handler; 85262306a36Sopenharmony_ci spin_unlock_irq(get_ccwdev_lock(cdev)); 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci mutex_unlock(&vmur_mutex); 85562306a36Sopenharmony_ci return 0; 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_cifail_remove_attr: 85862306a36Sopenharmony_ci ur_remove_attributes(&cdev->dev); 85962306a36Sopenharmony_cifail_urdev_put: 86062306a36Sopenharmony_ci urdev_put(urd); 86162306a36Sopenharmony_cifail_unlock: 86262306a36Sopenharmony_ci mutex_unlock(&vmur_mutex); 86362306a36Sopenharmony_ci return rc; 86462306a36Sopenharmony_ci} 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_cistatic int ur_set_online(struct ccw_device *cdev) 86762306a36Sopenharmony_ci{ 86862306a36Sopenharmony_ci struct urdev *urd; 86962306a36Sopenharmony_ci int minor, major, rc; 87062306a36Sopenharmony_ci char node_id[16]; 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci TRACE("ur_set_online: cdev=%p\n", cdev); 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci mutex_lock(&vmur_mutex); 87562306a36Sopenharmony_ci urd = urdev_get_from_cdev(cdev); 87662306a36Sopenharmony_ci if (!urd) { 87762306a36Sopenharmony_ci /* ur_remove already deleted our urd */ 87862306a36Sopenharmony_ci rc = -ENODEV; 87962306a36Sopenharmony_ci goto fail_unlock; 88062306a36Sopenharmony_ci } 88162306a36Sopenharmony_ci 88262306a36Sopenharmony_ci if (urd->char_device) { 88362306a36Sopenharmony_ci /* Another ur_set_online was faster */ 88462306a36Sopenharmony_ci rc = -EBUSY; 88562306a36Sopenharmony_ci goto fail_urdev_put; 88662306a36Sopenharmony_ci } 88762306a36Sopenharmony_ci 88862306a36Sopenharmony_ci minor = urd->dev_id.devno; 88962306a36Sopenharmony_ci major = MAJOR(ur_first_dev_maj_min); 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci urd->char_device = cdev_alloc(); 89262306a36Sopenharmony_ci if (!urd->char_device) { 89362306a36Sopenharmony_ci rc = -ENOMEM; 89462306a36Sopenharmony_ci goto fail_urdev_put; 89562306a36Sopenharmony_ci } 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci urd->char_device->ops = &ur_fops; 89862306a36Sopenharmony_ci urd->char_device->owner = ur_fops.owner; 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci rc = cdev_add(urd->char_device, MKDEV(major, minor), 1); 90162306a36Sopenharmony_ci if (rc) 90262306a36Sopenharmony_ci goto fail_free_cdev; 90362306a36Sopenharmony_ci if (urd->cdev->id.cu_type == READER_PUNCH_DEVTYPE) { 90462306a36Sopenharmony_ci if (urd->class == DEV_CLASS_UR_I) 90562306a36Sopenharmony_ci sprintf(node_id, "vmrdr-%s", dev_name(&cdev->dev)); 90662306a36Sopenharmony_ci if (urd->class == DEV_CLASS_UR_O) 90762306a36Sopenharmony_ci sprintf(node_id, "vmpun-%s", dev_name(&cdev->dev)); 90862306a36Sopenharmony_ci } else if (urd->cdev->id.cu_type == PRINTER_DEVTYPE) { 90962306a36Sopenharmony_ci sprintf(node_id, "vmprt-%s", dev_name(&cdev->dev)); 91062306a36Sopenharmony_ci } else { 91162306a36Sopenharmony_ci rc = -EOPNOTSUPP; 91262306a36Sopenharmony_ci goto fail_free_cdev; 91362306a36Sopenharmony_ci } 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci urd->device = device_create(vmur_class, &cdev->dev, 91662306a36Sopenharmony_ci urd->char_device->dev, NULL, "%s", node_id); 91762306a36Sopenharmony_ci if (IS_ERR(urd->device)) { 91862306a36Sopenharmony_ci rc = PTR_ERR(urd->device); 91962306a36Sopenharmony_ci TRACE("ur_set_online: device_create rc=%d\n", rc); 92062306a36Sopenharmony_ci goto fail_free_cdev; 92162306a36Sopenharmony_ci } 92262306a36Sopenharmony_ci urdev_put(urd); 92362306a36Sopenharmony_ci mutex_unlock(&vmur_mutex); 92462306a36Sopenharmony_ci return 0; 92562306a36Sopenharmony_ci 92662306a36Sopenharmony_cifail_free_cdev: 92762306a36Sopenharmony_ci cdev_del(urd->char_device); 92862306a36Sopenharmony_ci urd->char_device = NULL; 92962306a36Sopenharmony_cifail_urdev_put: 93062306a36Sopenharmony_ci urdev_put(urd); 93162306a36Sopenharmony_cifail_unlock: 93262306a36Sopenharmony_ci mutex_unlock(&vmur_mutex); 93362306a36Sopenharmony_ci return rc; 93462306a36Sopenharmony_ci} 93562306a36Sopenharmony_ci 93662306a36Sopenharmony_cistatic int ur_set_offline_force(struct ccw_device *cdev, int force) 93762306a36Sopenharmony_ci{ 93862306a36Sopenharmony_ci struct urdev *urd; 93962306a36Sopenharmony_ci int rc; 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ci TRACE("ur_set_offline: cdev=%p\n", cdev); 94262306a36Sopenharmony_ci urd = urdev_get_from_cdev(cdev); 94362306a36Sopenharmony_ci if (!urd) 94462306a36Sopenharmony_ci /* ur_remove already deleted our urd */ 94562306a36Sopenharmony_ci return -ENODEV; 94662306a36Sopenharmony_ci if (!urd->char_device) { 94762306a36Sopenharmony_ci /* Another ur_set_offline was faster */ 94862306a36Sopenharmony_ci rc = -EBUSY; 94962306a36Sopenharmony_ci goto fail_urdev_put; 95062306a36Sopenharmony_ci } 95162306a36Sopenharmony_ci if (!force && (refcount_read(&urd->ref_count) > 2)) { 95262306a36Sopenharmony_ci /* There is still a user of urd (e.g. ur_open) */ 95362306a36Sopenharmony_ci TRACE("ur_set_offline: BUSY\n"); 95462306a36Sopenharmony_ci rc = -EBUSY; 95562306a36Sopenharmony_ci goto fail_urdev_put; 95662306a36Sopenharmony_ci } 95762306a36Sopenharmony_ci if (cancel_work_sync(&urd->uevent_work)) { 95862306a36Sopenharmony_ci /* Work not run yet - need to release reference here */ 95962306a36Sopenharmony_ci urdev_put(urd); 96062306a36Sopenharmony_ci } 96162306a36Sopenharmony_ci device_destroy(vmur_class, urd->char_device->dev); 96262306a36Sopenharmony_ci cdev_del(urd->char_device); 96362306a36Sopenharmony_ci urd->char_device = NULL; 96462306a36Sopenharmony_ci rc = 0; 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_cifail_urdev_put: 96762306a36Sopenharmony_ci urdev_put(urd); 96862306a36Sopenharmony_ci return rc; 96962306a36Sopenharmony_ci} 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_cistatic int ur_set_offline(struct ccw_device *cdev) 97262306a36Sopenharmony_ci{ 97362306a36Sopenharmony_ci int rc; 97462306a36Sopenharmony_ci 97562306a36Sopenharmony_ci mutex_lock(&vmur_mutex); 97662306a36Sopenharmony_ci rc = ur_set_offline_force(cdev, 0); 97762306a36Sopenharmony_ci mutex_unlock(&vmur_mutex); 97862306a36Sopenharmony_ci return rc; 97962306a36Sopenharmony_ci} 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_cistatic void ur_remove(struct ccw_device *cdev) 98262306a36Sopenharmony_ci{ 98362306a36Sopenharmony_ci unsigned long flags; 98462306a36Sopenharmony_ci 98562306a36Sopenharmony_ci TRACE("ur_remove\n"); 98662306a36Sopenharmony_ci 98762306a36Sopenharmony_ci mutex_lock(&vmur_mutex); 98862306a36Sopenharmony_ci 98962306a36Sopenharmony_ci if (cdev->online) 99062306a36Sopenharmony_ci ur_set_offline_force(cdev, 1); 99162306a36Sopenharmony_ci ur_remove_attributes(&cdev->dev); 99262306a36Sopenharmony_ci 99362306a36Sopenharmony_ci spin_lock_irqsave(get_ccwdev_lock(cdev), flags); 99462306a36Sopenharmony_ci urdev_put(dev_get_drvdata(&cdev->dev)); 99562306a36Sopenharmony_ci dev_set_drvdata(&cdev->dev, NULL); 99662306a36Sopenharmony_ci cdev->handler = NULL; 99762306a36Sopenharmony_ci spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); 99862306a36Sopenharmony_ci 99962306a36Sopenharmony_ci mutex_unlock(&vmur_mutex); 100062306a36Sopenharmony_ci} 100162306a36Sopenharmony_ci 100262306a36Sopenharmony_ci/* 100362306a36Sopenharmony_ci * Module initialisation and cleanup 100462306a36Sopenharmony_ci */ 100562306a36Sopenharmony_cistatic int __init ur_init(void) 100662306a36Sopenharmony_ci{ 100762306a36Sopenharmony_ci int rc; 100862306a36Sopenharmony_ci dev_t dev; 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_ci if (!MACHINE_IS_VM) { 101162306a36Sopenharmony_ci pr_err("The %s cannot be loaded without z/VM\n", 101262306a36Sopenharmony_ci ur_banner); 101362306a36Sopenharmony_ci return -ENODEV; 101462306a36Sopenharmony_ci } 101562306a36Sopenharmony_ci 101662306a36Sopenharmony_ci vmur_dbf = debug_register("vmur", 4, 1, 4 * sizeof(long)); 101762306a36Sopenharmony_ci if (!vmur_dbf) 101862306a36Sopenharmony_ci return -ENOMEM; 101962306a36Sopenharmony_ci rc = debug_register_view(vmur_dbf, &debug_sprintf_view); 102062306a36Sopenharmony_ci if (rc) 102162306a36Sopenharmony_ci goto fail_free_dbf; 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci debug_set_level(vmur_dbf, 6); 102462306a36Sopenharmony_ci 102562306a36Sopenharmony_ci vmur_class = class_create("vmur"); 102662306a36Sopenharmony_ci if (IS_ERR(vmur_class)) { 102762306a36Sopenharmony_ci rc = PTR_ERR(vmur_class); 102862306a36Sopenharmony_ci goto fail_free_dbf; 102962306a36Sopenharmony_ci } 103062306a36Sopenharmony_ci 103162306a36Sopenharmony_ci rc = ccw_driver_register(&ur_driver); 103262306a36Sopenharmony_ci if (rc) 103362306a36Sopenharmony_ci goto fail_class_destroy; 103462306a36Sopenharmony_ci 103562306a36Sopenharmony_ci rc = alloc_chrdev_region(&dev, 0, NUM_MINORS, "vmur"); 103662306a36Sopenharmony_ci if (rc) { 103762306a36Sopenharmony_ci pr_err("Kernel function alloc_chrdev_region failed with " 103862306a36Sopenharmony_ci "error code %d\n", rc); 103962306a36Sopenharmony_ci goto fail_unregister_driver; 104062306a36Sopenharmony_ci } 104162306a36Sopenharmony_ci ur_first_dev_maj_min = MKDEV(MAJOR(dev), 0); 104262306a36Sopenharmony_ci 104362306a36Sopenharmony_ci pr_info("%s loaded.\n", ur_banner); 104462306a36Sopenharmony_ci return 0; 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_cifail_unregister_driver: 104762306a36Sopenharmony_ci ccw_driver_unregister(&ur_driver); 104862306a36Sopenharmony_cifail_class_destroy: 104962306a36Sopenharmony_ci class_destroy(vmur_class); 105062306a36Sopenharmony_cifail_free_dbf: 105162306a36Sopenharmony_ci debug_unregister(vmur_dbf); 105262306a36Sopenharmony_ci return rc; 105362306a36Sopenharmony_ci} 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_cistatic void __exit ur_exit(void) 105662306a36Sopenharmony_ci{ 105762306a36Sopenharmony_ci unregister_chrdev_region(ur_first_dev_maj_min, NUM_MINORS); 105862306a36Sopenharmony_ci ccw_driver_unregister(&ur_driver); 105962306a36Sopenharmony_ci class_destroy(vmur_class); 106062306a36Sopenharmony_ci debug_unregister(vmur_dbf); 106162306a36Sopenharmony_ci pr_info("%s unloaded.\n", ur_banner); 106262306a36Sopenharmony_ci} 106362306a36Sopenharmony_ci 106462306a36Sopenharmony_cimodule_init(ur_init); 106562306a36Sopenharmony_cimodule_exit(ur_exit); 1066