Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

powerpc: Fix transactional FP/VMX/VSX unavailable handlers

Currently, if a process starts a transaction and then takes an
exception because the FPU, VMX or VSX unit is unavailable to it,
we end up corrupting any FP/VMX/VSX state that was valid before
the interrupt. For example, if the process starts a transaction
with the FPU available to it but VMX unavailable, and then does
a VMX instruction inside the transaction, the FP state gets
corrupted.

Loading up the desired state generally involves doing a reclaim
and a recheckpoint. To avoid corrupting already-valid state, we have
to be careful not to reload that state from the thread_struct
between the reclaim and the recheckpoint (since the thread_struct
values are stale by now), and we have to reload that state from
the transact_fp/vr arrays after the recheckpoint to get back the
current transactional values saved there by the reclaim.

Signed-off-by: Paul Mackerras <paulus@samba.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>

authored by

Paul Mackerras and committed by
Benjamin Herrenschmidt
3ac8ff1c d31626f7

+37 -10
+37 -10
arch/powerpc/kernel/traps.c
··· 1416 1416 /* This loads and recheckpoints the FP registers from 1417 1417 * thread.fpr[]. They will remain in registers after the 1418 1418 * checkpoint so we don't need to reload them after. 1419 + * If VMX is in use, the VRs now hold checkpointed values, 1420 + * so we don't want to load the VRs from the thread_struct. 1419 1421 */ 1420 - tm_recheckpoint(&current->thread, regs->msr); 1422 + tm_recheckpoint(&current->thread, MSR_FP); 1423 + 1424 + /* If VMX is in use, get the transactional values back */ 1425 + if (regs->msr & MSR_VEC) { 1426 + do_load_up_transact_altivec(&current->thread); 1427 + /* At this point all the VSX state is loaded, so enable it */ 1428 + regs->msr |= MSR_VSX; 1429 + } 1421 1430 } 1422 1431 1423 - #ifdef CONFIG_ALTIVEC 1424 1432 void altivec_unavailable_tm(struct pt_regs *regs) 1425 1433 { 1426 1434 /* See the comments in fp_unavailable_tm(). This function operates ··· 1440 1432 regs->nip, regs->msr); 1441 1433 tm_reclaim_current(TM_CAUSE_FAC_UNAV); 1442 1434 regs->msr |= MSR_VEC; 1443 - tm_recheckpoint(&current->thread, regs->msr); 1435 + tm_recheckpoint(&current->thread, MSR_VEC); 1444 1436 current->thread.used_vr = 1; 1445 - } 1446 - #endif 1447 1437 1448 - #ifdef CONFIG_VSX 1438 + if (regs->msr & MSR_FP) { 1439 + do_load_up_transact_fpu(&current->thread); 1440 + regs->msr |= MSR_VSX; 1441 + } 1442 + } 1443 + 1449 1444 void vsx_unavailable_tm(struct pt_regs *regs) 1450 1445 { 1446 + unsigned long orig_msr = regs->msr; 1447 + 1451 1448 /* See the comments in fp_unavailable_tm(). This works similarly, 1452 1449 * though we're loading both FP and VEC registers in here. 1453 1450 * ··· 1464 1451 "MSR=%lx\n", 1465 1452 regs->nip, regs->msr); 1466 1453 1454 + current->thread.used_vsr = 1; 1455 + 1456 + /* If FP and VMX are already loaded, we have all the state we need */ 1457 + if ((orig_msr & (MSR_FP | MSR_VEC)) == (MSR_FP | MSR_VEC)) { 1458 + regs->msr |= MSR_VSX; 1459 + return; 1460 + } 1461 + 1467 1462 /* This reclaims FP and/or VR regs if they're already enabled */ 1468 1463 tm_reclaim_current(TM_CAUSE_FAC_UNAV); 1469 1464 1470 1465 regs->msr |= MSR_VEC | MSR_FP | current->thread.fpexc_mode | 1471 1466 MSR_VSX; 1472 - /* This loads & recheckpoints FP and VRs. */ 1473 - tm_recheckpoint(&current->thread, regs->msr); 1474 - current->thread.used_vsr = 1; 1467 + 1468 + /* This loads & recheckpoints FP and VRs; but we have 1469 + * to be sure not to overwrite previously-valid state. 1470 + */ 1471 + tm_recheckpoint(&current->thread, regs->msr & ~orig_msr); 1472 + 1473 + if (orig_msr & MSR_FP) 1474 + do_load_up_transact_fpu(&current->thread); 1475 + if (orig_msr & MSR_VEC) 1476 + do_load_up_transact_altivec(&current->thread); 1475 1477 } 1476 - #endif 1477 1478 #endif /* CONFIG_PPC_TRANSACTIONAL_MEM */ 1478 1479 1479 1480 void performance_monitor_exception(struct pt_regs *regs)