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

Lothar Wassmann (LW@KARO-electronics.de)
Fri, 23 May 2003 14:46:19 +0200


This is a MIME-formatted message. If you see this text it means that your
E-mail software does not support MIME-formatted messages.

--=_courier-12193-1053694224-0001-2
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Description: message body text

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:

--=_courier-12193-1053694224-0001-2
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="disktest.c"

/*
* 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;
}

--=_courier-12193-1053694224-0001-2
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Description: message body text

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

--=_courier-12193-1053694224-0001-2
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="Makefile"

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 $< $@

--=_courier-12193-1053694224-0001-2--