pyocd / pyOCD

Open source Python library for programming and debugging Arm Cortex-M microcontrollers
https://pyocd.io
Apache License 2.0
1.13k stars 483 forks source link

Can't access SCB or device peripherals #1528

Open pengi opened 1 year ago

pengi commented 1 year ago

I just got into pyOCD, and it works well for accessing RAM and Flash on my nRF52840.

Problem

When using the cortex_m target, I am also able to access the SCB at 0xe000ed00, but not when using the nrf52840 target. Reading peripherals and SCB are key when debugging odd behaviours on low level drivers, or tracking down fault cases.

Steps to reproduce

To reproduce the issue:

  1. Connect to any Cortex-M using a proper target. For example: pyocd gdbserver -t nrf52840
  2. Start gdb and connect: target remote localhost:3333
  3. Read system control block. For example, CPUID registers, as specified in ARMv7-M Architecture Reference Manual: (gdb) x 0xE000ED00

Or:

Use a tool like https://github.com/pengi/arm_gdb

  1. Connect to any Cortex-M using a proper target. For example: pyocd gdbserver -t nrf52840
  2. Start gdb and connect: target remote localhost:3333
  3. Read SCB: arm scb

What I expect to see:

Printout of the register, for example:

(gdb) x 0xe000ed00
0xe000ed00:     0x410fc241

What I see instead:

(gdb) x 0xe000ed00
0xe000ed00:     Cannot access memory at address 0xe000ed00

Analysis

I tracked it down to the cortex_m target is having the default memory map according to:

    def _create_default_cortex_m_memory_map(self) -> MemoryMap:
        """@brief Create a MemoryMap for the Cortex-M system address map."""
        return MemoryMap(
                RamRegion(name="Code",          start=0x00000000, length=0x20000000, access='rwx'),
                RamRegion(name="SRAM",          start=0x20000000, length=0x20000000, access='rwx'),
                DeviceRegion(name="Peripheral", start=0x40000000, length=0x20000000, access='rw'),
                RamRegion(name="RAM1",          start=0x60000000, length=0x20000000, access='rwx'),
                RamRegion(name="RAM2",          start=0x80000000, length=0x20000000, access='rwx'),
                DeviceRegion(name="Device1",    start=0xA0000000, length=0x20000000, access='rw'),
                DeviceRegion(name="Device2",    start=0xC0000000, length=0x20000000, access='rw'),
                DeviceRegion(name="PPB",        start=0xE0000000, length=0x20000000, access='rw'),
                )

This is also how at least ARMv6-M, ARMv7-M and ARMv8-M specifies it (exception is the split PPB area in ARMv8-M, but should work anyway I think). And those applies to all Cortex-M0 to Cortex-M33

When I checked target_nRF52840_xxAA.py, it only specifies:

    MEMORY_MAP = MemoryMap(
        FlashRegion(    start=0x0,         length=0x100000,     blocksize=0x1000, is_boot_memory=True,
            algo=FLASH_ALGO),
        # User Information Configation Registers (UICR) as a flash region
        FlashRegion(    start=0x10001000,  length=0x400,        blocksize=0x400, is_testable=False,
            algo=FLASH_ALGO),
        RamRegion(      start=0x20000000,  length=0x40000)
        )

which looks like the correct areas for flash and RAM, but it is missing the DeviceRegion areas specified in the cortex_m. When looking at it, it seems to be missing in all targets even.

I have made a workaround by adding a user script ~/.pyocd_user.py that ads the most important regions I'm missing, that I load with --script ~/.pyocd_user.py. But it is still a workaround in my opinion:

def will_connect(board):
    target.memory_map.add_region(DeviceRegion(
        name="Peripheral",
        start=0x40000000,
        length=0x20000000,
        access='rw'
    ))
    target.memory_map.add_region(DeviceRegion(
        name="PPB",
        start=0xE0000000,
        length=0x20000000,
        access='rw'
    ))

So the cortex_m target shows it is possible to use the DeviceRegion for access. But I think it's a generic problem that it is not available for any targets, since they are totally valid regions that should be available from the debugger.

Possible solutions

I'm thinking two options:

I've only used pyocd for a few hours now and yet only one target, so not sure about the best solution to go for it. What are your thoughts?

References

"System Address Map" in:

pengi commented 1 year ago

Also, I'm happy to fix it and make a PR for it. Just don't exactly know what preferred solution would be so far.

Personally, I'm leaning towards making coresight_target´ add the defaultDeviceRegions that are not overlapping any areas specified by the target. So if a target specifies aDeviceRegion` within a peripheral area, then that won't be added for example.

Or even do that for the other regions that are not DeviceRegion?

flit commented 1 year ago

Thanks for all the details!

There are a couple roots to the problem here. First is that, by default, gdb restricts access to the memory map supplied it to (thus causing the gdb error you see). Second, CMSIS-Packs don't normally include device regions in the memory map.

The standard solution for this is to issue set mem inaccessible-by-default no to gdb, so it ignores the supplied memory map and allows all accesses to be sent to the remote server (pyocd), and on to the target itself. Then the target just faults for truly invalid accesses.

I've also thought about inserting a region or regions for Device memory that isn't covered by the target's memory map. The problem is, it covers a 512 MB or more of memory. There are sometimes peripherals or memory you'd want to access not inside Device (mostly QuadSPI, DRAM, boot ROMs, and alias regions). The vendor PPB region covering the 0xf000_0000 range would also have to covered. So it's simplest to handle it on the gdb side.

That said, I'm not entirely opposed to insertion of Device regions. But it probably can't be the default behaviour (so controlled with a session option).

pengi commented 1 year ago

Hi, thanks for the reply

I've been thinking a bit about this, and I've realized I don't know enough about what should be included in the pack files at this point. Something I have to read up on.

There are two primary areas I'm thinking about.

The first is the System Control Space, (looking at ARMv7-M now) addresssed from 0xe0000000 to 0xffffffff, which I think the first 1MB of is used for the Private Peripheral Bus / PPB. After that comes Vendor_SYS. Question is, should the Private Peripheral Bus, including SCB, NVIC be part of the pack file?

Second is the peripherals block, which is specified in the ARM reference manual as interval 0x40000000 to 0x5fffffff. For what I undertand, not specified in ARM reference manual, but matches most implementors manuals I've seen, they are organized by the peripheral id. Does this area contain a proper memory map, or is it just to access the peripherals by having some bits mapping to the peripheral ID number. Should this be part of the pack files?

Since if they are not, then I think it makes sense that the probe adds them, so they will be available. And I think that's expected from most users, that they can access those memory areas from gdb, and it works with other tools already. So therefore I think it would be nice to have it to start with, since it would reduce the entry level to get started with pyocd even.

I documented my setup, including what I have to do to get a fully working setup. I really like pyocd though, so from first start, it is the tool I'm going to now. I just would like there to be less tweaks needed:

https://gist.github.com/pengi/de37f344f7a0a7141b597153236d9cac