| 1 | /* |
| 2 | * S390 Channel I/O |
| 3 | * |
| 4 | * Copyright (c) 2013 Alexander Graf <agraf@suse.de> |
| 5 | * Copyright (c) 2019 IBM Corp. |
| 6 | * |
| 7 | * Author(s): Jason J. Herne <jjherne@us.ibm.com> |
| 8 | * |
| 9 | * This work is licensed under the terms of the GNU GPL, version 2 or (at |
| 10 | * your option) any later version. See the COPYING file in the top-level |
| 11 | * directory. |
| 12 | */ |
| 13 | |
| 14 | #include <string.h> |
| 15 | #include <stdio.h> |
| 16 | #include "s390-ccw.h" |
| 17 | #include "s390-arch.h" |
| 18 | #include "helper.h" |
| 19 | #include "cio.h" |
| 20 | |
| 21 | static char chsc_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE))); |
| 22 | |
| 23 | static int __do_cio(SubChannelId schid, uint32_t ccw_addr, int fmt, Irb *irb); |
| 24 | |
| 25 | int enable_mss_facility(void) |
| 26 | { |
| 27 | int ret; |
| 28 | ChscAreaSda *sda_area = (ChscAreaSda *) chsc_page; |
| 29 | |
| 30 | memset(sda_area, 0, PAGE_SIZE); |
| 31 | sda_area->request.length = 0x0400; |
| 32 | sda_area->request.code = 0x0031; |
| 33 | sda_area->operation_code = 0x2; |
| 34 | |
| 35 | ret = chsc(sda_area); |
| 36 | if ((ret == 0) && (sda_area->response.code == 0x0001)) { |
| 37 | return 0; |
| 38 | } |
| 39 | return -EIO; |
| 40 | } |
| 41 | |
| 42 | void enable_subchannel(SubChannelId schid) |
| 43 | { |
| 44 | Schib schib; |
| 45 | |
| 46 | stsch_err(schid, &schib); |
| 47 | schib.pmcw.ena = 1; |
| 48 | msch(schid, &schib); |
| 49 | } |
| 50 | |
| 51 | uint16_t cu_type(SubChannelId schid) |
| 52 | { |
| 53 | SenseId sense_data; |
| 54 | Ccw1 sense_id_ccw = { |
| 55 | .cmd_code = CCW_CMD_SENSE_ID, |
| 56 | .flags = CCW_FLAG_SLI, |
| 57 | .count = sizeof(sense_data), |
| 58 | .cda = ptr2u32(&sense_data), |
| 59 | }; |
| 60 | |
| 61 | if (do_cio(schid, CU_TYPE_UNKNOWN, ptr2u32(&sense_id_ccw), CCW_FMT1)) { |
| 62 | puts("Failed to run SenseID CCW"); |
| 63 | return CU_TYPE_UNKNOWN; |
| 64 | } |
| 65 | |
| 66 | return sense_data.cu_type; |
| 67 | } |
| 68 | |
| 69 | int basic_sense(SubChannelId schid, uint16_t cutype, void *sense_data, |
| 70 | uint16_t data_size) |
| 71 | { |
| 72 | Ccw1 senseCcw = { |
| 73 | .cmd_code = CCW_CMD_BASIC_SENSE, |
| 74 | .count = data_size, |
| 75 | .cda = ptr2u32(sense_data), |
| 76 | }; |
| 77 | Irb irb; |
| 78 | |
| 79 | return __do_cio(schid, ptr2u32(&senseCcw), CCW_FMT1, &irb); |
| 80 | } |
| 81 | |
| 82 | static bool irb_error(Irb *irb) |
| 83 | { |
| 84 | if (irb->scsw.cstat) { |
| 85 | return true; |
| 86 | } |
| 87 | return irb->scsw.dstat != (SCSW_DSTAT_DEVEND | SCSW_DSTAT_CHEND); |
| 88 | } |
| 89 | |
| 90 | static void print_eckd_dasd_sense_data(SenseDataEckdDasd *sd) |
| 91 | { |
| 92 | char msgline[512]; |
| 93 | |
| 94 | if (sd->config_info & 0x8000) { |
| 95 | puts("Eckd Dasd Sense Data (fmt 24-bytes):"); |
| 96 | } else { |
| 97 | puts("Eckd Dasd Sense Data (fmt 32-bytes):"); |
| 98 | } |
| 99 | |
| 100 | strcat(msgline, " Sense Condition Flags :"); |
| 101 | if (sd->common_status & SNS_STAT0_CMD_REJECT) { |
| 102 | strcat(msgline, " [Cmd-Reject]"); |
| 103 | } |
| 104 | if (sd->common_status & SNS_STAT0_INTERVENTION_REQ) { |
| 105 | strcat(msgline, " [Intervention-Required]"); |
| 106 | } |
| 107 | if (sd->common_status & SNS_STAT0_BUS_OUT_CHECK) { |
| 108 | strcat(msgline, " [Bus-Out-Parity-Check]"); |
| 109 | } |
| 110 | if (sd->common_status & SNS_STAT0_EQUIPMENT_CHECK) { |
| 111 | strcat(msgline, " [Equipment-Check]"); |
| 112 | } |
| 113 | if (sd->common_status & SNS_STAT0_DATA_CHECK) { |
| 114 | strcat(msgline, " [Data-Check]"); |
| 115 | } |
| 116 | if (sd->common_status & SNS_STAT0_OVERRUN) { |
| 117 | strcat(msgline, " [Overrun]"); |
| 118 | } |
| 119 | if (sd->common_status & SNS_STAT0_INCOMPL_DOMAIN) { |
| 120 | strcat(msgline, " [Incomplete-Domain]"); |
| 121 | } |
| 122 | |
| 123 | if (sd->status[0] & SNS_STAT1_PERM_ERR) { |
| 124 | strcat(msgline, " [Permanent-Error]"); |
| 125 | } |
| 126 | if (sd->status[0] & SNS_STAT1_INV_TRACK_FORMAT) { |
| 127 | strcat(msgline, " [Invalid-Track-Fmt]"); |
| 128 | } |
| 129 | if (sd->status[0] & SNS_STAT1_EOC) { |
| 130 | strcat(msgline, " [End-of-Cyl]"); |
| 131 | } |
| 132 | if (sd->status[0] & SNS_STAT1_MESSAGE_TO_OPER) { |
| 133 | strcat(msgline, " [Operator-Msg]"); |
| 134 | } |
| 135 | if (sd->status[0] & SNS_STAT1_NO_REC_FOUND) { |
| 136 | strcat(msgline, " [No-Record-Found]"); |
| 137 | } |
| 138 | if (sd->status[0] & SNS_STAT1_FILE_PROTECTED) { |
| 139 | strcat(msgline, " [File-Protected]"); |
| 140 | } |
| 141 | if (sd->status[0] & SNS_STAT1_WRITE_INHIBITED) { |
| 142 | strcat(msgline, " [Write-Inhibited]"); |
| 143 | } |
| 144 | if (sd->status[0] & SNS_STAT1_IMPRECISE_END) { |
| 145 | strcat(msgline, " [Imprecise-Ending]"); |
| 146 | } |
| 147 | |
| 148 | if (sd->status[1] & SNS_STAT2_REQ_INH_WRITE) { |
| 149 | strcat(msgline, " [Req-Inhibit-Write]"); |
| 150 | } |
| 151 | if (sd->status[1] & SNS_STAT2_CORRECTABLE) { |
| 152 | strcat(msgline, " [Correctable-Data-Check]"); |
| 153 | } |
| 154 | if (sd->status[1] & SNS_STAT2_FIRST_LOG_ERR) { |
| 155 | strcat(msgline, " [First-Error-Log]"); |
| 156 | } |
| 157 | if (sd->status[1] & SNS_STAT2_ENV_DATA_PRESENT) { |
| 158 | strcat(msgline, " [Env-Data-Present]"); |
| 159 | } |
| 160 | if (sd->status[1] & SNS_STAT2_IMPRECISE_END) { |
| 161 | strcat(msgline, " [Imprecise-End]"); |
| 162 | } |
| 163 | puts(msgline); |
| 164 | |
| 165 | printf(" Residual Count = 0x%X\n", sd->res_count); |
| 166 | printf(" Phys Drive ID = 0x%X\n", sd->phys_drive_id); |
| 167 | printf(" low cyl address = 0x%X\n", sd->low_cyl_addr); |
| 168 | printf(" head addr & hi cyl = 0x%X\n", sd->head_high_cyl_addr); |
| 169 | printf(" format/message = 0x%X\n", sd->fmt_msg); |
| 170 | printf(" fmt-dependent[0-7] = 0x%llX\n", sd->fmt_dependent_info[0]); |
| 171 | printf(" fmt-dependent[8-15]= 0x%llX\n", sd->fmt_dependent_info[1]); |
| 172 | printf(" prog action code = 0x%X\n", sd->program_action_code); |
| 173 | printf(" Configuration info = 0x%X\n", sd->config_info); |
| 174 | printf(" mcode / hi-cyl = 0x%X\n", sd->mcode_hicyl); |
| 175 | printf(" cyl & head addr [0]= 0x%X\n", sd->cyl_head_addr[0]); |
| 176 | printf(" cyl & head addr [1]= 0x%X\n", sd->cyl_head_addr[1]); |
| 177 | printf(" cyl & head addr [2]= 0x%X\n", sd->cyl_head_addr[2]); |
| 178 | } |
| 179 | |
| 180 | static void print_irb_err(Irb *irb) |
| 181 | { |
| 182 | uint64_t this_ccw = *(uint64_t *)u32toptr(irb->scsw.cpa); |
| 183 | uint64_t prev_ccw = *(uint64_t *)u32toptr(irb->scsw.cpa - 8); |
| 184 | char msgline[256]; |
| 185 | |
| 186 | puts("Interrupt Response Block Data:"); |
| 187 | |
| 188 | strcat(msgline, " Function Ctrl :"); |
| 189 | if (irb->scsw.ctrl & SCSW_FCTL_START_FUNC) { |
| 190 | strcat(msgline, " [Start]"); |
| 191 | } |
| 192 | if (irb->scsw.ctrl & SCSW_FCTL_HALT_FUNC) { |
| 193 | strcat(msgline, " [Halt]"); |
| 194 | } |
| 195 | if (irb->scsw.ctrl & SCSW_FCTL_CLEAR_FUNC) { |
| 196 | strcat(msgline, " [Clear]"); |
| 197 | } |
| 198 | puts(msgline); |
| 199 | |
| 200 | msgline[0] = '\0'; |
| 201 | strcat(msgline, " Activity Ctrl :"); |
| 202 | if (irb->scsw.ctrl & SCSW_ACTL_RESUME_PEND) { |
| 203 | strcat(msgline, " [Resume-Pending]"); |
| 204 | } |
| 205 | if (irb->scsw.ctrl & SCSW_ACTL_START_PEND) { |
| 206 | strcat(msgline, " [Start-Pending]"); |
| 207 | } |
| 208 | if (irb->scsw.ctrl & SCSW_ACTL_HALT_PEND) { |
| 209 | strcat(msgline, " [Halt-Pending]"); |
| 210 | } |
| 211 | if (irb->scsw.ctrl & SCSW_ACTL_CLEAR_PEND) { |
| 212 | strcat(msgline, " [Clear-Pending]"); |
| 213 | } |
| 214 | if (irb->scsw.ctrl & SCSW_ACTL_CH_ACTIVE) { |
| 215 | strcat(msgline, " [Channel-Active]"); |
| 216 | } |
| 217 | if (irb->scsw.ctrl & SCSW_ACTL_DEV_ACTIVE) { |
| 218 | strcat(msgline, " [Device-Active]"); |
| 219 | } |
| 220 | if (irb->scsw.ctrl & SCSW_ACTL_SUSPENDED) { |
| 221 | strcat(msgline, " [Suspended]"); |
| 222 | } |
| 223 | puts(msgline); |
| 224 | |
| 225 | msgline[0] = '\0'; |
| 226 | strcat(msgline, " Status Ctrl :"); |
| 227 | if (irb->scsw.ctrl & SCSW_SCTL_ALERT) { |
| 228 | strcat(msgline, " [Alert]"); |
| 229 | } |
| 230 | if (irb->scsw.ctrl & SCSW_SCTL_INTERMED) { |
| 231 | strcat(msgline, " [Intermediate]"); |
| 232 | } |
| 233 | if (irb->scsw.ctrl & SCSW_SCTL_PRIMARY) { |
| 234 | strcat(msgline, " [Primary]"); |
| 235 | } |
| 236 | if (irb->scsw.ctrl & SCSW_SCTL_SECONDARY) { |
| 237 | strcat(msgline, " [Secondary]"); |
| 238 | } |
| 239 | if (irb->scsw.ctrl & SCSW_SCTL_STATUS_PEND) { |
| 240 | strcat(msgline, " [Status-Pending]"); |
| 241 | } |
| 242 | puts(msgline); |
| 243 | |
| 244 | msgline[0] = '\0'; |
| 245 | strcat(msgline, " Device Status :"); |
| 246 | if (irb->scsw.dstat & SCSW_DSTAT_ATTN) { |
| 247 | strcat(msgline, " [Attention]"); |
| 248 | } |
| 249 | if (irb->scsw.dstat & SCSW_DSTAT_STATMOD) { |
| 250 | strcat(msgline, " [Status-Modifier]"); |
| 251 | } |
| 252 | if (irb->scsw.dstat & SCSW_DSTAT_CUEND) { |
| 253 | strcat(msgline, " [Ctrl-Unit-End]"); |
| 254 | } |
| 255 | if (irb->scsw.dstat & SCSW_DSTAT_BUSY) { |
| 256 | strcat(msgline, " [Busy]"); |
| 257 | } |
| 258 | if (irb->scsw.dstat & SCSW_DSTAT_CHEND) { |
| 259 | strcat(msgline, " [Channel-End]"); |
| 260 | } |
| 261 | if (irb->scsw.dstat & SCSW_DSTAT_DEVEND) { |
| 262 | strcat(msgline, " [Device-End]"); |
| 263 | } |
| 264 | if (irb->scsw.dstat & SCSW_DSTAT_UCHK) { |
| 265 | strcat(msgline, " [Unit-Check]"); |
| 266 | } |
| 267 | if (irb->scsw.dstat & SCSW_DSTAT_UEXCP) { |
| 268 | strcat(msgline, " [Unit-Exception]"); |
| 269 | } |
| 270 | puts(msgline); |
| 271 | |
| 272 | msgline[0] = '\0'; |
| 273 | strcat(msgline, " Channel Status :"); |
| 274 | if (irb->scsw.cstat & SCSW_CSTAT_PCINT) { |
| 275 | strcat(msgline, " [Program-Ctrl-Interruption]"); |
| 276 | } |
| 277 | if (irb->scsw.cstat & SCSW_CSTAT_BADLEN) { |
| 278 | strcat(msgline, " [Incorrect-Length]"); |
| 279 | } |
| 280 | if (irb->scsw.cstat & SCSW_CSTAT_PROGCHK) { |
| 281 | strcat(msgline, " [Program-Check]"); |
| 282 | } |
| 283 | if (irb->scsw.cstat & SCSW_CSTAT_PROTCHK) { |
| 284 | strcat(msgline, " [Protection-Check]"); |
| 285 | } |
| 286 | if (irb->scsw.cstat & SCSW_CSTAT_CHDCHK) { |
| 287 | strcat(msgline, " [Channel-Data-Check]"); |
| 288 | } |
| 289 | if (irb->scsw.cstat & SCSW_CSTAT_CHCCHK) { |
| 290 | strcat(msgline, " [Channel-Ctrl-Check]"); |
| 291 | } |
| 292 | if (irb->scsw.cstat & SCSW_CSTAT_ICCHK) { |
| 293 | strcat(msgline, " [Interface-Ctrl-Check]"); |
| 294 | } |
| 295 | if (irb->scsw.cstat & SCSW_CSTAT_CHAINCHK) { |
| 296 | strcat(msgline, " [Chaining-Check]"); |
| 297 | } |
| 298 | puts(msgline); |
| 299 | |
| 300 | printf(" cpa= 0x%X\n", irb->scsw.cpa); |
| 301 | printf(" prev_ccw= 0x%llX\n", prev_ccw); |
| 302 | printf(" this_ccw= 0x%llX\n", this_ccw); |
| 303 | } |
| 304 | |
| 305 | /* |
| 306 | * Handles executing ssch, tsch and returns the irb obtained from tsch. |
| 307 | * Returns 0 on success, -1 if unexpected status pending and we need to retry, |
| 308 | * otherwise returns condition code from ssch/tsch for error cases. |
| 309 | */ |
| 310 | static int __do_cio(SubChannelId schid, uint32_t ccw_addr, int fmt, Irb *irb) |
| 311 | { |
| 312 | /* |
| 313 | * QEMU's CIO implementation requires prefetch and 64-bit idaws. We |
| 314 | * allow all paths. |
| 315 | */ |
| 316 | CmdOrb orb = { |
| 317 | .fmt = fmt, |
| 318 | .pfch = 1, |
| 319 | .c64 = 1, |
| 320 | .lpm = 0xFF, |
| 321 | .cpa = ccw_addr, |
| 322 | }; |
| 323 | int rc; |
| 324 | |
| 325 | IPL_assert(fmt == 0 || fmt == 1, "Invalid ccw format"); |
| 326 | |
| 327 | /* ccw_addr must be <= 24 bits and point to at least one whole ccw. */ |
| 328 | if (fmt == 0) { |
| 329 | IPL_assert(ccw_addr <= 0xFFFFFF - 8, "Invalid ccw address"); |
| 330 | } |
| 331 | |
| 332 | rc = ssch(schid, &orb); |
| 333 | if (rc == 1 || rc == 2) { |
| 334 | /* Subchannel status pending or busy. Eat status and ask for retry. */ |
| 335 | tsch(schid, irb); |
| 336 | return -1; |
| 337 | } |
| 338 | if (rc) { |
| 339 | printf("ssch failed with cc= 0x%x\n", rc); |
| 340 | return rc; |
| 341 | } |
| 342 | |
| 343 | consume_io_int(); |
| 344 | |
| 345 | /* collect status */ |
| 346 | rc = tsch(schid, irb); |
| 347 | if (rc) { |
| 348 | printf("tsch failed with cc= 0x%X\n", rc); |
| 349 | } |
| 350 | |
| 351 | return rc; |
| 352 | } |
| 353 | |
| 354 | /* |
| 355 | * Executes a channel program at a given subchannel. The request to run the |
| 356 | * channel program is sent to the subchannel, we then wait for the interrupt |
| 357 | * signaling completion of the I/O operation(s) performed by the channel |
| 358 | * program. Lastly we verify that the i/o operation completed without error and |
| 359 | * that the interrupt we received was for the subchannel used to run the |
| 360 | * channel program. |
| 361 | * |
| 362 | * Note: This function assumes it is running in an environment where no other |
| 363 | * cpus are generating or receiving I/O interrupts. So either run it in a |
| 364 | * single-cpu environment or make sure all other cpus are not doing I/O and |
| 365 | * have I/O interrupts masked off. We also assume that only one device is |
| 366 | * active (generating i/o interrupts). |
| 367 | * |
| 368 | * Returns non-zero on error. |
| 369 | */ |
| 370 | int do_cio(SubChannelId schid, uint16_t cutype, uint32_t ccw_addr, int fmt) |
| 371 | { |
| 372 | Irb irb = {}; |
| 373 | SenseDataEckdDasd sd; |
| 374 | int rc, retries = 0; |
| 375 | |
| 376 | while (true) { |
| 377 | rc = __do_cio(schid, ccw_addr, fmt, &irb); |
| 378 | |
| 379 | if (rc == -1) { |
| 380 | retries++; |
| 381 | continue; |
| 382 | } |
| 383 | if (rc) { |
| 384 | /* ssch/tsch error. Message already reported by __do_cio */ |
| 385 | break; |
| 386 | } |
| 387 | |
| 388 | if (!irb_error(&irb)) { |
| 389 | break; |
| 390 | } |
| 391 | |
| 392 | /* |
| 393 | * Unexpected unit check, or interface-control-check. Use sense to |
| 394 | * clear (unit check only) then retry. |
| 395 | */ |
| 396 | if ((unit_check(&irb) || iface_ctrl_check(&irb)) && retries <= 2) { |
| 397 | if (unit_check(&irb)) { |
| 398 | basic_sense(schid, cutype, &sd, sizeof(sd)); |
| 399 | } |
| 400 | retries++; |
| 401 | continue; |
| 402 | } |
| 403 | |
| 404 | printf("cio device error\n"); |
| 405 | printf(" ssid 0x%X\n", schid.ssid); |
| 406 | printf(" cssid 0x%X\n", schid.cssid); |
| 407 | printf(" sch_no 0x%X\n", schid.sch_no); |
| 408 | printf(" ctrl-unit type 0x%X\n", cutype); |
| 409 | printf("\n"); |
| 410 | print_irb_err(&irb); |
| 411 | if (cutype == CU_TYPE_DASD_3990 || cutype == CU_TYPE_DASD_2107 || |
| 412 | cutype == CU_TYPE_UNKNOWN) { |
| 413 | if (!basic_sense(schid, cutype, &sd, sizeof(sd))) { |
| 414 | print_eckd_dasd_sense_data(&sd); |
| 415 | } |
| 416 | } |
| 417 | rc = -1; |
| 418 | break; |
| 419 | } |
| 420 | |
| 421 | return rc; |
| 422 | } |