views:

725

answers:

7

On an embedded system we have a setup that allows us to read arbitrary data over a command-line interface for diagnostic purposes. For most data, this works fine, we use memcpy() to copy data at the requested address and send it back across a serial connection.

However, for 16-bit hardware registers, memcpy() causes some problems. If I try to access a 16-bit hardware register using two 8-bit accesses, the high-order byte doesn't read correctly.

Has anyone encountered this issue? I'm a 'high-level' (C#/Java/Python/Ruby) guy that's moving closer to the hardware and this is alien territory.

What's the best way to deal with this? I see some info, specifically, a somewhat confusing [to me] post here. The author of this post has exactly the same issue I do but I hate to implement a solution without fully understanding what I'm doing.

Any light you can shed on this issue is much appreciated. Thanks!

+3  A: 

If you require access to hardware registers of a specific size, then you have two choices:

  • Understand how your C compiler generates code so you can use the appropriate integer type to access the memory, or
  • Embed some assembly to do the access with the correct byte or word size.

Reading hardware registers can have side affects, depending on the register and its function, of course, so it's important to access hardware registers with the proper sized access so you can read the entire register in one go.

Eddie
+4  A: 

Each register in this hardware is exposed as a two-byte array, the first element is aligned at a two-byte boundary (its address is even). memcpy() runs a cycle and copies one byte at each iteration, so it copies from these registers this way (all loops unrolled, char is one byte):

*((char*)target) = *((char*)register);// evenly aligned - address is always even
*((char*)target + 1) = *((char*)register + 1);//oddly aligned - address is always odd

However the second line works incorrectly for some hardware specific reasons. If you copy two bytes at a time instead of one at a time, it is instead done this way (short int is two bytes):

*((short int*)target) = *((short*)register;// evenly aligned

Here you copy two bytes in one operation and the first byte is evenly aligned. Since there's no separate copying from an oddly aligned address, it works.

The modified memcpy checks whether the addresses are venely aligned and copies in tow bytes chunks if they are.

sharptooth
+1  A: 

What do you need to know? You've already found a separate post explaining it. Apparently the CPU documentation requires that 16-bit hardware registers are accessed with 16-bit reads and writes, but your implementation of memcpy uses 8-bit reads/writes. So they don't work together.

The solution is simply not to use memcpy to access this register. Instead, write your own routine which copies 16-bit values.

jalf
I'm slow... :-) More context really helps.
Andrew Flanagan
+2  A: 

I think all the detail is contained in that thread you posted so I'll try and break it down a little;

Specifically;

If you access a 16-bit hardware register using two 8-bit
accesses, the high-order byte doesn't read correctly (it
always read as 0xFF for me). This is fair enough since
TI's docs state that 16-bit hardware registers must be
read and written using 16-bit-wide instructions, and
normally would be, unless you're using memcpy() to
read them.

So the problem here is that the hardware registers only report the correct value if their values are read in a single 16-bit read. This would be equivalent to doing;

uint16 value = *(regAddress);

This reads from the address into the value register using a single 16-byte read. On the other hand you have memcpy which is copying data a single-byte at a time. Something like;

while (n--)
{
  *(uint8*)pDest++ = *(uint8*)pSource++;
}

So this causes the registers to be read 8-bits (1 byte) at a time, resulting in the values being invalid.

The solution posted in that thread is to use a version of memcpy that will copy the data using 16-bit reads whereever the source and destination are a6-bit aligned.

Andrew Grant
+4  A: 

In addition to what Eddie said, you typically need to use a volatile pointer to read a hardware register (assuming a memory mapped register, which is not the case for all systems, but it sounds like is true for yours). Something like:

// using types from stdint.h to ensure particular size values
// most systems that access hardware registers will have typedefs
// for something similar (for 16-bit values might be uint16_t, INT16U,
// or something)

uint16_t volatile* pReg = (int16_t volatile*) 0x1234abcd;  // whatever the reg address is

uint16_t val = *pReg;  // read the 16-bit wide register

Here's a series of articles by Dan Saks that should give you pretty much everything you need to know to be able to effectively use memory mapped registers in C/C++:

Michael Burr
I saw something about this before... As mentioned, this stuff is all new -- thanks for the tip on this keyword.
Andrew Flanagan
Thanks for the links... Great stuff.
Andrew Flanagan
+2  A: 

Usually it's sufficient to use an integer type that is the same size as your register. On most compilers, a short is 16 bits.

void wordcpy(short *dest, const short *src, size_t bytecount)
{
    int i;
    for (i = 0;  i < bytecount/2;  ++i)
        *dest++ = *src++;
}
Mark Ransom
+1  A: 

Not sure exactly what the question is - I think that post has the right solution. As you stated, the issue is that the standard memcpy() routine reads a byte at a time, which does not work correctly for memory mapped hardware registers. That is a limitation of the processor - there's simply no way to get a valid value reading a byte at at time.

The suggested solution is to write your own memcpy() which only works on word-aligned addresses, and reads 16-bit words at a time. This is fairly straightforward - the link gives both a c and an assembly version. The only gotcha is to make sure you always do the 16 bit copies from validly aligned address. You can do that in 2 ways: either use linker commands or pragmas to make sure things are aligned, or add a special case for the extra byte at the front of an unaligned buffer.

AShelly