Re: [patch] cache flush bug in mm/filemap.c (all kernels >= 2.5.30(at least))

From: Lothar Wassmann (LW@KARO-electronics.de)
Date: Fri May 23 2003 - 07:46:19 EST


Russell King writes:
> On Fri, May 23, 2003 at 12:04:13PM +0200, Lothar Wassmann wrote:
> > Is 2.5.68 current enough? The problem was even better reproducible
> > with this kernel than with the old one. So I made all my tests with
> > 2.5.68.
>
> Can you attach the test program which reproduces your problem, and
> include a description of what you think is going wrong?
>
I'm using a custom PXA250/255 board (same problem on both processors)
with either kernel 2.5.30-rmk1-pxa1 or 2.5.68-rmk1-pxa1. Both show the
same malfunction when reading a file non-sequentially from an IDE CF
card.

The problem is, that when reading an 'mmap'ed file non-sequentially
(e.g. back to front) data errors occur. It seems that the program is
reading stale data from the cache until the cache line has been
evicted and the correct data appears. The problem doesn't occur when
the data cache is set to write through mode.

Lothar Wassmann
Here is the source:


/*
 * creates a testfile, 'mmap's it, and checks its content reading
 * page back to front. If a data error is found, the same page is read
 * over and over again, until data is eventually correct after some time.
 *
 * This points out a cache problem in the ARM linux kernel
 * Using the cache in Write-Through mode (kernel command line option: cachepolicy=writethrough)
 * or CONFIG_XSCALE_CACHE_ERRATA=y in older kernels prevents this problem
 *
 * (C) Lothar Wassmann, <LW@KARO-electronics.de>
 *
 */
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define PAGE_SIZE 4096
#define PAGE_SIZE_INT ((PAGE_SIZE)/sizeof(unsigned long))
#define PAGE_MASK ((PAGE_SIZE)-1)

size_t file_size = 256 * PAGE_SIZE;

unsigned long *buf=NULL;

const char* fn="testfile";

void usage(const char* name)
{
        printf("%s <mount point> [filename]\n", name);
        printf("\trequires <mount point> to be defined in /etc/fstab\n");
        printf("\t<mount point> will be unmounted and remounted during the test\n");
}

