diff options
Diffstat (limited to 'hw/s390x/css.c')
-rw-r--r-- | hw/s390x/css.c | 186 |
1 files changed, 182 insertions, 4 deletions
diff --git a/hw/s390x/css.c b/hw/s390x/css.c index c59be1aad1..35683d7954 100644 --- a/hw/s390x/css.c +++ b/hw/s390x/css.c @@ -787,6 +787,183 @@ static CCW1 copy_ccw_from_guest(hwaddr addr, bool fmt1) } return ret; } +/** + * If out of bounds marks the stream broken. If broken returns -EINVAL, + * otherwise the requested length (may be zero) + */ +static inline int cds_check_len(CcwDataStream *cds, int len) +{ + if (cds->at_byte + len > cds->count) { + cds->flags |= CDS_F_STREAM_BROKEN; + } + return cds->flags & CDS_F_STREAM_BROKEN ? -EINVAL : len; +} + +static inline bool cds_ccw_addrs_ok(hwaddr addr, int len, bool ccw_fmt1) +{ + return (addr + len) < (ccw_fmt1 ? (1UL << 31) : (1UL << 24)); +} + +static int ccw_dstream_rw_noflags(CcwDataStream *cds, void *buff, int len, + CcwDataStreamOp op) +{ + int ret; + + ret = cds_check_len(cds, len); + if (ret <= 0) { + return ret; + } + if (!cds_ccw_addrs_ok(cds->cda, len, cds->flags & CDS_F_FMT)) { + return -EINVAL; /* channel program check */ + } + if (op == CDS_OP_A) { + goto incr; + } + ret = address_space_rw(&address_space_memory, cds->cda, + MEMTXATTRS_UNSPECIFIED, buff, len, op); + if (ret != MEMTX_OK) { + cds->flags |= CDS_F_STREAM_BROKEN; + return -EINVAL; + } +incr: + cds->at_byte += len; + cds->cda += len; + return 0; +} + +/* returns values between 1 and bsz, where bsz is a power of 2 */ +static inline uint16_t ida_continuous_left(hwaddr cda, uint64_t bsz) +{ + return bsz - (cda & (bsz - 1)); +} + +static inline uint64_t ccw_ida_block_size(uint8_t flags) +{ + if ((flags & CDS_F_C64) && !(flags & CDS_F_I2K)) { + return 1ULL << 12; + } + return 1ULL << 11; +} + +static inline int ida_read_next_idaw(CcwDataStream *cds) +{ + union {uint64_t fmt2; uint32_t fmt1; } idaw; + int ret; + hwaddr idaw_addr; + bool idaw_fmt2 = cds->flags & CDS_F_C64; + bool ccw_fmt1 = cds->flags & CDS_F_FMT; + + if (idaw_fmt2) { + idaw_addr = cds->cda_orig + sizeof(idaw.fmt2) * cds->at_idaw; + if (idaw_addr & 0x07 || !cds_ccw_addrs_ok(idaw_addr, 0, ccw_fmt1)) { + return -EINVAL; /* channel program check */ + } + ret = address_space_rw(&address_space_memory, idaw_addr, + MEMTXATTRS_UNSPECIFIED, (void *) &idaw.fmt2, + sizeof(idaw.fmt2), false); + cds->cda = be64_to_cpu(idaw.fmt2); + } else { + idaw_addr = cds->cda_orig + sizeof(idaw.fmt1) * cds->at_idaw; + if (idaw_addr & 0x03 || !cds_ccw_addrs_ok(idaw_addr, 0, ccw_fmt1)) { + return -EINVAL; /* channel program check */ + } + ret = address_space_rw(&address_space_memory, idaw_addr, + MEMTXATTRS_UNSPECIFIED, (void *) &idaw.fmt1, + sizeof(idaw.fmt1), false); + cds->cda = be64_to_cpu(idaw.fmt1); + if (cds->cda & 0x80000000) { + return -EINVAL; /* channel program check */ + } + } + ++(cds->at_idaw); + if (ret != MEMTX_OK) { + /* assume inaccessible address */ + return -EINVAL; /* channel program check */ + } + return 0; +} + +static int ccw_dstream_rw_ida(CcwDataStream *cds, void *buff, int len, + CcwDataStreamOp op) +{ + uint64_t bsz = ccw_ida_block_size(cds->flags); + int ret = 0; + uint16_t cont_left, iter_len; + + ret = cds_check_len(cds, len); + if (ret <= 0) { + return ret; + } + if (!cds->at_idaw) { + /* read first idaw */ + ret = ida_read_next_idaw(cds); + if (ret) { + goto err; + } + cont_left = ida_continuous_left(cds->cda, bsz); + } else { + cont_left = ida_continuous_left(cds->cda, bsz); + if (cont_left == bsz) { + ret = ida_read_next_idaw(cds); + if (ret) { + goto err; + } + if (cds->cda & (bsz - 1)) { + ret = -EINVAL; /* channel program check */ + goto err; + } + } + } + do { + iter_len = MIN(len, cont_left); + if (op != CDS_OP_A) { + ret = address_space_rw(&address_space_memory, cds->cda, + MEMTXATTRS_UNSPECIFIED, buff, iter_len, op); + if (ret != MEMTX_OK) { + /* assume inaccessible address */ + ret = -EINVAL; /* channel program check */ + goto err; + } + } + cds->at_byte += iter_len; + cds->cda += iter_len; + len -= iter_len; + if (!len) { + break; + } + ret = ida_read_next_idaw(cds); + if (ret) { + goto err; + } + cont_left = bsz; + } while (true); + return ret; +err: + cds->flags |= CDS_F_STREAM_BROKEN; + return ret; +} + +void ccw_dstream_init(CcwDataStream *cds, CCW1 const *ccw, ORB const *orb) +{ + /* + * We don't support MIDA (an optional facility) yet and we + * catch this earlier. Just for expressing the precondition. + */ + g_assert(!(orb->ctrl1 & ORB_CTRL1_MASK_MIDAW)); + cds->flags = (orb->ctrl0 & ORB_CTRL0_MASK_I2K ? CDS_F_I2K : 0) | + (orb->ctrl0 & ORB_CTRL0_MASK_C64 ? CDS_F_C64 : 0) | + (orb->ctrl0 & ORB_CTRL0_MASK_FMT ? CDS_F_FMT : 0) | + (ccw->flags & CCW_FLAG_IDA ? CDS_F_IDA : 0); + + cds->count = ccw->count; + cds->cda_orig = ccw->cda; + ccw_dstream_rewind(cds); + if (!(cds->flags & CDS_F_IDA)) { + cds->op_handler = ccw_dstream_rw_noflags; + } else { + cds->op_handler = ccw_dstream_rw_ida; + } +} static int css_interpret_ccw(SubchDev *sch, hwaddr ccw_addr, bool suspend_allowed) @@ -839,6 +1016,7 @@ static int css_interpret_ccw(SubchDev *sch, hwaddr ccw_addr, } /* Look at the command. */ + ccw_dstream_init(&sch->cds, &ccw, &(sch->orb)); switch (ccw.cmd_code) { case CCW_CMD_NOOP: /* Nothing to do. */ @@ -852,8 +1030,8 @@ static int css_interpret_ccw(SubchDev *sch, hwaddr ccw_addr, } } len = MIN(ccw.count, sizeof(sch->sense_data)); - cpu_physical_memory_write(ccw.cda, sch->sense_data, len); - sch->curr_status.scsw.count = ccw.count - len; + ccw_dstream_write_buf(&sch->cds, sch->sense_data, len); + sch->curr_status.scsw.count = ccw_dstream_residual_count(&sch->cds); memset(sch->sense_data, 0, sizeof(sch->sense_data)); ret = 0; break; @@ -879,8 +1057,8 @@ static int css_interpret_ccw(SubchDev *sch, hwaddr ccw_addr, } else { sense_id.reserved = 0; } - cpu_physical_memory_write(ccw.cda, &sense_id, len); - sch->curr_status.scsw.count = ccw.count - len; + ccw_dstream_write_buf(&sch->cds, &sense_id, len); + sch->curr_status.scsw.count = ccw_dstream_residual_count(&sch->cds); ret = 0; break; } |