diff options
author | Kumar Gala <galak@kernel.crashing.org> | 2007-08-24 17:42:53 -0400 |
---|---|---|
committer | Kumar Gala <galak@kernel.crashing.org> | 2007-09-14 09:51:48 -0400 |
commit | 26caeb2ee1924d564e8d8190aa783a569532f81a (patch) | |
tree | 754c3c1e94f36b1d9822360dfdc03b2d48fd07a0 /arch/powerpc/kernel | |
parent | f9234736112bf193e5ab451abbfbdf279cc53137 (diff) |
[POWERPC] Handle alignment faults on SPE load/store instructions
This adds code to handle alignment traps generated by the following
SPE (signal processing engine) load/store instructions, by emulating
the instruction in the kernel (as is done for other instructions that
generate alignment traps):
evldd[x] Vector Load Double Word into Double Word [Indexed]
evldw[x] Vector Load Double into Two Words [Indexed]
evldh[x] Vector Load Double into Four Half Words [Indexed]
evlhhesplat[x] Vector Load Half Word into Half Words Even and Splat [Indexed]
evlhhousplat[x] Vector Load Half Word into Half Word Odd Unsigned and Splat [Indexed]
evlhhossplat[x] Vector Load Half Word into Half Word Odd Signed and Splat [Indexed]
evlwhe[x] Vector Load Word into Two Half Words Even [Indexed]
evlwhou[x] Vector Load Word into Two Half Words Odd Unsigned (zero-extended) [Indexed]
evlwhos[x] Vector Load Word into Two Half Words Odd Signed (with sign extension) [Indexed]
evlwwsplat[x] Vector Load Word into Word and Splat [Indexed]
evlwhsplat[x] Vector Load Word into Two Half Words and Splat [Indexed]
evstdd[x] Vector Store Double of Double [Indexed]
evstdw[x] Vector Store Double of Two Words [Indexed]
evstdh[x] Vector Store Double of Four Half Words [Indexed]
evstwhe[x] Vector Store Word of Two Half Words from Even [Indexed]
evstwho[x] Vector Store Word of Two Half Words from Odd [Indexed]
evstwwe[x] Vector Store Word of Word from Even [Indexed]
evstwwo[x] Vector Store Word of Word from Odd [Indexed]
Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/kernel')
-rw-r--r-- | arch/powerpc/kernel/align.c | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/arch/powerpc/kernel/align.c b/arch/powerpc/kernel/align.c index 4c47f9cc0d9a..e06f75daeba3 100644 --- a/arch/powerpc/kernel/align.c +++ b/arch/powerpc/kernel/align.c | |||
@@ -46,6 +46,8 @@ struct aligninfo { | |||
46 | #define S 0x40 /* single-precision fp or... */ | 46 | #define S 0x40 /* single-precision fp or... */ |
47 | #define SX 0x40 /* ... byte count in XER */ | 47 | #define SX 0x40 /* ... byte count in XER */ |
48 | #define HARD 0x80 /* string, stwcx. */ | 48 | #define HARD 0x80 /* string, stwcx. */ |
49 | #define E4 0x40 /* SPE endianness is word */ | ||
50 | #define E8 0x80 /* SPE endianness is double word */ | ||
49 | 51 | ||
50 | /* DSISR bits reported for a DCBZ instruction: */ | 52 | /* DSISR bits reported for a DCBZ instruction: */ |
51 | #define DCBZ 0x5f /* 8xx/82xx dcbz faults when cache not enabled */ | 53 | #define DCBZ 0x5f /* 8xx/82xx dcbz faults when cache not enabled */ |
@@ -392,6 +394,248 @@ static int emulate_fp_pair(struct pt_regs *regs, unsigned char __user *addr, | |||
392 | return 1; /* exception handled and fixed up */ | 394 | return 1; /* exception handled and fixed up */ |
393 | } | 395 | } |
394 | 396 | ||
397 | #ifdef CONFIG_SPE | ||
398 | |||
399 | static struct aligninfo spe_aligninfo[32] = { | ||
400 | { 8, LD+E8 }, /* 0 00 00: evldd[x] */ | ||
401 | { 8, LD+E4 }, /* 0 00 01: evldw[x] */ | ||
402 | { 8, LD }, /* 0 00 10: evldh[x] */ | ||
403 | INVALID, /* 0 00 11 */ | ||
404 | { 2, LD }, /* 0 01 00: evlhhesplat[x] */ | ||
405 | INVALID, /* 0 01 01 */ | ||
406 | { 2, LD }, /* 0 01 10: evlhhousplat[x] */ | ||
407 | { 2, LD+SE }, /* 0 01 11: evlhhossplat[x] */ | ||
408 | { 4, LD }, /* 0 10 00: evlwhe[x] */ | ||
409 | INVALID, /* 0 10 01 */ | ||
410 | { 4, LD }, /* 0 10 10: evlwhou[x] */ | ||
411 | { 4, LD+SE }, /* 0 10 11: evlwhos[x] */ | ||
412 | { 4, LD+E4 }, /* 0 11 00: evlwwsplat[x] */ | ||
413 | INVALID, /* 0 11 01 */ | ||
414 | { 4, LD }, /* 0 11 10: evlwhsplat[x] */ | ||
415 | INVALID, /* 0 11 11 */ | ||
416 | |||
417 | { 8, ST+E8 }, /* 1 00 00: evstdd[x] */ | ||
418 | { 8, ST+E4 }, /* 1 00 01: evstdw[x] */ | ||
419 | { 8, ST }, /* 1 00 10: evstdh[x] */ | ||
420 | INVALID, /* 1 00 11 */ | ||
421 | INVALID, /* 1 01 00 */ | ||
422 | INVALID, /* 1 01 01 */ | ||
423 | INVALID, /* 1 01 10 */ | ||
424 | INVALID, /* 1 01 11 */ | ||
425 | { 4, ST }, /* 1 10 00: evstwhe[x] */ | ||
426 | INVALID, /* 1 10 01 */ | ||
427 | { 4, ST }, /* 1 10 10: evstwho[x] */ | ||
428 | INVALID, /* 1 10 11 */ | ||
429 | { 4, ST+E4 }, /* 1 11 00: evstwwe[x] */ | ||
430 | INVALID, /* 1 11 01 */ | ||
431 | { 4, ST+E4 }, /* 1 11 10: evstwwo[x] */ | ||
432 | INVALID, /* 1 11 11 */ | ||
433 | }; | ||
434 | |||
435 | #define EVLDD 0x00 | ||
436 | #define EVLDW 0x01 | ||
437 | #define EVLDH 0x02 | ||
438 | #define EVLHHESPLAT 0x04 | ||
439 | #define EVLHHOUSPLAT 0x06 | ||
440 | #define EVLHHOSSPLAT 0x07 | ||
441 | #define EVLWHE 0x08 | ||
442 | #define EVLWHOU 0x0A | ||
443 | #define EVLWHOS 0x0B | ||
444 | #define EVLWWSPLAT 0x0C | ||
445 | #define EVLWHSPLAT 0x0E | ||
446 | #define EVSTDD 0x10 | ||
447 | #define EVSTDW 0x11 | ||
448 | #define EVSTDH 0x12 | ||
449 | #define EVSTWHE 0x18 | ||
450 | #define EVSTWHO 0x1A | ||
451 | #define EVSTWWE 0x1C | ||
452 | #define EVSTWWO 0x1E | ||
453 | |||
454 | /* | ||
455 | * Emulate SPE loads and stores. | ||
456 | * Only Book-E has these instructions, and it does true little-endian, | ||
457 | * so we don't need the address swizzling. | ||
458 | */ | ||
459 | static int emulate_spe(struct pt_regs *regs, unsigned int reg, | ||
460 | unsigned int instr) | ||
461 | { | ||
462 | int t, ret; | ||
463 | union { | ||
464 | u64 ll; | ||
465 | u32 w[2]; | ||
466 | u16 h[4]; | ||
467 | u8 v[8]; | ||
468 | } data, temp; | ||
469 | unsigned char __user *p, *addr; | ||
470 | unsigned long *evr = ¤t->thread.evr[reg]; | ||
471 | unsigned int nb, flags; | ||
472 | |||
473 | instr = (instr >> 1) & 0x1f; | ||
474 | |||
475 | /* DAR has the operand effective address */ | ||
476 | addr = (unsigned char __user *)regs->dar; | ||
477 | |||
478 | nb = spe_aligninfo[instr].len; | ||
479 | flags = spe_aligninfo[instr].flags; | ||
480 | |||
481 | /* Verify the address of the operand */ | ||
482 | if (unlikely(user_mode(regs) && | ||
483 | !access_ok((flags & ST ? VERIFY_WRITE : VERIFY_READ), | ||
484 | addr, nb))) | ||
485 | return -EFAULT; | ||
486 | |||
487 | /* userland only */ | ||
488 | if (unlikely(!user_mode(regs))) | ||
489 | return 0; | ||
490 | |||
491 | flush_spe_to_thread(current); | ||
492 | |||
493 | /* If we are loading, get the data from user space, else | ||
494 | * get it from register values | ||
495 | */ | ||
496 | if (flags & ST) { | ||
497 | data.ll = 0; | ||
498 | switch (instr) { | ||
499 | case EVSTDD: | ||
500 | case EVSTDW: | ||
501 | case EVSTDH: | ||
502 | data.w[0] = *evr; | ||
503 | data.w[1] = regs->gpr[reg]; | ||
504 | break; | ||
505 | case EVSTWHE: | ||
506 | data.h[2] = *evr >> 16; | ||
507 | data.h[3] = regs->gpr[reg] >> 16; | ||
508 | break; | ||
509 | case EVSTWHO: | ||
510 | data.h[2] = *evr & 0xffff; | ||
511 | data.h[3] = regs->gpr[reg] & 0xffff; | ||
512 | break; | ||
513 | case EVSTWWE: | ||
514 | data.w[1] = *evr; | ||
515 | break; | ||
516 | case EVSTWWO: | ||
517 | data.w[1] = regs->gpr[reg]; | ||
518 | break; | ||
519 | default: | ||
520 | return -EINVAL; | ||
521 | } | ||
522 | } else { | ||
523 | temp.ll = data.ll = 0; | ||
524 | ret = 0; | ||
525 | p = addr; | ||
526 | |||
527 | switch (nb) { | ||
528 | case 8: | ||
529 | ret |= __get_user_inatomic(temp.v[0], p++); | ||
530 | ret |= __get_user_inatomic(temp.v[1], p++); | ||
531 | ret |= __get_user_inatomic(temp.v[2], p++); | ||
532 | ret |= __get_user_inatomic(temp.v[3], p++); | ||
533 | case 4: | ||
534 | ret |= __get_user_inatomic(temp.v[4], p++); | ||
535 | ret |= __get_user_inatomic(temp.v[5], p++); | ||
536 | case 2: | ||
537 | ret |= __get_user_inatomic(temp.v[6], p++); | ||
538 | ret |= __get_user_inatomic(temp.v[7], p++); | ||
539 | if (unlikely(ret)) | ||
540 | return -EFAULT; | ||
541 | } | ||
542 | |||
543 | switch (instr) { | ||
544 | case EVLDD: | ||
545 | case EVLDW: | ||
546 | case EVLDH: | ||
547 | data.ll = temp.ll; | ||
548 | break; | ||
549 | case EVLHHESPLAT: | ||
550 | data.h[0] = temp.h[3]; | ||
551 | data.h[2] = temp.h[3]; | ||
552 | break; | ||
553 | case EVLHHOUSPLAT: | ||
554 | case EVLHHOSSPLAT: | ||
555 | data.h[1] = temp.h[3]; | ||
556 | data.h[3] = temp.h[3]; | ||
557 | break; | ||
558 | case EVLWHE: | ||
559 | data.h[0] = temp.h[2]; | ||
560 | data.h[2] = temp.h[3]; | ||
561 | break; | ||
562 | case EVLWHOU: | ||
563 | case EVLWHOS: | ||
564 | data.h[1] = temp.h[2]; | ||
565 | data.h[3] = temp.h[3]; | ||
566 | break; | ||
567 | case EVLWWSPLAT: | ||
568 | data.w[0] = temp.w[1]; | ||
569 | data.w[1] = temp.w[1]; | ||
570 | break; | ||
571 | case EVLWHSPLAT: | ||
572 | data.h[0] = temp.h[2]; | ||
573 | data.h[1] = temp.h[2]; | ||
574 | data.h[2] = temp.h[3]; | ||
575 | data.h[3] = temp.h[3]; | ||
576 | break; | ||
577 | default: | ||
578 | return -EINVAL; | ||
579 | } | ||
580 | } | ||
581 | |||
582 | if (flags & SW) { | ||
583 | switch (flags & 0xf0) { | ||
584 | case E8: | ||
585 | SWAP(data.v[0], data.v[7]); | ||
586 | SWAP(data.v[1], data.v[6]); | ||
587 | SWAP(data.v[2], data.v[5]); | ||
588 | SWAP(data.v[3], data.v[4]); | ||
589 | break; | ||
590 | case E4: | ||
591 | |||
592 | SWAP(data.v[0], data.v[3]); | ||
593 | SWAP(data.v[1], data.v[2]); | ||
594 | SWAP(data.v[4], data.v[7]); | ||
595 | SWAP(data.v[5], data.v[6]); | ||
596 | break; | ||
597 | /* Its half word endian */ | ||
598 | default: | ||
599 | SWAP(data.v[0], data.v[1]); | ||
600 | SWAP(data.v[2], data.v[3]); | ||
601 | SWAP(data.v[4], data.v[5]); | ||
602 | SWAP(data.v[6], data.v[7]); | ||
603 | break; | ||
604 | } | ||
605 | } | ||
606 | |||
607 | if (flags & SE) { | ||
608 | data.w[0] = (s16)data.h[1]; | ||
609 | data.w[1] = (s16)data.h[3]; | ||
610 | } | ||
611 | |||
612 | /* Store result to memory or update registers */ | ||
613 | if (flags & ST) { | ||
614 | ret = 0; | ||
615 | p = addr; | ||
616 | switch (nb) { | ||
617 | case 8: | ||
618 | ret |= __put_user_inatomic(data.v[0], p++); | ||
619 | ret |= __put_user_inatomic(data.v[1], p++); | ||
620 | ret |= __put_user_inatomic(data.v[2], p++); | ||
621 | ret |= __put_user_inatomic(data.v[3], p++); | ||
622 | case 4: | ||
623 | ret |= __put_user_inatomic(data.v[4], p++); | ||
624 | ret |= __put_user_inatomic(data.v[5], p++); | ||
625 | case 2: | ||
626 | ret |= __put_user_inatomic(data.v[6], p++); | ||
627 | ret |= __put_user_inatomic(data.v[7], p++); | ||
628 | } | ||
629 | if (unlikely(ret)) | ||
630 | return -EFAULT; | ||
631 | } else { | ||
632 | *evr = data.w[0]; | ||
633 | regs->gpr[reg] = data.w[1]; | ||
634 | } | ||
635 | |||
636 | return 1; | ||
637 | } | ||
638 | #endif /* CONFIG_SPE */ | ||
395 | 639 | ||
396 | /* | 640 | /* |
397 | * Called on alignment exception. Attempts to fixup | 641 | * Called on alignment exception. Attempts to fixup |
@@ -450,6 +694,12 @@ int fix_alignment(struct pt_regs *regs) | |||
450 | /* extract the operation and registers from the dsisr */ | 694 | /* extract the operation and registers from the dsisr */ |
451 | reg = (dsisr >> 5) & 0x1f; /* source/dest register */ | 695 | reg = (dsisr >> 5) & 0x1f; /* source/dest register */ |
452 | areg = dsisr & 0x1f; /* register to update */ | 696 | areg = dsisr & 0x1f; /* register to update */ |
697 | |||
698 | #ifdef CONFIG_SPE | ||
699 | if ((instr >> 26) == 0x4) | ||
700 | return emulate_spe(regs, reg, instr); | ||
701 | #endif | ||
702 | |||
453 | instr = (dsisr >> 10) & 0x7f; | 703 | instr = (dsisr >> 10) & 0x7f; |
454 | instr |= (dsisr >> 13) & 0x60; | 704 | instr |= (dsisr >> 13) & 0x60; |
455 | 705 | ||