|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [PATCH v2 24/39] xen/riscv: add helpers for decoding a trapped load or store
On 9/14/26 1:03 PM, Jan Beulich wrote: On 27.08.2026 17:21, Oleksii Kurochko wrote:emulate_load() and emulate_store() will both need to obtain the instruction which caused a guest MMIO trap, decode it, and locate the register operand it names. Add what the two share, ahead of either of them being implemented: struct decoded_insn, insn_fetch_faulted(), decode_ldst_insn(), guest_xlen(), guest_gpr() and advance_pc(). The mask/match chain is adapted from Linux's KVM RISC-V implementation. Nothing calls any of this yet, so tag the functions __maybe_unused to keep the build going; the tags go away once emulate_load() and emulate_store() gain their bodies later. I think it could be really an option. I will rework in that way. @@ -13,9 +14,29 @@ #include <asm/csr.h> #include <asm/current.h> #include <asm/emulate.h> +#include <asm/guest_access.h> +#include <asm/processor.h> #include <asm/riscv_encoding.h> #include <asm/traps.h>+/*
We could compress the structure into 8 bytes:
struct decoded_insn {
/*
* The instruction itself: no ratified extension defines one wider than
* 32 bits, and insn_fetch_faulted() rejects anything longer.
*/
uint32_t insn;
/* Length of the instruction in bytes: 2 or 4. */
unsigned int insn_len:3;
/* Width of the memory access, in bytes: 1, 2, 4 or 8. */
unsigned int len:4;
/* Number of the register operand: rd for a load, rs2 for a store. */
unsigned int reg:5;
/* The access is a store rather than a load. */
bool is_write:1;
/* The load zero-extends its result rather than sign-extending it. */
bool is_unsigned:1;
};
One thing in any event: Why would the insn field need to be wider than 32 bits? Initial idea was that htinst register is HSXLEN so here it will be nice to emphasize this. But considering that read_guest() can read maximum 32-bit instructions and no ratified extension defines one wider than 32 bits, and insn_fetch_faulted() rejects anything longer then we could really use here uint32_t for insn + the comment will be useful:
struct decoded_insn {
/*
* The trapped instruction: as read from guest memory, or the 32-bit
* equivalent the hardware transformed it into (see
insn_fetch_faulted()).
* None of the extensions exposed to guests has instructions wider than
* 32 bits, and insn_fetch_faulted() rejects anything longer.
*/
uint32_t insn;
I will drop then function and just open-code where it is used.
good point. I'll apply.
I think that no as according to the spec:Whenever XLEN in any mode is set to a value less than the widest supported XLEN, all operations must ignore source operand register bits above the configured XLEN, and must sign-extend results to fill the entire widest supported XLEN in the destination register. Similarly, pc bits above XLEN are ignored, and when the pc is written, it is sign-extended to fill the widest supported XLEN. I think we want to add the following to the comment above guest_gpr(): * The register is held at its full width, whatever the guest's XLEN is (see* guest_xlen()). Where XLEN is narrower, the bits above it are not guaranteed * to be zero, nor even a sign extension: hardware only ignores them in source * operands, so they may have been left there by more privileged code running * at a wider XLEN. Hence callers must not rely on those bits when reading a * value, and must sign-extend what they write from bit XLEN-1, as hardware * does for the result of an operation.
Right, insn_len is the length of the trapped instruction in guest memory, which is what advance_pc() needs, whereas insn may hold its 32-bit transformed equivalent. I'll clarify both comments in the struct.:
struct decoded_insn {
/*
* The trapped instruction: as read from guest memory, or the 32-bit
* equivalent the hardware transformed it into (see
insn_fetch_faulted()).
* None of the extensions exposed to guests has instructions wider than
* 32 bits, and insn_fetch_faulted() rejects anything longer.
*/
uint32_t insn;
/*
* Length in bytes of the trapped instruction in guest memory: 2 or 4.
* This need not be the length of the encoding in insn, as a compressed
* instruction may have been transformed into its 32-bit equivalent.
*/
unsigned int insn_len:3;
It can't: on a fault read_guest() overwrites it. The exception table fixup records the sepc of the nested trap, i.e. Xen's own PC at the faulting hlvx, while the trap is to be reported to the guest at its instruction. I'll add a comment saying so:
/*
* Not set in the initializer: on a fault read_guest()
leaves in
* utrap.sepc the address of its own faulting access,
whereas the
* trap is to be reported at the guest instruction.
*/
utrap.sepc = regs->sepc;
+ trap_redirect(&utrap); + + return true; + } + + /* + * riscv_read_guest() fetches at most two halfwords, so a wider + * encoding has been read in part only and cannot be decoded here. + * + * Report an illegal instruction, which is what the guest would have + * got for such an encoding anyway: the ISA defines no instruction + * wider than 32 bits. + */Such wording is at risk of going stale. Better say that no guest-exposed extensions have wider than 32-bit insns.
I will re-word in the following way:
/*
* read_guest() fetches at most two halfwords, so a wider
encoding has
* been read in part only and cannot be decoded here.
*
* Report an illegal instruction: none of the extensions exposed to
* guests has instructions wider than 32 bits, so such an
encoding is
* not a valid instruction for the guest in the first place.
*/
Right, I'll mention that as well:
/*
* stval is left zero, which the spec allows for an illegal
* instruction: only part of the instruction is in hand,
and stval,
* being only XLEN bits wide, may not be able to hold all of it
* anyway.
*/
+ trap_redirect(&utrap); + + return true; + } + + di->insn_len = INSN_LEN(di->insn); I will do in this way as INSN_LEN() will (after a conversation in another thread) return zero if insn is something not 16 or 32:
di->insn_len = INSN_LEN(di->insn);
...
if ( !di->insn_len )
The callers don't actually zero it at present: di is declared without an initializer in both emulate_load() and emulate_store(), which is why these two fields get reset here. But I agree that's the better way: I'll have both callers initialize di with {} and drop the resets, stating in the comment that @di is expected to start out zeroed: "... insn_fetch_faulted()). Fields which don't apply to the instruction are left * alone, so @di is expected to start out zeroed. "
I think that I don't know how to do that better at the moment.
It could be less of if/else if to do in this way:
static bool decode_ldst_insn(struct decoded_insn *di, unsigned int xlen)
{
uint32_t insn = di->insn;
unsigned int funct3, width_log2;
if ( INSN_IS_16BIT(insn) )
{
/*
* C.LW, C.LD, C.SW and C.SD (bits[1:0] == 00), and their
sp-relative
* C.*SP forms (bits[1:0] == 10), have bits[15:13] of the form x1y:
* x is set for a store, and y selects a width of 4 or 8 bytes.
*/
funct3 = RV_X(insn, 13, 3);
if ( (insn & 1) || !(funct3 & 2) )
return false;
di->is_write = funct3 & 4;
width_log2 = 2 + (funct3 & 1);
if ( !(insn & 2) )
di->reg = RVC_RS2S(insn);
else if ( di->is_write )
di->reg = RVC_RS2(insn);
else
{
di->reg = RV_RD(insn);
/* C.LWSP and C.LDSP are reserved with rd being x0. */
if ( !di->reg )
return false;
}
}
else
{
/*
* funct3[1:0] is log2 of the width in bytes, and funct3[2] selects
* zero-extension for a load, while being reserved for a store.
*/
funct3 = RV_X(insn, 12, 3);
width_log2 = funct3 & 3;
switch ( insn & INSN_OPCODE_MASK )
{
case INSN_OPCODE_LOAD:
di->is_unsigned = funct3 & 4;
di->reg = RV_RD(insn);
break;
case INSN_OPCODE_STORE:
if ( funct3 & 4 )
return false;
di->is_write = true;
di->reg = RV_RS2(insn);
break;
default:
return false;
}
}
di->len = 1U << width_log2;
/*
* No access is wider than XLEN, and one as wide as XLEN exists only in
* its sign-extending form: this rules out the encodings which
exist for
* XLEN=64 only on a 32-bit guest, including C.FLW for C.LD (and
alike).
*/
if ( (di->len * BITS_PER_BYTE > xlen) ||
(di->is_unsigned && di->len * BITS_PER_BYTE == xlen) )
return false;
return true;
return true;
}
But I am not sure this is what you meant.
+ /* c.lwsp and c.ldsp are reserved with rd being x0. */ + else if ( (insn & INSN_MASK_C_LWSP) == INSN_MATCH_C_LWSP && rd ) + di->len = 4;Careful with insns not part of the base ISA: Between the trap and you getting to fetch and decode, the in-memory insn may have changed. You posibly set yourself up for vulnerabilities if you permit C encodings for guests not having C exposed to them. I think then it will be better to reject it duing instruction fetch in insn_fetch_faulted():
di->insn_len = INSN_LEN(di->insn);
/*
* read_guest() fetches at most two halfwords, so a wider
encoding has
* been read in part only and cannot be decoded here.
*
* Report an illegal instruction: none of the extensions exposed to
* guests has instructions wider than 32 bits, so such an
encoding is
* not a valid instruction for the guest in the first place.
The same
* goes for a compressed encoding where C isn't exposed to the
guest:
* the instruction in memory may have been changed since the
trap, so
* what is read back must not be taken to be what trapped.
*/
if ( !di->insn_len ||
(di->insn_len == 2 &&
!riscv_isa_extension_available(current->domain->arch.isa,
RISCV_ISA_EXT_c)) )
{
...
Would it be better?
Thanks.
~ Oleksii
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |