Re: [PATCH 05/17] Blackfin: convert to generic checksum code

From: Arnd Bergmann
Date: Fri Jun 19 2009 - 05:07:11 EST


On Friday 19 June 2009, Mike Frysinger wrote:
> On Mon, Jun 15, 2009 at 07:04, Arnd Bergmann wrote:
> > On Sunday 14 June 2009, Mike Frysinger wrote:
> >> The Blackfin port only implemented an optimized version of the
> >> csum_tcpudp_nofold function, so convert everything else to the new
> >> generic code.
> >>
> >> Signed-off-by: Mike Frysinger <vapier@xxxxxxxxxx>
> >
> > Have you tested this one well? I was as careful as possible with the
> > version I added, but it was basically only tested on microblaze, which
> > has a different endianess from blackfin. Some areas of the code may be
> > sensitive to this.
>
> i did some tests and it looks like do_csum() is broken :(

Thanks for testing. Indeed it turns out to be an endian-problem with
the handling of the last byte:

> here's the input:
> do_csum({ 0xff 0xfb 0x01 }, 3)
>
> and here's the output:
> Blackfin: 0xfc00
> generic: 0xfcff
>
> if i run the two funcs on my x86, i see similar behavior. the
> attached csum-test.c contains the csum code from lib/checksum.c and
> arch/blackfin/lib/checksum.c and shows the problem.

x86 and blackfin are both little-endian, so your variant is correct
there. They add the 0x01 to the low byte of the 16-bit word, while
on big-endian machines, you have to add it to the high byte.

> -mike
> csum-test.c
> #include <stdio.h>
>
> static inline unsigned short gen_from32to16(unsigned long x)
> {
> /* add up 16-bit and 16-bit for 16+c bit */
> x = (x & 0xffff) + (x >> 16);
> /* add up carry.. */
> x = (x & 0xffff) + (x >> 16);
> return x;
> }
>
> static unsigned int gen_do_csum(const unsigned char *buff, int len)
> {
> ...
> if (len & 1)
> result += (*buff << 8);

On little-endian, this needs to be

if (len & 1)
result += *buff;

> static unsigned short bf_do_csum(const unsigned char *buff, int len)
> {
> ...
> if (len > 0)
> sum += *buff;

Same problem, on big-endian this would need to be
if (len > 0)
sum += (*buff << 8);

The bug potentially also exists in arch/sh/lib64/c-checksum.c, which only
works on little-endian machines, while the sh architecture code appears
dual-endian. Paul, is your lib64/c-checksum.c implementation potentially
run on big-endian machines, or is sh64 guaranteed to be little-endian?

I've committed the patch below now.

Arnd <><

---