@@ -275,10 +275,46 @@ static enum es_result vc_ioio_exitinfo(struct es_em_ctxt *ctxt, u64 *exitinfo)
return ES_OK;
}
+static enum es_result vc_insn_string_read(struct es_em_ctxt *ctxt,
+ void *src, unsigned char *buf,
+ unsigned int data_size,
+ unsigned int count,
+ bool backwards)
+{
+ int i, b = backwards ? -1 : 1;
+
+ for (i = 0; i < count; i++) {
+ void *s = src + (i * data_size * b);
+ unsigned char *d = buf + (i * data_size);
+
+ memcpy(d, s, data_size);
+ }
+
+ return ES_OK;
+}
+
+static enum es_result vc_insn_string_write(struct es_em_ctxt *ctxt,
+ void *dst, unsigned char *buf,
+ unsigned int data_size,
+ unsigned int count,
+ bool backwards)
+{
+ int i, s = backwards ? -1 : 1;
+
+ for (i = 0; i < count; i++) {
+ void *d = dst + (i * data_size * s);
+ unsigned char *b = buf + (i * data_size);
+
+ memcpy(d, b, data_size);
+ }
+
+ return ES_OK;
+}
+
static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
{
struct ex_regs *regs = ctxt->regs;
- u64 exit_info_1;
+ u64 exit_info_1, exit_info_2;
enum es_result ret;
ret = vc_ioio_exitinfo(ctxt, &exit_info_1);
@@ -286,7 +322,75 @@ static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
return ret;
if (exit_info_1 & SVM_IOIO_TYPE_STR) {
- ret = ES_VMM_ERROR;
+ /* (REP) INS/OUTS */
+
+ bool df = ((regs->rflags & X86_EFLAGS_DF) == X86_EFLAGS_DF);
+ unsigned int io_bytes, exit_bytes;
+ unsigned int ghcb_count, op_count;
+ unsigned long es_base;
+ u64 sw_scratch;
+
+ /*
+ * For the string variants with rep prefix the amount of in/out
+ * operations per #VC exception is limited so that the kernel
+ * has a chance to take interrupts and re-schedule while the
+ * instruction is emulated.
+ */
+ io_bytes = (exit_info_1 >> 4) & 0x7;
+ ghcb_count = sizeof(ghcb->shared_buffer) / io_bytes;
+
+ op_count = (exit_info_1 & SVM_IOIO_REP) ? regs->rcx : 1;
+ exit_info_2 = op_count < ghcb_count ? op_count : ghcb_count;
+ exit_bytes = exit_info_2 * io_bytes;
+
+ es_base = 0;
+
+ /* Read bytes of OUTS into the shared buffer */
+ if (!(exit_info_1 & SVM_IOIO_TYPE_IN)) {
+ ret = vc_insn_string_read(ctxt,
+ (void *)(es_base + regs->rsi),
+ ghcb->shared_buffer, io_bytes,
+ exit_info_2, df);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Issue an VMGEXIT to the HV to consume the bytes from the
+ * shared buffer or to have it write them into the shared buffer
+ * depending on the instruction: OUTS or INS.
+ */
+ sw_scratch = __pa(ghcb) + offsetof(struct ghcb, shared_buffer);
+ ghcb_set_sw_scratch(ghcb, sw_scratch);
+ ret = sev_es_ghcb_hv_call(ghcb, ctxt, SVM_EXIT_IOIO,
+ exit_info_1, exit_info_2);
+ if (ret != ES_OK)
+ return ret;
+
+ /* Read bytes from shared buffer into the guest's destination. */
+ if (exit_info_1 & SVM_IOIO_TYPE_IN) {
+ ret = vc_insn_string_write(ctxt,
+ (void *)(es_base + regs->rdi),
+ ghcb->shared_buffer, io_bytes,
+ exit_info_2, df);
+ if (ret)
+ return ret;
+
+ if (df)
+ regs->rdi -= exit_bytes;
+ else
+ regs->rdi += exit_bytes;
+ } else {
+ if (df)
+ regs->rsi -= exit_bytes;
+ else
+ regs->rsi += exit_bytes;
+ }
+
+ if (exit_info_1 & SVM_IOIO_REP)
+ regs->rcx -= exit_info_2;
+
+ ret = regs->rcx ? ES_RETRY : ES_OK;
} else {
/* IN/OUT into/from rAX */