| /* |
| * Emulated ccw-attached 3270 implementation |
| * |
| * Copyright 2017 IBM Corp. |
| * Author(s): Yang Chen <bjcyang@linux.vnet.ibm.com> |
| * Jing Liu <liujbjl@linux.vnet.ibm.com> |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or (at |
| * your option) any later version. See the COPYING file in the top-level |
| * directory. |
| */ |
| #include "qemu/osdep.h" |
| #include "qapi/error.h" |
| #include "qemu/module.h" |
| #include "cpu.h" |
| #include "hw/s390x/css.h" |
| #include "hw/s390x/css-bridge.h" |
| #include "hw/s390x/3270-ccw.h" |
| |
| /* Handle READ ccw commands from guest */ |
| static int handle_payload_3270_read(EmulatedCcw3270Device *dev, CCW1 *ccw) |
| { |
| EmulatedCcw3270Class *ck = EMULATED_CCW_3270_GET_CLASS(dev); |
| CcwDevice *ccw_dev = CCW_DEVICE(dev); |
| int len; |
| |
| if (!ccw->cda) { |
| return -EFAULT; |
| } |
| |
| len = ck->read_payload_3270(dev, ccw->cda, ccw->count); |
| ccw_dev->sch->curr_status.scsw.count = ccw->count - len; |
| |
| return 0; |
| } |
| |
| /* Handle WRITE ccw commands to write data to client */ |
| static int handle_payload_3270_write(EmulatedCcw3270Device *dev, CCW1 *ccw) |
| { |
| EmulatedCcw3270Class *ck = EMULATED_CCW_3270_GET_CLASS(dev); |
| CcwDevice *ccw_dev = CCW_DEVICE(dev); |
| int len; |
| |
| if (!ccw->cda) { |
| return -EFAULT; |
| } |
| |
| len = ck->write_payload_3270(dev, ccw->cmd_code, ccw->cda, ccw->count); |
| |
| if (len <= 0) { |
| return -EIO; |
| } |
| |
| ccw_dev->sch->curr_status.scsw.count = ccw->count - len; |
| return 0; |
| } |
| |
| static int emulated_ccw_3270_cb(SubchDev *sch, CCW1 ccw) |
| { |
| int rc = 0; |
| EmulatedCcw3270Device *dev = sch->driver_data; |
| |
| switch (ccw.cmd_code) { |
| case TC_WRITESF: |
| case TC_WRITE: |
| case TC_EWRITE: |
| case TC_EWRITEA: |
| rc = handle_payload_3270_write(dev, &ccw); |
| break; |
| case TC_RDBUF: |
| case TC_READMOD: |
| rc = handle_payload_3270_read(dev, &ccw); |
| break; |
| default: |
| rc = -ENOSYS; |
| break; |
| } |
| |
| if (rc == -EIO) { |
| /* I/O error, specific devices generate specific conditions */ |
| SCSW *s = &sch->curr_status.scsw; |
| |
| sch->curr_status.scsw.dstat = SCSW_DSTAT_UNIT_CHECK; |
| sch->sense_data[0] = 0x40; /* intervention-req */ |
| s->ctrl &= ~SCSW_ACTL_START_PEND; |
| s->ctrl &= ~SCSW_CTRL_MASK_STCTL; |
| s->ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY | |
| SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND; |
| } |
| |
| return rc; |
| } |
| |
| static void emulated_ccw_3270_realize(DeviceState *ds, Error **errp) |
| { |
| uint16_t chpid; |
| EmulatedCcw3270Device *dev = EMULATED_CCW_3270(ds); |
| EmulatedCcw3270Class *ck = EMULATED_CCW_3270_GET_CLASS(dev); |
| CcwDevice *cdev = CCW_DEVICE(ds); |
| CCWDeviceClass *cdk = CCW_DEVICE_GET_CLASS(cdev); |
| DeviceState *parent = DEVICE(cdev); |
| BusState *qbus = qdev_get_parent_bus(parent); |
| VirtualCssBus *cbus = VIRTUAL_CSS_BUS(qbus); |
| SubchDev *sch; |
| Error *err = NULL; |
| |
| sch = css_create_sch(cdev->devno, true, cbus->squash_mcss, errp); |
| if (!sch) { |
| return; |
| } |
| |
| if (!ck->init) { |
| goto out_err; |
| } |
| |
| sch->driver_data = dev; |
| cdev->sch = sch; |
| chpid = css_find_free_chpid(sch->cssid); |
| |
| if (chpid > MAX_CHPID) { |
| error_setg(&err, "No available chpid to use."); |
| goto out_err; |
| } |
| |
| sch->id.reserved = 0xff; |
| sch->id.cu_type = EMULATED_CCW_3270_CU_TYPE; |
| css_sch_build_virtual_schib(sch, (uint8_t)chpid, |
| EMULATED_CCW_3270_CHPID_TYPE); |
| sch->do_subchannel_work = do_subchannel_work_virtual; |
| sch->ccw_cb = emulated_ccw_3270_cb; |
| |
| ck->init(dev, &err); |
| if (err) { |
| goto out_err; |
| } |
| |
| cdk->realize(cdev, &err); |
| if (err) { |
| goto out_err; |
| } |
| |
| return; |
| |
| out_err: |
| error_propagate(errp, err); |
| css_subch_assign(sch->cssid, sch->ssid, sch->schid, sch->devno, NULL); |
| cdev->sch = NULL; |
| g_free(sch); |
| } |
| |
| static Property emulated_ccw_3270_properties[] = { |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void emulated_ccw_3270_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->props = emulated_ccw_3270_properties; |
| dc->bus_type = TYPE_VIRTUAL_CSS_BUS; |
| dc->realize = emulated_ccw_3270_realize; |
| dc->hotpluggable = false; |
| } |
| |
| static const TypeInfo emulated_ccw_3270_info = { |
| .name = TYPE_EMULATED_CCW_3270, |
| .parent = TYPE_CCW_DEVICE, |
| .instance_size = sizeof(EmulatedCcw3270Device), |
| .class_init = emulated_ccw_3270_class_init, |
| .class_size = sizeof(EmulatedCcw3270Class), |
| .abstract = true, |
| }; |
| |
| static void emulated_ccw_register(void) |
| { |
| type_register_static(&emulated_ccw_3270_info); |
| } |
| |
| type_init(emulated_ccw_register) |