int create_file(const char* name, size_t size)
{
        int ret=0;
        int i;
        int fd;

        fd = open(name, O_CREAT|O_RDWR|O_SYNC|O_TRUNC, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
        if (fd < 0) {
                fprintf(stderr, "Failed to open '%s' for writing, errno=%d\n", name, errno);
                return errno;
        }

        for (i = size / sizeof(*buf); i > 0; i--) {
                buf[i-1] = i;
        }
        write(fd, buf, size);
        memset(buf, 0x55, size);

        close(fd);
        return ret;
}

int do_check(int fd, void *mapptr, size_t size)
{
        const int num_pages=size/PAGE_SIZE;
        volatile unsigned char *ptr=mapptr;
        int errors = 0;
        int soft = 0;
        int page;

        printf("Checking data from %08lx to %08lx\n", (unsigned long)(ptr + size),
               (unsigned long)ptr);

        for (page = num_pages - 1; page >= 0; page--) {
                volatile unsigned long *pp=(volatile unsigned long *)&ptr[page*PAGE_SIZE];
                int offs;
                int page_errs=0;
                int err_offs=-1;

                for (offs = 0; offs < PAGE_SIZE; offs += sizeof(unsigned long)) {
                        volatile unsigned long *lp=&pp[offs/sizeof(unsigned long)];
                        unsigned long data=*lp;
                        unsigned long ref=(((page*PAGE_SIZE)+offs)/sizeof(data)) + 1;

                        if (data != ref) {
                                const int max_tries=100000;
                                int retries=max_tries;
                                unsigned long new_data=*lp;

                                errors++;
                                page_errs++;
                                while ((new_data != ref) && (--retries > 0)) {
                                        if (data != new_data) {
                                                fprintf(stderr, "Data @ page %03x:%03x (%08lx) changed to %08lx(%08lx)\n",
                                                        page, offs, (unsigned long)lp, new_data, ref);
                                        }
                                        data = new_data;
                                        new_data = *lp;
                                }
                                if (new_data == ref) {
                                        fprintf(stderr, "Data @ page %03x:%03x (%08lx) OK after %d retries: %08lx\n",
                                                page, offs, (unsigned long)lp, max_tries - retries, new_data);
                                        soft++;
                                } else {
                                        if (err_offs != offs) {
                                                fprintf(stderr, "Data error @ page %03x:%03x (%08lx): %08lx -> %08lx\n",
                                                        page, offs, (unsigned long)lp, ref, data);
                                                err_offs = offs;
                                        }
                                        // retry the same page again, until data is correct
                                        offs = 0;
                                }
                        }
                }
                if (page_errs) {
                        page = num_pages;
                }
        }

        fprintf(stderr, "Errors reverse check: %d; soft: %d; total bytes %d in %d pages\n",
                errors, soft, size, num_pages);

        return errors;
}

int check_file(const char* name, size_t size)
{
        int ret=0;
        int fd;
        void *ptr=NULL;
        int errors=0;
        int last_errors=0;

        fd = open(name, O_RDONLY|O_SYNC);
        if (fd < 0) {
                fprintf(stderr, "Failed to open '%s' for reading\n", name);
                return errno;
        }

        ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
        if (ptr == MAP_FAILED) {
                close(fd);
                return -ENOMEM;
        }

        printf("Checking file '%s'\n", name);
        do {
                last_errors = errors;
                errors = do_check(fd, ptr, size);
                if (errors != 0) {
                        ret = errors;
                }
        } while (errors > 0 && errors != last_errors);

        if (munmap(ptr, size) != 0) {
                fprintf(stderr, "Failed to unmap %08lx\n", (unsigned long)ptr);
                if (ret == 0) {
                        ret = -ENOMEM;
                }
        }
        close(fd);
        if (buf != NULL) {
                memset(buf, 0x55, size);
        }

        if (ret == 0) {
                printf("check successful\n");
        } else {
                printf("check failed\n");
        }

        return ret;
}

int main(int argc, char *argv[])
{
        int rc=0;
        char fname[100];
        char mount[44];
        char umount[44];

        if (argc < 2) {
                // first argument is required
                usage(argv[0]);
                return 1;
        }
        if (argc > 2) {
                // take optional second argument as filename
                fn = argv[2];
        }

        sprintf(fname, "%s/%s", argv[1], fn);
        sprintf(mount, "mount %s", argv[1]);
        sprintf(umount, "umount %s", argv[1]);

        file_size &= ~PAGE_MASK; // round size to page boundary
        buf = malloc(file_size);

        if (buf == NULL) {
                fprintf(stderr, "Failed to allocate buffer\n");
                rc = -ENOMEM;
        }
        
        printf("Mounting '%s'\n", argv[1]);
        system(mount);

        while (rc == 0) {
                printf("Opening '%s'\n", fname);
                rc = create_file(fname, file_size);
                if (rc != 0) {
                        fprintf(stderr, "Failed to create file '%s', rc=%d\n", fname, rc);
                        break;
                }

                printf("Unmounting '%s'\n", argv[1]);
                system(umount);

                printf("Remounting '%s'\n", argv[1]);
                system(mount);

                rc = check_file(fname, file_size);
        }

        if (buf != NULL) {
                free(buf);
        }

        return rc;
}


and the Makefile, that I used to compile it (with gcc 2.95.3):

ARCH = arm
CC = arm-linux-gcc

CFLAGS := -Wall -Wstrict-prototypes -Wno-trigraphs -O2 \
          -fomit-frame-pointer -fno-strict-aliasing -fno-common

INSTALL_DIR=/tftpboot/rootfs/usr/local/arm/bin
SOURCES = disktest.c
OBJECTS= $(SOURCES:%.c=%.o)
BINARIES= $(SOURCES:%.c=%)
TARGETS = $(BINARIES:%=$(INSTALL_DIR)/%)

all: $(OBJECTS) $(BINARIES)

clean:
        rm -f $(OBJECTS)

install: all $(TARGETS)

$(BINARIES:%=$(INSTALL_DIR)/%): $(INSTALL_DIR)/%: %
        sudo cp -p $< $@

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Fri May 23 2003 - 22:00:55 EST