• Matt Redfearn's avatar
    MIPS: memset.S: Fix byte_fixup for MIPSr6 · b1c03f1e
    Matt Redfearn authored
    The __clear_user function is defined to return the number of bytes that
    could not be cleared. From the underlying memset / bzero implementation
    this means setting register a2 to that number on return. Currently if a
    page fault is triggered within the MIPSr6 version of setting of initial
    unaligned bytes, the value loaded into a2 on return is meaningless.
    
    During the MIPSr6 version of the initial unaligned bytes block, register
    a2 contains the number of bytes to be set beyond the initial unaligned
    bytes. The t0 register is initally set to the number of unaligned bytes
    - STORSIZE, effectively a negative version of the number of unaligned
    bytes. This is then incremented before each byte is saved.
    
    The label .Lbyte_fixup\@ is jumped to on page fault. Currently the value
    in a2 is incorrectly replaced by 0 - t0 + 1, effectively the number of
    unaligned bytes remaining. This leads to the failures being reported by
    the following test code:
    
    static int __init test_clear_user(void)
    {
    	int j, k;
    
    	pr_info("\n\n\nTesting clear_user\n");
    	for (j = 0; j < 512; j++) {
    		if ((k = clear_user(NULL+3, j)) != j) {
    			pr_err("clear_user (NULL %d) returned %d\n", j, k);
    		}
    	}
    	return 0;
    }
    late_initcall(test_clear_user);
    
    Which reports:
    [    3.965439] Testing clear_user
    [    3.973169] clear_user (NULL 8) returned 6
    [    3.976782] clear_user (NULL 9) returned 6
    [    3.980390] clear_user (NULL 10) returned 6
    [    3.984052] clear_user (NULL 11) returned 6
    [    3.987524] clear_user (NULL 12) returned 6
    
    Fix this by subtracting t0 from a2 (rather than $0), effectivey giving:
    unset_bytes = (#bytes - (#unaligned bytes)) - (-#unaligned bytes remaining + 1) + 1
         a2     =             a2                -              t0                   + 1
    
    This fixes the value returned from __clear user when the number of bytes
    to set is > LONGSIZE and the address is invalid and unaligned.
    
    Unfortunately, this breaks the fixup handling for unaligned bytes after
    the final long, where register a2 still contains the number of bytes
    remaining to be set and the t0 register is to 0 - the number of
    unaligned bytes remaining.
    
    Because t0 is now is now subtracted from a2 rather than 0, the number of
    bytes unset is reported incorrectly:
    
    static int __init test_clear_user(void)
    {
    	char *test;
    	int j, k;
    
    	pr_info("\n\n\nTesting clear_user\n");
    	test = vmalloc(PAGE_SIZE);
    
    	for (j = 256; j < 512; j++) {
    		if ((k = clear_user(test + PAGE_SIZE - 254, j)) != j - 254) {
    			pr_err("clear_user (%px %d) returned %d\n",
    				test + PAGE_SIZE - 254, j, k);
    		}
    	}
    	return 0;
    }
    late_initcall(test_clear_user);
    
    [    3.976775] clear_user (c00000000000df02 256) returned 4
    [    3.981957] clear_user (c00000000000df02 257) returned 6
    [    3.986425] clear_user (c00000000000df02 258) returned 8
    [    3.990850] clear_user (c00000000000df02 259) returned 10
    [    3.995332] clear_user (c00000000000df02 260) returned 12
    [    3.999815] clear_user (c00000000000df02 261) returned 14
    
    Fix this by ensuring that a2 is set to 0 during the set of final
    unaligned bytes.
    Signed-off-by: default avatarMatt Redfearn <matt.redfearn@mips.com>
    Signed-off-by: default avatarPaul Burton <paul.burton@mips.com>
    Fixes: 8c56208a ("MIPS: lib: memset: Add MIPS R6 support")
    Patchwork: https://patchwork.linux-mips.org/patch/19338/
    Cc: James Hogan <jhogan@kernel.org>
    Cc: Ralf Baechle <ralf@linux-mips.org>
    Cc: linux-mips@linux-mips.org
    Cc: linux-kernel@vger.kernel.org
    Cc: stable@vger.kernel.org # v4.0+
    b1c03f1e
memset.S 6.74 KB