ARM: Implement support for the IT instruction and the ITSTATE bits of CPSR.
This commit is contained in:
parent
b93ceef538
commit
05bd3eb4ec
|
@ -159,121 +159,133 @@ ISA::setMiscReg(int misc_reg, const MiscReg &val, ThreadContext *tc)
|
||||||
npc = npc | (ULL(1) << PcTBitShift);
|
npc = npc | (ULL(1) << PcTBitShift);
|
||||||
|
|
||||||
tc->setNextPC(npc);
|
tc->setNextPC(npc);
|
||||||
}
|
} else if (misc_reg >= MISCREG_CP15_UNIMP_START &&
|
||||||
if (misc_reg >= MISCREG_CP15_UNIMP_START &&
|
|
||||||
misc_reg < MISCREG_CP15_END) {
|
misc_reg < MISCREG_CP15_END) {
|
||||||
panic("Unimplemented CP15 register %s wrote with %#x.\n",
|
panic("Unimplemented CP15 register %s wrote with %#x.\n",
|
||||||
miscRegName[misc_reg], val);
|
miscRegName[misc_reg], val);
|
||||||
}
|
} else {
|
||||||
switch (misc_reg) {
|
switch (misc_reg) {
|
||||||
case MISCREG_CPACR:
|
case MISCREG_ITSTATE:
|
||||||
{
|
{
|
||||||
CPACR newCpacr = 0;
|
ITSTATE itstate = newVal;
|
||||||
CPACR valCpacr = val;
|
CPSR cpsr = miscRegs[MISCREG_CPSR];
|
||||||
newCpacr.cp10 = valCpacr.cp10;
|
cpsr.it1 = itstate.bottom2;
|
||||||
newCpacr.cp11 = valCpacr.cp11;
|
cpsr.it2 = itstate.top6;
|
||||||
if (newCpacr.cp10 != 0x3 || newCpacr.cp11 != 3) {
|
miscRegs[MISCREG_CPSR] = cpsr;
|
||||||
panic("Disabling coprocessors isn't implemented.\n");
|
DPRINTF(MiscRegs,
|
||||||
|
"Updating ITSTATE -> %#x in CPSR -> %#x.\n",
|
||||||
|
(uint8_t)itstate, (uint32_t)cpsr);
|
||||||
}
|
}
|
||||||
newVal = newCpacr;
|
break;
|
||||||
}
|
case MISCREG_CPACR:
|
||||||
break;
|
{
|
||||||
case MISCREG_CSSELR:
|
CPACR newCpacr = 0;
|
||||||
warn("The csselr register isn't implemented.\n");
|
CPACR valCpacr = val;
|
||||||
break;
|
newCpacr.cp10 = valCpacr.cp10;
|
||||||
case MISCREG_FPSCR:
|
newCpacr.cp11 = valCpacr.cp11;
|
||||||
{
|
if (newCpacr.cp10 != 0x3 || newCpacr.cp11 != 3) {
|
||||||
const uint32_t ones = (uint32_t)(-1);
|
panic("Disabling coprocessors isn't implemented.\n");
|
||||||
FPSCR fpscrMask = 0;
|
}
|
||||||
fpscrMask.ioc = ones;
|
newVal = newCpacr;
|
||||||
fpscrMask.dzc = ones;
|
}
|
||||||
fpscrMask.ofc = ones;
|
break;
|
||||||
fpscrMask.ufc = ones;
|
case MISCREG_CSSELR:
|
||||||
fpscrMask.ixc = ones;
|
warn("The csselr register isn't implemented.\n");
|
||||||
fpscrMask.idc = ones;
|
break;
|
||||||
fpscrMask.len = ones;
|
case MISCREG_FPSCR:
|
||||||
fpscrMask.stride = ones;
|
{
|
||||||
fpscrMask.rMode = ones;
|
const uint32_t ones = (uint32_t)(-1);
|
||||||
fpscrMask.fz = ones;
|
FPSCR fpscrMask = 0;
|
||||||
fpscrMask.dn = ones;
|
fpscrMask.ioc = ones;
|
||||||
fpscrMask.ahp = ones;
|
fpscrMask.dzc = ones;
|
||||||
fpscrMask.qc = ones;
|
fpscrMask.ofc = ones;
|
||||||
fpscrMask.v = ones;
|
fpscrMask.ufc = ones;
|
||||||
fpscrMask.c = ones;
|
fpscrMask.ixc = ones;
|
||||||
fpscrMask.z = ones;
|
fpscrMask.idc = ones;
|
||||||
fpscrMask.n = ones;
|
fpscrMask.len = ones;
|
||||||
newVal = (newVal & (uint32_t)fpscrMask) |
|
fpscrMask.stride = ones;
|
||||||
(miscRegs[MISCREG_FPSCR] & ~(uint32_t)fpscrMask);
|
fpscrMask.rMode = ones;
|
||||||
}
|
fpscrMask.fz = ones;
|
||||||
break;
|
fpscrMask.dn = ones;
|
||||||
case MISCREG_FPEXC:
|
fpscrMask.ahp = ones;
|
||||||
{
|
fpscrMask.qc = ones;
|
||||||
const uint32_t fpexcMask = 0x60000000;
|
fpscrMask.v = ones;
|
||||||
newVal = (newVal & fpexcMask) |
|
fpscrMask.c = ones;
|
||||||
(miscRegs[MISCREG_FPEXC] & ~fpexcMask);
|
fpscrMask.z = ones;
|
||||||
}
|
fpscrMask.n = ones;
|
||||||
break;
|
newVal = (newVal & (uint32_t)fpscrMask) |
|
||||||
case MISCREG_SCTLR:
|
(miscRegs[MISCREG_FPSCR] & ~(uint32_t)fpscrMask);
|
||||||
{
|
}
|
||||||
DPRINTF(MiscRegs, "Writing SCTLR: %#x\n", newVal);
|
break;
|
||||||
SCTLR sctlr = miscRegs[MISCREG_SCTLR];
|
case MISCREG_FPEXC:
|
||||||
SCTLR new_sctlr = newVal;
|
{
|
||||||
new_sctlr.nmfi = (bool)sctlr.nmfi;
|
const uint32_t fpexcMask = 0x60000000;
|
||||||
miscRegs[MISCREG_SCTLR] = (MiscReg)new_sctlr;
|
newVal = (newVal & fpexcMask) |
|
||||||
|
(miscRegs[MISCREG_FPEXC] & ~fpexcMask);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MISCREG_SCTLR:
|
||||||
|
{
|
||||||
|
DPRINTF(MiscRegs, "Writing SCTLR: %#x\n", newVal);
|
||||||
|
SCTLR sctlr = miscRegs[MISCREG_SCTLR];
|
||||||
|
SCTLR new_sctlr = newVal;
|
||||||
|
new_sctlr.nmfi = (bool)sctlr.nmfi;
|
||||||
|
miscRegs[MISCREG_SCTLR] = (MiscReg)new_sctlr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case MISCREG_TLBTR:
|
||||||
|
case MISCREG_MVFR0:
|
||||||
|
case MISCREG_MVFR1:
|
||||||
|
case MISCREG_MPIDR:
|
||||||
|
case MISCREG_FPSID:
|
||||||
|
return;
|
||||||
|
case MISCREG_TLBIALLIS:
|
||||||
|
case MISCREG_TLBIALL:
|
||||||
|
warn("Need to flush all TLBs in MP\n");
|
||||||
|
tc->getITBPtr()->flushAll();
|
||||||
|
tc->getDTBPtr()->flushAll();
|
||||||
|
return;
|
||||||
|
case MISCREG_ITLBIALL:
|
||||||
|
tc->getITBPtr()->flushAll();
|
||||||
|
return;
|
||||||
|
case MISCREG_DTLBIALL:
|
||||||
|
tc->getDTBPtr()->flushAll();
|
||||||
|
return;
|
||||||
|
case MISCREG_TLBIMVAIS:
|
||||||
|
case MISCREG_TLBIMVA:
|
||||||
|
warn("Need to flush all TLBs in MP\n");
|
||||||
|
tc->getITBPtr()->flushMvaAsid(mbits(newVal, 31, 12),
|
||||||
|
bits(newVal, 7,0));
|
||||||
|
tc->getDTBPtr()->flushMvaAsid(mbits(newVal, 31, 12),
|
||||||
|
bits(newVal, 7,0));
|
||||||
|
return;
|
||||||
|
case MISCREG_TLBIASIDIS:
|
||||||
|
case MISCREG_TLBIASID:
|
||||||
|
warn("Need to flush all TLBs in MP\n");
|
||||||
|
tc->getITBPtr()->flushAsid(bits(newVal, 7,0));
|
||||||
|
tc->getDTBPtr()->flushAsid(bits(newVal, 7,0));
|
||||||
|
return;
|
||||||
|
case MISCREG_TLBIMVAAIS:
|
||||||
|
case MISCREG_TLBIMVAA:
|
||||||
|
warn("Need to flush all TLBs in MP\n");
|
||||||
|
tc->getITBPtr()->flushMva(mbits(newVal, 31,12));
|
||||||
|
tc->getDTBPtr()->flushMva(mbits(newVal, 31,12));
|
||||||
|
return;
|
||||||
|
case MISCREG_ITLBIMVA:
|
||||||
|
tc->getITBPtr()->flushMvaAsid(mbits(newVal, 31, 12),
|
||||||
|
bits(newVal, 7,0));
|
||||||
|
return;
|
||||||
|
case MISCREG_DTLBIMVA:
|
||||||
|
tc->getDTBPtr()->flushMvaAsid(mbits(newVal, 31, 12),
|
||||||
|
bits(newVal, 7,0));
|
||||||
|
return;
|
||||||
|
case MISCREG_ITLBIASID:
|
||||||
|
tc->getITBPtr()->flushAsid(bits(newVal, 7,0));
|
||||||
|
return;
|
||||||
|
case MISCREG_DTLBIASID:
|
||||||
|
tc->getDTBPtr()->flushAsid(bits(newVal, 7,0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case MISCREG_TLBTR:
|
|
||||||
case MISCREG_MVFR0:
|
|
||||||
case MISCREG_MVFR1:
|
|
||||||
case MISCREG_MPIDR:
|
|
||||||
case MISCREG_FPSID:
|
|
||||||
return;
|
|
||||||
case MISCREG_TLBIALLIS:
|
|
||||||
case MISCREG_TLBIALL:
|
|
||||||
warn("Need to flush all TLBs in MP\n");
|
|
||||||
tc->getITBPtr()->flushAll();
|
|
||||||
tc->getDTBPtr()->flushAll();
|
|
||||||
return;
|
|
||||||
case MISCREG_ITLBIALL:
|
|
||||||
tc->getITBPtr()->flushAll();
|
|
||||||
return;
|
|
||||||
case MISCREG_DTLBIALL:
|
|
||||||
tc->getDTBPtr()->flushAll();
|
|
||||||
return;
|
|
||||||
case MISCREG_TLBIMVAIS:
|
|
||||||
case MISCREG_TLBIMVA:
|
|
||||||
warn("Need to flush all TLBs in MP\n");
|
|
||||||
tc->getITBPtr()->flushMvaAsid(mbits(newVal, 31, 12),
|
|
||||||
bits(newVal, 7,0));
|
|
||||||
tc->getDTBPtr()->flushMvaAsid(mbits(newVal, 31, 12),
|
|
||||||
bits(newVal, 7,0));
|
|
||||||
return;
|
|
||||||
case MISCREG_TLBIASIDIS:
|
|
||||||
case MISCREG_TLBIASID:
|
|
||||||
warn("Need to flush all TLBs in MP\n");
|
|
||||||
tc->getITBPtr()->flushAsid(bits(newVal, 7,0));
|
|
||||||
tc->getDTBPtr()->flushAsid(bits(newVal, 7,0));
|
|
||||||
return;
|
|
||||||
case MISCREG_TLBIMVAAIS:
|
|
||||||
case MISCREG_TLBIMVAA:
|
|
||||||
warn("Need to flush all TLBs in MP\n");
|
|
||||||
tc->getITBPtr()->flushMva(mbits(newVal, 31,12));
|
|
||||||
tc->getDTBPtr()->flushMva(mbits(newVal, 31,12));
|
|
||||||
return;
|
|
||||||
case MISCREG_ITLBIMVA:
|
|
||||||
tc->getITBPtr()->flushMvaAsid(mbits(newVal, 31, 12),
|
|
||||||
bits(newVal, 7,0));
|
|
||||||
return;
|
|
||||||
case MISCREG_DTLBIMVA:
|
|
||||||
tc->getDTBPtr()->flushMvaAsid(mbits(newVal, 31, 12),
|
|
||||||
bits(newVal, 7,0));
|
|
||||||
return;
|
|
||||||
case MISCREG_ITLBIASID:
|
|
||||||
tc->getITBPtr()->flushAsid(bits(newVal, 7,0));
|
|
||||||
return;
|
|
||||||
case MISCREG_DTLBIASID:
|
|
||||||
tc->getDTBPtr()->flushAsid(bits(newVal, 7,0));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
setMiscRegNoEffect(misc_reg, newVal);
|
setMiscRegNoEffect(misc_reg, newVal);
|
||||||
}
|
}
|
||||||
|
|
|
@ -852,30 +852,70 @@ def format Thumb16ShiftAddSubMoveCmp() {{
|
||||||
const IntRegIndex rm = (IntRegIndex)(uint32_t)bits(machInst, 8, 6);
|
const IntRegIndex rm = (IntRegIndex)(uint32_t)bits(machInst, 8, 6);
|
||||||
switch (bits(machInst, 13, 11)) {
|
switch (bits(machInst, 13, 11)) {
|
||||||
case 0x0: // lsl
|
case 0x0: // lsl
|
||||||
return new MovRegCc(machInst, rd, INTREG_ZERO, rn, imm5, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new MovReg(machInst, rd, INTREG_ZERO, rn, imm5, LSL);
|
||||||
|
} else {
|
||||||
|
return new MovRegCc(machInst, rd, INTREG_ZERO, rn, imm5, LSL);
|
||||||
|
}
|
||||||
case 0x1: // lsr
|
case 0x1: // lsr
|
||||||
return new MovRegCc(machInst, rd, INTREG_ZERO, rn, imm5, LSR);
|
if (machInst.itstateMask) {
|
||||||
|
return new MovReg(machInst, rd, INTREG_ZERO, rn, imm5, LSR);
|
||||||
|
} else {
|
||||||
|
return new MovRegCc(machInst, rd, INTREG_ZERO, rn, imm5, LSR);
|
||||||
|
}
|
||||||
case 0x2: // asr
|
case 0x2: // asr
|
||||||
return new MovRegCc(machInst, rd, INTREG_ZERO, rn, imm5, ASR);
|
if (machInst.itstateMask) {
|
||||||
|
return new MovReg(machInst, rd, INTREG_ZERO, rn, imm5, ASR);
|
||||||
|
} else {
|
||||||
|
return new MovRegCc(machInst, rd, INTREG_ZERO, rn, imm5, ASR);
|
||||||
|
}
|
||||||
case 0x3:
|
case 0x3:
|
||||||
switch (bits(machInst, 10, 9)) {
|
switch (bits(machInst, 10, 9)) {
|
||||||
case 0x0:
|
case 0x0:
|
||||||
return new AddRegCc(machInst, rd, rn, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new AddReg(machInst, rd, rn, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new AddRegCc(machInst, rd, rn, rm, 0, LSL);
|
||||||
|
}
|
||||||
case 0x1:
|
case 0x1:
|
||||||
return new SubRegCc(machInst, rd, rn, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new SubReg(machInst, rd, rn, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new SubRegCc(machInst, rd, rn, rm, 0, LSL);
|
||||||
|
}
|
||||||
case 0x2:
|
case 0x2:
|
||||||
return new AddImmCc(machInst, rd, rn, imm3, true);
|
if (machInst.itstateMask) {
|
||||||
|
return new AddImm(machInst, rd, rn, imm3, true);
|
||||||
|
} else {
|
||||||
|
return new AddImmCc(machInst, rd, rn, imm3, true);
|
||||||
|
}
|
||||||
case 0x3:
|
case 0x3:
|
||||||
return new SubImmCc(machInst, rd, rn, imm3, true);
|
if (machInst.itstateMask) {
|
||||||
|
return new SubImm(machInst, rd, rn, imm3, true);
|
||||||
|
} else {
|
||||||
|
return new SubImmCc(machInst, rd, rn, imm3, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 0x4:
|
case 0x4:
|
||||||
return new MovImmCc(machInst, rd8, INTREG_ZERO, imm8, false);
|
if (machInst.itstateMask) {
|
||||||
|
return new MovImm(machInst, rd8, INTREG_ZERO, imm8, false);
|
||||||
|
} else {
|
||||||
|
return new MovImmCc(machInst, rd8, INTREG_ZERO, imm8, false);
|
||||||
|
}
|
||||||
case 0x5:
|
case 0x5:
|
||||||
return new CmpImmCc(machInst, INTREG_ZERO, rd8, imm8, true);
|
return new CmpImmCc(machInst, INTREG_ZERO, rd8, imm8, true);
|
||||||
case 0x6:
|
case 0x6:
|
||||||
return new AddImmCc(machInst, rd8, rd8, imm8, true);
|
if (machInst.itstateMask) {
|
||||||
|
return new AddImm(machInst, rd8, rd8, imm8, true);
|
||||||
|
} else {
|
||||||
|
return new AddImmCc(machInst, rd8, rd8, imm8, true);
|
||||||
|
}
|
||||||
case 0x7:
|
case 0x7:
|
||||||
return new SubImmCc(machInst, rd8, rd8, imm8, true);
|
if (machInst.itstateMask) {
|
||||||
|
return new SubImm(machInst, rd8, rd8, imm8, true);
|
||||||
|
} else {
|
||||||
|
return new SubImmCc(machInst, rd8, rd8, imm8, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
@ -888,37 +928,97 @@ def format Thumb16DataProcessing() {{
|
||||||
const IntRegIndex rm = (IntRegIndex)(uint32_t)bits(machInst, 5, 3);
|
const IntRegIndex rm = (IntRegIndex)(uint32_t)bits(machInst, 5, 3);
|
||||||
switch (bits(machInst, 9, 6)) {
|
switch (bits(machInst, 9, 6)) {
|
||||||
case 0x0:
|
case 0x0:
|
||||||
return new AndRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new AndReg(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new AndRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
}
|
||||||
case 0x1:
|
case 0x1:
|
||||||
return new EorRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new EorReg(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new EorRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
}
|
||||||
case 0x2: //lsl
|
case 0x2: //lsl
|
||||||
return new MovRegRegCc(machInst, rdn, INTREG_ZERO, rdn, rm, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new MovRegReg(machInst, rdn,
|
||||||
|
INTREG_ZERO, rdn, rm, LSL);
|
||||||
|
} else {
|
||||||
|
return new MovRegRegCc(machInst, rdn,
|
||||||
|
INTREG_ZERO, rdn, rm, LSL);
|
||||||
|
}
|
||||||
case 0x3: //lsr
|
case 0x3: //lsr
|
||||||
return new MovRegRegCc(machInst, rdn, INTREG_ZERO, rdn, rm, LSR);
|
if (machInst.itstateMask) {
|
||||||
|
return new MovRegReg(machInst, rdn,
|
||||||
|
INTREG_ZERO, rdn, rm, LSR);
|
||||||
|
} else {
|
||||||
|
return new MovRegRegCc(machInst, rdn,
|
||||||
|
INTREG_ZERO, rdn, rm, LSR);
|
||||||
|
}
|
||||||
case 0x4: //asr
|
case 0x4: //asr
|
||||||
return new MovRegRegCc(machInst, rdn, INTREG_ZERO, rdn, rm, ASR);
|
if (machInst.itstateMask) {
|
||||||
|
return new MovRegReg(machInst, rdn,
|
||||||
|
INTREG_ZERO, rdn, rm, ASR);
|
||||||
|
} else {
|
||||||
|
return new MovRegRegCc(machInst, rdn,
|
||||||
|
INTREG_ZERO, rdn, rm, ASR);
|
||||||
|
}
|
||||||
case 0x5:
|
case 0x5:
|
||||||
return new AdcRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new AdcReg(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new AdcRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
}
|
||||||
case 0x6:
|
case 0x6:
|
||||||
return new SbcRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new SbcReg(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new SbcRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
}
|
||||||
case 0x7: // ror
|
case 0x7: // ror
|
||||||
return new MovRegRegCc(machInst, rdn, INTREG_ZERO, rdn, rm, ROR);
|
if (machInst.itstateMask) {
|
||||||
|
return new MovRegReg(machInst, rdn,
|
||||||
|
INTREG_ZERO, rdn, rm, ROR);
|
||||||
|
} else {
|
||||||
|
return new MovRegRegCc(machInst, rdn,
|
||||||
|
INTREG_ZERO, rdn, rm, ROR);
|
||||||
|
}
|
||||||
case 0x8:
|
case 0x8:
|
||||||
return new TstRegCc(machInst, INTREG_ZERO, rdn, rm, 0, LSL);
|
return new TstRegCc(machInst, INTREG_ZERO, rdn, rm, 0, LSL);
|
||||||
case 0x9:
|
case 0x9:
|
||||||
return new RsbImmCc(machInst, rdn, rm, 0, true);
|
if (machInst.itstateMask) {
|
||||||
|
return new RsbImm(machInst, rdn, rm, 0, true);
|
||||||
|
} else {
|
||||||
|
return new RsbImmCc(machInst, rdn, rm, 0, true);
|
||||||
|
}
|
||||||
case 0xa:
|
case 0xa:
|
||||||
return new CmpRegCc(machInst, INTREG_ZERO, rdn, rm, 0, LSL);
|
return new CmpRegCc(machInst, INTREG_ZERO, rdn, rm, 0, LSL);
|
||||||
case 0xb:
|
case 0xb:
|
||||||
return new CmnRegCc(machInst, INTREG_ZERO, rdn, rm, 0, LSL);
|
return new CmnRegCc(machInst, INTREG_ZERO, rdn, rm, 0, LSL);
|
||||||
case 0xc:
|
case 0xc:
|
||||||
return new OrrRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new OrrReg(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new OrrRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
}
|
||||||
case 0xd:
|
case 0xd:
|
||||||
return new MulCc(machInst, rdn, rm, rdn);
|
if (machInst.itstateMask) {
|
||||||
|
return new Mul(machInst, rdn, rm, rdn);
|
||||||
|
} else {
|
||||||
|
return new MulCc(machInst, rdn, rm, rdn);
|
||||||
|
}
|
||||||
case 0xe:
|
case 0xe:
|
||||||
return new BicRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new BicReg(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new BicRegCc(machInst, rdn, rdn, rm, 0, LSL);
|
||||||
|
}
|
||||||
case 0xf:
|
case 0xf:
|
||||||
return new MvnRegCc(machInst, rdn, INTREG_ZERO, rm, 0, LSL);
|
if (machInst.itstateMask) {
|
||||||
|
return new MvnReg(machInst, rdn, INTREG_ZERO, rm, 0, LSL);
|
||||||
|
} else {
|
||||||
|
return new MvnRegCc(machInst, rdn, INTREG_ZERO, rm, 0, LSL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
@ -1070,7 +1170,7 @@ def format Thumb16Misc() {{
|
||||||
return new WarnUnimplemented("bkpt", machInst);
|
return new WarnUnimplemented("bkpt", machInst);
|
||||||
case 0xf:
|
case 0xf:
|
||||||
if (bits(machInst, 3, 0) != 0)
|
if (bits(machInst, 3, 0) != 0)
|
||||||
return new WarnUnimplemented("it", machInst);
|
return new ItInst(machInst);
|
||||||
switch (bits(machInst, 7, 4)) {
|
switch (bits(machInst, 7, 4)) {
|
||||||
case 0x0:
|
case 0x0:
|
||||||
return new NopInst(machInst);
|
return new NopInst(machInst);
|
||||||
|
|
|
@ -46,10 +46,6 @@
|
||||||
// Load/store microops
|
// Load/store microops
|
||||||
//
|
//
|
||||||
|
|
||||||
let {{
|
|
||||||
predicateTest = 'testPredicate(CondCodes, condCode)'
|
|
||||||
}};
|
|
||||||
|
|
||||||
let {{
|
let {{
|
||||||
microLdrUopCode = "IWRa = cSwap(Mem.uw, ((CPSR)Cpsr).e);"
|
microLdrUopCode = "IWRa = cSwap(Mem.uw, ((CPSR)Cpsr).e);"
|
||||||
microLdrUopIop = InstObjParams('ldr_uop', 'MicroLdrUop',
|
microLdrUopIop = InstObjParams('ldr_uop', 'MicroLdrUop',
|
||||||
|
|
|
@ -456,10 +456,18 @@ let {{
|
||||||
decoder_output += RegRegRegRegOpConstructor.subst(usada8Iop)
|
decoder_output += RegRegRegRegOpConstructor.subst(usada8Iop)
|
||||||
exec_output += PredOpExecute.subst(usada8Iop)
|
exec_output += PredOpExecute.subst(usada8Iop)
|
||||||
|
|
||||||
nopIop = InstObjParams("nop", "NopInst", "ArmStaticInst", "", [])
|
nopIop = InstObjParams("nop", "NopInst", "PredOp", \
|
||||||
|
{ "code" : "", "predicate_test" : predicateTest })
|
||||||
header_output += BasicDeclare.subst(nopIop)
|
header_output += BasicDeclare.subst(nopIop)
|
||||||
decoder_output += BasicConstructor.subst(nopIop)
|
decoder_output += BasicConstructor.subst(nopIop)
|
||||||
exec_output += BasicExecute.subst(nopIop)
|
exec_output += PredOpExecute.subst(nopIop)
|
||||||
|
|
||||||
|
itIop = InstObjParams("it", "ItInst", "PredOp", \
|
||||||
|
{ "code" : "Itstate = machInst.newItstate;",
|
||||||
|
"predicate_test" : predicateTest })
|
||||||
|
header_output += BasicDeclare.subst(itIop)
|
||||||
|
decoder_output += BasicConstructor.subst(itIop)
|
||||||
|
exec_output += PredOpExecute.subst(itIop)
|
||||||
|
|
||||||
ubfxCode = '''
|
ubfxCode = '''
|
||||||
Dest = bits(Op1, imm2, imm1);
|
Dest = bits(Op1, imm2, imm1);
|
||||||
|
|
|
@ -171,6 +171,7 @@ def operands {{
|
||||||
'Mem': ('Mem', 'uw', None, ('IsMemRef', 'IsLoad', 'IsStore'), 2),
|
'Mem': ('Mem', 'uw', None, ('IsMemRef', 'IsLoad', 'IsStore'), 2),
|
||||||
|
|
||||||
'Cpsr': ('ControlReg', 'uw', 'MISCREG_CPSR', (None, None, 'IsControl'), 1),
|
'Cpsr': ('ControlReg', 'uw', 'MISCREG_CPSR', (None, None, 'IsControl'), 1),
|
||||||
|
'Itstate': ('ControlReg', 'ub', 'MISCREG_ITSTATE', None, 2),
|
||||||
'Spsr': ('ControlReg', 'uw', 'MISCREG_SPSR', None, 2),
|
'Spsr': ('ControlReg', 'uw', 'MISCREG_SPSR', None, 2),
|
||||||
'Fpsr': ('ControlReg', 'uw', 'MISCREG_FPSR', None, 2),
|
'Fpsr': ('ControlReg', 'uw', 'MISCREG_FPSR', None, 2),
|
||||||
'Fpsid': ('ControlReg', 'uw', 'MISCREG_FPSID', None, 2),
|
'Fpsid': ('ControlReg', 'uw', 'MISCREG_FPSID', None, 2),
|
||||||
|
|
|
@ -71,6 +71,10 @@ def template SwapExecute {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -101,6 +105,10 @@ def template SwapInitiateAcc {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -127,6 +135,10 @@ def template SwapCompleteAcc {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -154,6 +166,10 @@ def template LoadExecute {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -186,6 +202,10 @@ def template StoreExecute {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -224,6 +244,10 @@ def template StoreExExecute {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -257,6 +281,10 @@ def template StoreExInitiateAcc {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -290,6 +318,10 @@ def template StoreInitiateAcc {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -340,6 +372,10 @@ def template LoadCompleteAcc {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -361,6 +397,10 @@ def template StoreCompleteAcc {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -385,6 +425,10 @@ def template StoreExCompleteAcc {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
|
@ -46,7 +46,11 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
let {{
|
let {{
|
||||||
predicateTest = 'testPredicate(CondCodes, condCode)'
|
predicateTest = '''
|
||||||
|
testPredicate(CondCodes, machInst.itstateMask ?
|
||||||
|
(ConditionCode)(uint8_t)machInst.itstateCond :
|
||||||
|
condCode)
|
||||||
|
'''
|
||||||
}};
|
}};
|
||||||
|
|
||||||
def template DataImmDeclare {{
|
def template DataImmDeclare {{
|
||||||
|
@ -143,6 +147,10 @@ def template PredOpExecute {{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fault == NoFault && machInst.itstateMask != 0) {
|
||||||
|
xc->setMiscReg(MISCREG_ITSTATE, machInst.newItstate);
|
||||||
|
}
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
|
@ -67,6 +67,7 @@ namespace ArmISA
|
||||||
|
|
||||||
enum MiscRegIndex {
|
enum MiscRegIndex {
|
||||||
MISCREG_CPSR = 0,
|
MISCREG_CPSR = 0,
|
||||||
|
MISCREG_ITSTATE,
|
||||||
MISCREG_SPSR,
|
MISCREG_SPSR,
|
||||||
MISCREG_SPSR_FIQ,
|
MISCREG_SPSR_FIQ,
|
||||||
MISCREG_SPSR_IRQ,
|
MISCREG_SPSR_IRQ,
|
||||||
|
@ -189,7 +190,7 @@ namespace ArmISA
|
||||||
unsigned crm, unsigned opc2);
|
unsigned crm, unsigned opc2);
|
||||||
|
|
||||||
const char * const miscRegName[NUM_MISCREGS] = {
|
const char * const miscRegName[NUM_MISCREGS] = {
|
||||||
"cpsr", "spsr", "spsr_fiq", "spsr_irq", "spsr_svc",
|
"cpsr", "itstate", "spsr", "spsr_fiq", "spsr_irq", "spsr_svc",
|
||||||
"spsr_mon", "spsr_und", "spsr_abt",
|
"spsr_mon", "spsr_und", "spsr_abt",
|
||||||
"fpsr", "fpsid", "fpscr", "fpexc", "mvfr0", "mvfr1",
|
"fpsr", "fpsid", "fpscr", "fpexc", "mvfr0", "mvfr1",
|
||||||
"sctlr_rst", "sev_mailbox",
|
"sctlr_rst", "sev_mailbox",
|
||||||
|
@ -241,6 +242,14 @@ namespace ArmISA
|
||||||
Bitfield<4, 0> mode;
|
Bitfield<4, 0> mode;
|
||||||
EndBitUnion(CPSR)
|
EndBitUnion(CPSR)
|
||||||
|
|
||||||
|
BitUnion8(ITSTATE)
|
||||||
|
Bitfield<7, 4> cond;
|
||||||
|
Bitfield<3, 0> mask;
|
||||||
|
// Bitfields for moving to/from CPSR
|
||||||
|
Bitfield<7, 2> top6;
|
||||||
|
Bitfield<1, 0> bottom2;
|
||||||
|
EndBitUnion(ITSTATE)
|
||||||
|
|
||||||
// This mask selects bits of the CPSR that actually go in the CondCodes
|
// This mask selects bits of the CPSR that actually go in the CondCodes
|
||||||
// integer register to allow renaming.
|
// integer register to allow renaming.
|
||||||
static const uint32_t CondCodesMask = 0xF80F0000;
|
static const uint32_t CondCodesMask = 0xF80F0000;
|
||||||
|
|
|
@ -62,11 +62,21 @@ namespace ArmISA
|
||||||
MachInst data;
|
MachInst data;
|
||||||
bool bigThumb;
|
bool bigThumb;
|
||||||
int offset;
|
int offset;
|
||||||
|
ITSTATE itstate;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
bigThumb = false;
|
||||||
|
offset = 0;
|
||||||
|
emi = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Predecoder(ThreadContext * _tc) :
|
Predecoder(ThreadContext * _tc) :
|
||||||
tc(_tc), data(0), bigThumb(false), offset(0)
|
tc(_tc), data(0)
|
||||||
{}
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
ThreadContext * getTC()
|
ThreadContext * getTC()
|
||||||
{
|
{
|
||||||
|
@ -78,11 +88,24 @@ namespace ArmISA
|
||||||
tc = _tc;
|
tc = _tc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset()
|
void advanceThumbCond()
|
||||||
{
|
{
|
||||||
bigThumb = false;
|
uint8_t condMask = itstate.mask;
|
||||||
offset = 0;
|
uint8_t thumbCond = itstate.cond;
|
||||||
emi = 0;
|
DPRINTF(Predecoder, "Advancing ITSTATE from %#x, %#x.\n",
|
||||||
|
thumbCond, condMask);
|
||||||
|
condMask = condMask << 1;
|
||||||
|
uint8_t newBit = bits(condMask, 4);
|
||||||
|
condMask &= mask(4);
|
||||||
|
if (condMask == 0) {
|
||||||
|
thumbCond = 0;
|
||||||
|
} else {
|
||||||
|
replaceBits(thumbCond, 0, newBit);
|
||||||
|
}
|
||||||
|
DPRINTF(Predecoder, "Advancing ITSTATE to %#x, %#x.\n",
|
||||||
|
thumbCond, condMask);
|
||||||
|
itstate.mask = condMask;
|
||||||
|
itstate.cond = thumbCond;
|
||||||
}
|
}
|
||||||
|
|
||||||
void process()
|
void process()
|
||||||
|
@ -92,7 +115,7 @@ namespace ArmISA
|
||||||
emi.sevenAndFour = bits(data, 7) && bits(data, 4);
|
emi.sevenAndFour = bits(data, 7) && bits(data, 4);
|
||||||
emi.isMisc = (bits(data, 24, 23) == 0x2 &&
|
emi.isMisc = (bits(data, 24, 23) == 0x2 &&
|
||||||
bits(data, 20) == 0);
|
bits(data, 20) == 0);
|
||||||
DPRINTF(Predecoder, "Arm inst.\n");
|
DPRINTF(Predecoder, "Arm inst: %#x.\n", (uint64_t)emi);
|
||||||
} else {
|
} else {
|
||||||
uint16_t word = (data >> (offset * 8));
|
uint16_t word = (data >> (offset * 8));
|
||||||
if (bigThumb) {
|
if (bigThumb) {
|
||||||
|
@ -102,6 +125,11 @@ namespace ArmISA
|
||||||
offset += 2;
|
offset += 2;
|
||||||
DPRINTF(Predecoder, "Second half of 32 bit Thumb: %#x.\n",
|
DPRINTF(Predecoder, "Second half of 32 bit Thumb: %#x.\n",
|
||||||
emi.instBits);
|
emi.instBits);
|
||||||
|
if (itstate.mask) {
|
||||||
|
emi.itstate = itstate;
|
||||||
|
advanceThumbCond();
|
||||||
|
emi.newItstate = itstate;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
uint16_t highBits = word & 0xF800;
|
uint16_t highBits = word & 0xF800;
|
||||||
if (highBits == 0xE800 || highBits == 0xF000 ||
|
if (highBits == 0xE800 || highBits == 0xF000 ||
|
||||||
|
@ -114,6 +142,11 @@ namespace ArmISA
|
||||||
DPRINTF(Predecoder, "All of 32 bit Thumb: %#x.\n",
|
DPRINTF(Predecoder, "All of 32 bit Thumb: %#x.\n",
|
||||||
emi.instBits);
|
emi.instBits);
|
||||||
offset += 4;
|
offset += 4;
|
||||||
|
if (itstate.mask) {
|
||||||
|
emi.itstate = itstate;
|
||||||
|
advanceThumbCond();
|
||||||
|
emi.newItstate = itstate;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// We only have the first half word.
|
// We only have the first half word.
|
||||||
DPRINTF(Predecoder,
|
DPRINTF(Predecoder,
|
||||||
|
@ -130,6 +163,19 @@ namespace ArmISA
|
||||||
emi.condCode = COND_UC;
|
emi.condCode = COND_UC;
|
||||||
DPRINTF(Predecoder, "16 bit Thumb: %#x.\n",
|
DPRINTF(Predecoder, "16 bit Thumb: %#x.\n",
|
||||||
emi.instBits);
|
emi.instBits);
|
||||||
|
if (bits(word, 15, 8) == 0xbf &&
|
||||||
|
bits(word, 3, 0) != 0x0) {
|
||||||
|
emi.itstate = itstate;
|
||||||
|
itstate = bits(word, 7, 0);
|
||||||
|
emi.newItstate = itstate;
|
||||||
|
DPRINTF(Predecoder,
|
||||||
|
"IT detected, cond = %#x, mask = %#x\n",
|
||||||
|
itstate.cond, itstate.mask);
|
||||||
|
} else if (itstate.mask) {
|
||||||
|
emi.itstate = itstate;
|
||||||
|
advanceThumbCond();
|
||||||
|
emi.newItstate = itstate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,6 +191,9 @@ namespace ArmISA
|
||||||
FPSCR fpscr = tc->readMiscReg(MISCREG_FPSCR);
|
FPSCR fpscr = tc->readMiscReg(MISCREG_FPSCR);
|
||||||
emi.fpscrLen = fpscr.len;
|
emi.fpscrLen = fpscr.len;
|
||||||
emi.fpscrStride = fpscr.stride;
|
emi.fpscrStride = fpscr.stride;
|
||||||
|
CPSR cpsr = tc->readMiscReg(MISCREG_CPSR);
|
||||||
|
itstate.top6 = cpsr.it2;
|
||||||
|
itstate.bottom2 = cpsr.it1;
|
||||||
process();
|
process();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,12 @@ namespace ArmISA
|
||||||
typedef uint32_t MachInst;
|
typedef uint32_t MachInst;
|
||||||
|
|
||||||
BitUnion64(ExtMachInst)
|
BitUnion64(ExtMachInst)
|
||||||
|
Bitfield<63, 56> newItstate;
|
||||||
|
// ITSTATE bits
|
||||||
|
Bitfield<55, 48> itstate;
|
||||||
|
Bitfield<55, 52> itstateCond;
|
||||||
|
Bitfield<51, 48> itstateMask;
|
||||||
|
|
||||||
// FPSCR fields
|
// FPSCR fields
|
||||||
Bitfield<41, 40> fpscrStride;
|
Bitfield<41, 40> fpscrStride;
|
||||||
Bitfield<39, 37> fpscrLen;
|
Bitfield<39, 37> fpscrLen;
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
#include "arch/arm/miscregs.hh"
|
#include "arch/arm/miscregs.hh"
|
||||||
#include "arch/arm/types.hh"
|
#include "arch/arm/types.hh"
|
||||||
#include "base/hashmap.hh"
|
#include "base/hashmap.hh"
|
||||||
|
#include "base/trace.hh"
|
||||||
#include "base/types.hh"
|
#include "base/types.hh"
|
||||||
#include "cpu/thread_context.hh"
|
#include "cpu/thread_context.hh"
|
||||||
|
|
||||||
|
|
|
@ -357,10 +357,10 @@ BaseSimpleCPU::checkForInterrupts()
|
||||||
Fault interrupt = interrupts->getInterrupt(tc);
|
Fault interrupt = interrupts->getInterrupt(tc);
|
||||||
|
|
||||||
if (interrupt != NoFault) {
|
if (interrupt != NoFault) {
|
||||||
predecoder.reset();
|
|
||||||
fetchOffset = 0;
|
fetchOffset = 0;
|
||||||
interrupts->updateIntrInfo(tc);
|
interrupts->updateIntrInfo(tc);
|
||||||
interrupt->invoke(tc);
|
interrupt->invoke(tc);
|
||||||
|
predecoder.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -508,8 +508,8 @@ BaseSimpleCPU::advancePC(Fault fault)
|
||||||
fetchOffset = 0;
|
fetchOffset = 0;
|
||||||
if (fault != NoFault) {
|
if (fault != NoFault) {
|
||||||
curMacroStaticInst = StaticInst::nullStaticInstPtr;
|
curMacroStaticInst = StaticInst::nullStaticInstPtr;
|
||||||
predecoder.reset();
|
|
||||||
fault->invoke(tc);
|
fault->invoke(tc);
|
||||||
|
predecoder.reset();
|
||||||
} else {
|
} else {
|
||||||
//If we're at the last micro op for this instruction
|
//If we're at the last micro op for this instruction
|
||||||
if (curStaticInst && curStaticInst->isLastMicroop()) {
|
if (curStaticInst && curStaticInst->isLastMicroop()) {
|
||||||
|
|
Loading…
Reference in a new issue