Memory mapped hardware registers

Hi,

I'm developping board support code for an embedded m68k system. The actual MCU is a MC68331 that has a CPU32 core (more or less a 68020). This MCU define hardware registers at specific memory locations. My toolchain is composed of GCC 3.4.3 and binutils 2.15.

It possible to define a variable that would be placed at specific location using gcc or linker script hacks ? It would avoid using a pointer to access a register. I don't care if this is non portable and/or gcc/binutils specific.

For example, the CPU32 has the SIMCR 16-bits register placed at 0xFFFA00.

typedef unsigned short word;

/* SIMCR (0xYFFA00): SIM Configuration */ typedef struct _SIMCR { word exoff : 1; word frzsw : 1; word frzbm : 1; word _UNUSED_1 : 1; word slven : 1; word _UNUSED_2 : 1; word shen : 2; word supv : 1; word mm : 1; word _UNUSED_3 : 2; word iarb : 4; } SIMCR;

How can I place a single SIMCR structure at 0xFFFA00 ? I guess some linker magic is involved ? Also, the actual variable must be left uninitialized; you don't want init code to overwrite the register with zeros as this will crash the MCU.

Thanks,

Simon Paradis simon _dot_ paradis _at_ usherbrooke _dot_ ca

Reply to
Simon Paradis
Loading thread data ...

I suppose you are using µCLinux. As there is no memory management unit, you can access the hardware as well in Kernel as in user mode.

Why not just load a pointer with the appropriate address:

struct _SIMCR *psimcr

psimcr = (struct _SIMCR *) 0xYFFA00; psimcr -> exoff = 0x11; etc.

BTW.: You are the first person I meet using the GNU compiler with an

68K. I might be intending to do that (for a non-Linux project). So I have some questions. What debugger are you using ? Does this work OK ?

Thanks

-Michael

Reply to
Michael Schnell

I got it after messing around. I defined all 8 or 16 bit registers as bitfields like that:

/* CSPAR1 (0xYFFA46): Chip Select Pin Assignement Register 1 */ typedef struct _CSPAR1 { WORD _UNUSED_1 : 6; WORD cspa14 : 2; WORD cspa13 : 2; WORD cspa12 : 2; WORD cspa11 : 2; WORD cspa10 : 2; } CSPAR1;

Then I built a monster struct having the *exact* layout as written in Motorola documentation. The struct has attribute packed: it's a gcc extension that disable all struct member alignement. The _uXX bytes are unused bytes in the SIM area. They exist to make sure registers are properly placed in memory.

typedef struct _SIM_REGISTERS { /* 15..............8 7...............0 */ /* ------------------------------------------------- */ /* YFFA00 */ SIMCR simcr; /* YFFA02 */ WORD simtr; /* YFFA04 */ SYNCR syncr; /* YFFA06 */ BYTE _u1; RSR rsr; /* YFFA08 */ WORD simtre; /* YFFA0A */ BYTE _u2; BYTE _u3; /* YFFA0C */ BYTE _u4; BYTE _u5; /* YFFA0E */ BYTE _u6; BYTE _u7; /* YFFA10 */ BYTE _u8; PORTE0 porte0; /* YFFA12 */ BYTE _u9; PORTE1 porte1; /* YFFA14 */ BYTE _u10; DDRE ddre;

... many more registers snipped ..

} __attribute__ ((__packed__)) SIM_REGISTERS;

extern volatile SIM_REGISTERS sim;

Then in sim_registers.c, you allocate a single global SIM_REGISTER:

volatile SIM_REGISTERS sim;

The variable is volatile because those registers can change anytime abd some bits always reads as 0 even if set them to 1. This 'sim' thing results in a so-called COMMON symbol (ie uninitialized symbol).

In the sections statement of the linker script, you add:

/* SIM (System Integration Module) Registers. This maps the * sim variable from bsp/registers/sim_registers.c at the * MCU SIM memory area. */ .sim_registers 0xFFFA00 : { sim_registers.o(COMMON); }

That section must be before bss in the linker script because otherwise, the sim variable could end up in .bss because typically .bss includes all COMMON symbols.

And there you go. The global sim structure match Motorola's doc. The C code can access registers directly with no pointers like that.

#include

int main(void) { return sim.simcr.iarb; }

If you look at the resulting S-Record (code begins at 0x3000)

S00700006D61696E53 S11330004E560000103900FFFA01720FC0814E5E67 S10530104E75F7 S10530120000B8 S113301441F900003044B1FC000030486406421811 S1133024528860F261FFFFFFFFD64E4F00634E7576 S1133034000000010000000200000003000030480A S10C3048466F6F6F6F6F6F21007A S9033014B8

you see no data/code loaded at FFFA00 which is what you want: registers do not get overwritten. This is similar to .bss sections: the data there takes no space in the executable but gets zeroed-out in RAM by startup code. Here, I just don't zero out the register area. If you see lots of S10FFFA00xxxxxxx, you didn't get it right, the CPU will crash when loading the code.

To verify you didn't screw up, you can run 'nm --size' on sim_registers.o or look at the linker produced map file. The sim variable should be the same size as the SIM area (128 bytes). The map file will show the placement:

.sim_registers 0x00fffa00 0x80 sim_registers.o(COMMON) COMMON 0x00fffa00 0x80 /home/Simon/kit331/bsp/registers/sim_registers.o 0x0 (size before relaxing) 0x00fffa00 sim

Looking at asm code generated by the compiler from the test C code above, you see the direct access (the sim.simcr.iarb field is in the 4 low order bits of the second byte of the sim area). So the asm code does what's expected. Apparently, m68k gcc aligns bit fields from MSB to LSB.

main: link.w %a6,#0 move.b sim+1,%d0 moveq #15,%d1 and.l %d1,%d0 unlk %a6 rts

Now that's quite non-portable code :-) You can do these tricks for stuff like interrupt vectors and every memory mapped registers.

Simon Paradis simon _dot_ paradis _at_ usherbrooke _dot_ ca

Reply to
Simon Paradis

Right. Nonetheless reverse ingeneering the ways of the compiler is dangerous. The position of bit fields in the variables is implementation depending (and maybe undefined at all) and so they can change with a new version of GCC or (even with the next compiler run). Though that supposedly is very unlikely.

-Michael

Reply to
Michael Schnell

IMHO it would be more straight forward to place the sim in it's own section

volatile SIM_REGISTERS sim __attribute__((section(".sim_reg")));

should do this.

Now in the linker script you can allocate this section .sim_reg at the appropriate address without any problems with COMMON.

-Michael

Reply to
Michael Schnell

Yes that's true.

Also, I was putting all those register in a big struct with packed attribute. That was a bad idea since I was already taking care of alignement myself. When you specify packed attribute; the compiler generates two 8 bit load when loading a 16bit int presumably because you can't load a word from odd memory addresses.

I removed the packed attribute because I know no such invalid load would occur and now the compiler load words in a single pass.

It's quite informative to read the asm output sometimes.

section

Reply to
Simon Paradis

Hi,

I have used the 68K GNU compiler on a project where we previously used the Crosscode compiler. The 68K GNU Compiler generates MUCH better code than the Crosscode compiler. You need to get a version of the gcc toolset which includes the patches made by Peter Barada. This includes a patch for the support of the interrupt attribute with the 68K, which enables the writing of interrupt handlers in C. Normally these patches refer to the Coldfire toolset, but this is the same toolset as for the m68K - just specify the actual CPU used. e.g -m68060 for the 68060. I use insight or gdb+DDD as the debugger. I modified an existing gdb-stub for our hardware, and can source level debug over the serial port. The actual code runs in RAM (copied at startup) so breakpoints etc. are not a problem.

Hope this helps Regards Anton Erasmus

Reply to
Anton Erasmus

Thanks !

-Michael

Reply to
Michael Schnell

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.