{"article":{"slug":"whats-on-that-access-port","title":"What's on that Access Port?","subtitle":null,"summary":"Daniel Mangum digs into the Nordic nRF54LM20 access port and what you can learn when a peripheral writes memory while the CPU is halted.","content_type":"blog_post","language":"en","canonical_url":"https://danielmangum.com/posts/whats-on-that-access-port/","author":{"name":"Daniel Mangum","url":"https://danielmangum.com/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"danielmangum.com","url":"https://danielmangum.com/","listing_slug":null,"listing":null},"topics":[{"name":"Hardware","slug":"hardware","url":"https://listedarticles.com/topics/hardware"},{"name":"Engineering","slug":"engineering","url":"https://listedarticles.com/topics/engineering"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1960,"reading_minutes":9,"published_at":"2026-10-07T00:00:00.000Z","added_at":"2026-10-16T11:18:42.311Z","updated_at":"2026-10-16T11:18:42.311Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/whats-on-that-access-port","markdown_url":"https://listedarticles.com/articles/whats-on-that-access-port.md","example":false,"citation":"Daniel Mangum, danielmangum.com. \"What's on that Access Port?.\" 7 Oct 2026. https://danielmangum.com/posts/whats-on-that-access-port/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://danielmangum.com/posts/whats-on-that-access-port/"},"body_markdown":"In my last post, I wrote about debugging the Nordic Semiconductor nRF54LM20 when a peripheral, specifically the Key Management Unit (KMU), writes directly to memory while the CPU is halted. Or I should really say that I wrote about debugging the application core (Cortex-M33), as the nRF54LM20 also has a RISC-V coprocessor (VPR) referred to as the Fast Lightweight Peripheral Processor (FLPR). Around the time that Nordic announced the VPR core, I wrote two posts; one about its architecture and the other about how communication works between it and an application core.\n\nIn the latter VPR post, I attached to the application core with GDB and\n`JLinkGDBServer` in the same manner that I did in my recent KMU exploration post\nin order to step through the communication protocol. This strategy was\nappropriate for the operations described in the respective posts, but in some\ncases it may be desirable to connect to the VPR core for debugging.\n\nTypically, silicon vendors will provide tooling, or work with open source\nprojects, to make it easy to designate the core you want to connect to at a\nhigher level of abstraction. For example, Nordic products have robust suport in\nthe Zephyr RTOS ecosystem, and Zephyr’s meta\nCLI, `west`, can\nbe used to build (`west build`) and flash (`west flash`) firmware on all chips.\n`west` supports building and flashing multiple\nimages\nin one operation using System Build\n(Sysbuild).\nEach build system managed by Sysbuild is referred to as a domain, and the\n`--domain` flag can be used to perform the specified operation for only the\nspecified domain.\n\nWhile we typically want to target all domains when building and flashing, `west debug` is a great example of how the `--domain` flag can be useful. For example,\nif we were building the Zephyr `blinky` sample for the nRF54LM20, using the FLPR\nfor GPIO control via Nordic’s High Performance Framework\n(HPF)\n(stay tuned for a future post on HPF), we would use the following command to\nbuild the `blinky` application for the application core and the GPIO application\nfor the FLPR core.\n\n```\nwest build -p -b nrf54lm20dk/nrf54lm20b/cpuapp nrf/samples/zephyr/basic/blinky -- -DSB_CONFIG_HPF=y -DSB_CONFIG_HPF_GPIO=y -DSB_CONFIG_HPF_GPIO_BACKEND_ICMSG=y -DEXTRA_DTC_OVERLAY_FILE=\"./boards/nrf54lm20dk_nrf54lm20b_cpuapp_hpf_gpio.overlay\"\n```\nIn the resulting `build/` directory, we would see a top-level `domains.yaml`\nfile with the following contents.\n\n```\ndefault: blinky\nbuild_dir: <local-path>/build\ndomains:\n  - name: blinky\n    build_dir: <local-application-path>/build/blinky\n  - name: hpf_gpio\n    build_dir: <local-application-path>/build/hpf_gpio\nflash_order:\n  - blinky\n  - hpf_gpio\n```\nIssuing a `west flash` command would flash both of the images onto the device,\nbut because we can only debug one core at a time, `west debug` with no flags\nwould target the `default` domain (`blinky`) and connect to the application\ncore. Specifying `hpf_gpio` would instead connect to the FLPR.\n\n```\nwest debug --domain hpf_gpio\n```\nIf you prefer a graphical UX, you could leverage this same functionality via Nordic’s VSCode tooling, as described in this post by my Nordic coworker Sebastian Viviani.\n\n\nBehind the scenes, Zephyr includes board support files that help `west` know how\nto setup the debugging connection. If we were to look in each specific domain’s\n`build/<domain>/zephyr/` directory, we would find a `runners.yaml` file. For\nexample, the `hpf_gpio` domain in the previous build includes the following\ncontents.\n\n```\n# Available runners configured by board.cmake.\nrunners:\n- nrfutil\n- jlink\n# Default flash runner if --runner is not given.\nflash-runner: nrfutil\n# Default debug runner if --runner is not given.\ndebug-runner: jlink\n# Common runner configuration values.\nconfig:\n  board_dir: <local-application-path>/zephyr/boards/nordic/nrf54lm20dk\n  # Build outputs:\n  elf_file: zephyr.elf\n  hex_file: zephyr.hex\n  bin_file: zephyr.bin\n  # Host tools:\n  gdb: <local-zephyr-sdk-path>/zephyr-sdk-1.0.1/gnu/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gdb\n  openocd: <local-zephyr-sdk-path>/zephyr-sdk-1.0.1/hosttools/usr/bin/openocd\n  openocd_search:\n    - <local-zephyr-sdk-path>/zephyr-sdk-1.0.1/hosttools/opt/openocd/share/openocd/scripts\n# Runner specific arguments\nargs:\n  nrfutil:\n    []\n  jlink:\n    - --dt-flash=y\n    - --device=nRF54LM20A_RV32\n    - --speed=4000\n```\nIf we were to look at the nRF54LM20 DK board support files in the Zephyr source tree, we would find the following CMake snippet.\n\n`boards/nordic/nrf54lm20dk/board.cmake`\n\n```\nif(CONFIG_SOC_NRF54LM20A_CPUAPP OR CONFIG_SOC_NRF54LM20B_CPUAPP)\n  board_runner_args(jlink \"--device=nRF54LM20A_M33\" \"--speed=4000\")\nelseif(CONFIG_SOC_NRF54LM20A_CPUFLPR OR CONFIG_SOC_NRF54LM20B_CPUFLPR)\n  board_runner_args(jlink \"--device=nRF54LM20A_RV32\" \"--speed=4000\")\nendif()\nif(CONFIG_TFM_FLASH_MERGED_BINARY)\n  set_property(TARGET runners_yaml_props_target PROPERTY hex_file tfm_merged.hex)\nendif()\ninclude(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)\ninclude(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)\n```\nThe `args.jlink` flags at the bottom of the `runners.yaml` file are sourced from\nthese `board_runner_args()` CMake macros, and they are passed to\n`JLinkGDBServer` when invoking `west debug` for the corresponding domain.\nBecause J-Link has native multi-core\nsupport for the nRF54L\nseries, the `--device` flag can be used to tell `JLinkGDBServer` to consult its\ninternal database for information on how to connect to each of the respective\ncores.\n\nIf the debugger is not already knowledgeable of the SoC architecture, it may be necessary to provide more detailed information. For example, see the nRF54H20 J-Link script for connecting to the application core.\n\n\nIn fact, if we were to connect to the FLPR core on the nRF54LM20 using `JLinkExe -device nRF54LM20A_RV32` directly, we would see the following sequence of lines\nin the initial output.\n\n```\nFound SW-DP with ID 0x6BA02477\nRISC-V behind DAP detected\nDPIDR: 0x6BA02477\nCoreSight SoC-400 or earlier\nAP[1] (AHB-AP) specified by user as debug AP.\nAP map scan skipped.\nAP map:\n  AP[0]: AHB-AP, APAddr = 0x00000000\n  AP[1]: AHB-AP, APAddr = 0x01000000\n  AP[2]: MEM-AP, APAddr = 0x02000000\nCore base addr: 0x5004C400 (user configured)\n```\nIn my nRF54LM20 KMU post, I included the following diagram from the nRF54LM20 datasheet that describes the debug architecture, including the three Access Ports (APs) that are available via the Debug Access Port (DAP).\n\nThe datasheet also includes a table describing the functionality of each access port.\n\n| AP ID | Type | Description | \n|---|---|---|\n| 0 | AHB-AP | CM33 access port | \n| 1 | AHB-AP | AUX access port | \n| 2 | CTRL-AP | Control access port | \n\nAs observed in the `JLinkExe` output, it opts to forego scanning the access port\nmap in favor of connecting directly to the second\nAHB-AP\n(`AP[1]`), which it knows to be the access port for the RISC-V FLPR core. If\ninstead invoking `JLinkExe -device nRF54LM20A_M33` to debug the Cortex-M33\napplication core, the following output would be observed.\n\n```\nFound SW-DP with ID 0x6BA02477\nDPIDR: 0x6BA02477\nCoreSight SoC-400 or earlier\nAP map detection skipped. Manually configured AP map found.\nAP[0]: AHB-AP (IDR: Not set, ADDR: 0x00000000)\nAP[1]: APB-AP (IDR: Not set, ADDR: 0x00000000)\nAP[2]: MEM-AP (IDR: Not set, ADDR: 0x00000000)\nIterating through AP map to find AHB-AP to use\nAP[0]: Core found\nAP[0]: AHB-AP ROM base: 0xE00FE000\nCPUID register: 0x411FD210. Implementer code: 0x41 (ARM)\n```\nIn this case the J-Link once again reports that the AP map is known, but instead\nof selecting an access port immediately, it iterates through the map looking for\nthe Cortex-M33 core. While the underlying logic is hidden in the closed source\n`JLinkArm.dll` library that underlies most J-Link tooling, it is likely that the\ndefault behavior for Arm cores, even those on devices known to the J-Link, is to\nleverage the information offered by the Arm Debug Interface\n(ADI) to discover the core and\nits capabilities.\n\nThe ADI specifies the architecture of a Debug Access Port (DAP), which consists of a Debug Port (DP) and one or more Access Ports (APs).\n\nThere are multiple types of debug ports: JTAG (JTAG-DP), Serial Wire (SW-DP), and Serial Wire / JTAG (SWJ-DP). Likewise, there are multiple types of access ports, with the Advanced High-performance Bus Access Port (AHB-AP) itself being a type of Memory Access Port (MEM-AP), and the Control Access Port (CTRL-AP) being a custom AP implementation by Nordic. There are a minimum set of registers for DPs and APs respectively, which must be provided by any implementation. The following diagram outlines the interaction between the DP and MEM-APs (and thus AHB-APs).\n\nThis diagram gives a hint to how a debug probe, such as the J-Link, may go about\ndiscovering the access ports that are available on a given DAP, even if the SoC\nis not already known. The first relevant register is the DP’s AP Select\n(`SELECT`) register (highlighted in the DP registers above). It can be used to\nselect an access port (`APSEL`) and the register bank (`APBANKSEL`) within the\naccess port.\n\n| APSEL | RES0 | APBANKSEL | DPBANKSEL | \n|---|---|---|---|\n| XXXXXXXX | XXXXXXXXXXXXXXXX | XXXX | XXXX | \n| [31:24] | [23:8] | [7:4] | [3:0] | \n\nIf the value in the `APSEL` bits does not correspond to an access port on the\nDAP, the ADI defines the following behavior.\n\nIf there is no AP with the ID APSEL, all AP transactions return zero on reads and are ignored on writes.\n\n\nAfter successfully selecting an access port, the AP’s identification\nregister\n(`IDR`) (highlighted in the MEM-AP registers above), which is the only register\nthat must be implemented by all APs, can be used to understand the attributes of\nthe AP. It is always the last register in the AP register space, located at\n`0xFC`.\n\n| Revision | Designer | Class | RES0 | Variant | Type | \n|---|---|---|---|---|---|\n| XXXX | XXXXXXXXXXX | XXXX | XXXXX | XXXX | XXXX | \n| [31:28] | [27:17] | [16:13] | [12:8] | [7:4] | [3:0] | \n\nFor the most part, debuggers hide the specific reads and writes to DP and AP\nregisters behind higher levels of abstractions, such as reading a memory address\nor advancing the program counter of a CPU. However, J-Link does support lower\nlevel commands to read and write directly to DPs (`ReadDP` / `WriteDP`) and APs\n(`ReadAP` / `WriteAP`). If you wanted to manually scan the AP map, you could use\nthe following sequence of commands.\n\nWrite to DP register `2` (`SELECT`), setting the `APSEL` value to `0` and the\n`APBANKSEL` value to `0xF`, which will allow for targeting the `IDR` register\n(`0xFC`) on subsequent AP reads.\n\n```\nJ-Link> WriteDP 2 0x000000F0\nWriting DP register 2 = 0x000000F0 (0 write repetitions needed)\n```\nRead AP register `3` (`IDR` in bank `0xF`).\n\n```\nJ-Link> ReadAP 3\nReading AP register 3 = 0x84770001 (0 read repetitions needed)\n```\nThe resulting value (`0x84770001`) can be decoded to provide information about\nthe AP, which we already know to be an AHB-AP.\n\n| Revision | Designer | Class | RES0 | Variant | Type | \n|---|---|---|---|---|---|\n| 1000 | 01000111011 | 1000 | 00000 | 0000 | 0001 | \n| Revision 8 | Arm Limited | Mem-AP | RESERVED | - | AMBA AHB3 bus | \n\nIssuing the same sequence of commands with `APSEL` set to `1` once again informs\nus that the second access port is also an AHB-AP.\n\n```\nJ-Link> WriteDP 2 0x010000F0\nWriting DP register 2 = 0x010000F0 (0 write repetitions needed)\n```\n```\nJ-Link> ReadAP 3\nReading AP register 3 = 0x84770001 (0 read repetitions needed)\n```\nHowever, reading the third access port (`2`), returns a different `IDR` value.\n\n```\nJ-Link> WriteDP 2 0x020000F0\nWriting DP register 2 = 0x020000F0 (0 write repetitions needed)\n```\n```\nJ-Link> ReadAP 3\nReading AP register 3 = 0x32880000 (0 read repetitions needed)\n```\nThis is expected given that the third AP is a Nordic CTRL-AP (`0x32880000`).\nDecoding the fields in the register, we can identity Nordic’s JEDEC\nmanufacturing\ncode\nin the `DESIGNER` field, as specified in the nRF54LM20\ndatasheet.\n\n| Revision | Designer | Class | RES0 | Variant | Type | \n|---|---|---|---|---|---|\n| 0011 | 00101000100 | 0000 | 00000 | 0000 | 0000 | \n| Revision 3 | Nordic VLSI ASA | Undefined | RESERVED | - | CTRL-AP | \n\nAn attempt to select a fourth access port (`3`) results in a read of the `IDR`\nregister returning `0x00000000`, indicating that we have likely reached the end\nof the AP map.\n\nThough silicon vendors will typically make understanding the low level details of the debug interface extraneous, having at least cursory knowledge of how a debugger enables you to access and control various components of an SoC can be useful. Direct DP and AP register accesses may be necessary in the event that you are reverse engineering an unknown chip, authoring your own debug tooling, or attempting to verify the security state of your product.","body_html":"<p>In my last post, I wrote about debugging the Nordic Semiconductor nRF54LM20 when a peripheral, specifically the Key Management Unit (KMU), writes directly to memory while the CPU is halted. Or I should really say that I wrote about debugging the application core (Cortex-M33), as the nRF54LM20 also has a RISC-V coprocessor (VPR) referred to as the Fast Lightweight Peripheral Processor (FLPR). Around the time that Nordic announced the VPR core, I wrote two posts; one about its architecture and the other about how communication works between it and an application core.</p>\n<p>In the latter VPR post, I attached to the application core with GDB and\n<code>JLinkGDBServer</code> in the same manner that I did in my recent KMU exploration post\nin order to step through the communication protocol. This strategy was\nappropriate for the operations described in the respective posts, but in some\ncases it may be desirable to connect to the VPR core for debugging.</p>\n<p>Typically, silicon vendors will provide tooling, or work with open source\nprojects, to make it easy to designate the core you want to connect to at a\nhigher level of abstraction. For example, Nordic products have robust suport in\nthe Zephyr RTOS ecosystem, and Zephyr’s meta\nCLI, <code>west</code>, can\nbe used to build (<code>west build</code>) and flash (<code>west flash</code>) firmware on all chips.\n<code>west</code> supports building and flashing multiple\nimages\nin one operation using System Build\n(Sysbuild).\nEach build system managed by Sysbuild is referred to as a domain, and the\n<code>--domain</code> flag can be used to perform the specified operation for only the\nspecified domain.</p>\n<p>While we typically want to target all domains when building and flashing, <code>west debug</code> is a great example of how the <code>--domain</code> flag can be useful. For example,\nif we were building the Zephyr <code>blinky</code> sample for the nRF54LM20, using the FLPR\nfor GPIO control via Nordic’s High Performance Framework\n(HPF)\n(stay tuned for a future post on HPF), we would use the following command to\nbuild the <code>blinky</code> application for the application core and the GPIO application\nfor the FLPR core.</p>\n<pre><code>west build -p -b nrf54lm20dk/nrf54lm20b/cpuapp nrf/samples/zephyr/basic/blinky -- -DSB_CONFIG_HPF=y -DSB_CONFIG_HPF_GPIO=y -DSB_CONFIG_HPF_GPIO_BACKEND_ICMSG=y -DEXTRA_DTC_OVERLAY_FILE=&quot;./boards/nrf54lm20dk_nrf54lm20b_cpuapp_hpf_gpio.overlay&quot;</code></pre>\n<p>In the resulting <code>build/</code> directory, we would see a top-level <code>domains.yaml</code>\nfile with the following contents.</p>\n<pre><code>default: blinky\nbuild_dir: &lt;local-path&gt;/build\ndomains:\n  - name: blinky\n    build_dir: &lt;local-application-path&gt;/build/blinky\n  - name: hpf_gpio\n    build_dir: &lt;local-application-path&gt;/build/hpf_gpio\nflash_order:\n  - blinky\n  - hpf_gpio</code></pre>\n<p>Issuing a <code>west flash</code> command would flash both of the images onto the device,\nbut because we can only debug one core at a time, <code>west debug</code> with no flags\nwould target the <code>default</code> domain (<code>blinky</code>) and connect to the application\ncore. Specifying <code>hpf_gpio</code> would instead connect to the FLPR.</p>\n<pre><code>west debug --domain hpf_gpio</code></pre>\n<p>If you prefer a graphical UX, you could leverage this same functionality via Nordic’s VSCode tooling, as described in this post by my Nordic coworker Sebastian Viviani.</p>\n<p>Behind the scenes, Zephyr includes board support files that help <code>west</code> know how\nto setup the debugging connection. If we were to look in each specific domain’s\n<code>build/&lt;domain&gt;/zephyr/</code> directory, we would find a <code>runners.yaml</code> file. For\nexample, the <code>hpf_gpio</code> domain in the previous build includes the following\ncontents.</p>\n<pre><code># Available runners configured by board.cmake.\nrunners:\n- nrfutil\n- jlink\n# Default flash runner if --runner is not given.\nflash-runner: nrfutil\n# Default debug runner if --runner is not given.\ndebug-runner: jlink\n# Common runner configuration values.\nconfig:\n  board_dir: &lt;local-application-path&gt;/zephyr/boards/nordic/nrf54lm20dk\n  # Build outputs:\n  elf_file: zephyr.elf\n  hex_file: zephyr.hex\n  bin_file: zephyr.bin\n  # Host tools:\n  gdb: &lt;local-zephyr-sdk-path&gt;/zephyr-sdk-1.0.1/gnu/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gdb\n  openocd: &lt;local-zephyr-sdk-path&gt;/zephyr-sdk-1.0.1/hosttools/usr/bin/openocd\n  openocd_search:\n    - &lt;local-zephyr-sdk-path&gt;/zephyr-sdk-1.0.1/hosttools/opt/openocd/share/openocd/scripts\n# Runner specific arguments\nargs:\n  nrfutil:\n    []\n  jlink:\n    - --dt-flash=y\n    - --device=nRF54LM20A_RV32\n    - --speed=4000</code></pre>\n<p>If we were to look at the nRF54LM20 DK board support files in the Zephyr source tree, we would find the following CMake snippet.</p>\n<p><code>boards/nordic/nrf54lm20dk/board.cmake</code></p>\n<pre><code>if(CONFIG_SOC_NRF54LM20A_CPUAPP OR CONFIG_SOC_NRF54LM20B_CPUAPP)\n  board_runner_args(jlink &quot;--device=nRF54LM20A_M33&quot; &quot;--speed=4000&quot;)\nelseif(CONFIG_SOC_NRF54LM20A_CPUFLPR OR CONFIG_SOC_NRF54LM20B_CPUFLPR)\n  board_runner_args(jlink &quot;--device=nRF54LM20A_RV32&quot; &quot;--speed=4000&quot;)\nendif()\nif(CONFIG_TFM_FLASH_MERGED_BINARY)\n  set_property(TARGET runners_yaml_props_target PROPERTY hex_file tfm_merged.hex)\nendif()\ninclude(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)\ninclude(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)</code></pre>\n<p>The <code>args.jlink</code> flags at the bottom of the <code>runners.yaml</code> file are sourced from\nthese <code>board_runner_args()</code> CMake macros, and they are passed to\n<code>JLinkGDBServer</code> when invoking <code>west debug</code> for the corresponding domain.\nBecause J-Link has native multi-core\nsupport for the nRF54L\nseries, the <code>--device</code> flag can be used to tell <code>JLinkGDBServer</code> to consult its\ninternal database for information on how to connect to each of the respective\ncores.</p>\n<p>If the debugger is not already knowledgeable of the SoC architecture, it may be necessary to provide more detailed information. For example, see the nRF54H20 J-Link script for connecting to the application core.</p>\n<p>In fact, if we were to connect to the FLPR core on the nRF54LM20 using <code>JLinkExe -device nRF54LM20A_RV32</code> directly, we would see the following sequence of lines\nin the initial output.</p>\n<pre><code>Found SW-DP with ID 0x6BA02477\nRISC-V behind DAP detected\nDPIDR: 0x6BA02477\nCoreSight SoC-400 or earlier\nAP[1] (AHB-AP) specified by user as debug AP.\nAP map scan skipped.\nAP map:\n  AP[0]: AHB-AP, APAddr = 0x00000000\n  AP[1]: AHB-AP, APAddr = 0x01000000\n  AP[2]: MEM-AP, APAddr = 0x02000000\nCore base addr: 0x5004C400 (user configured)</code></pre>\n<p>In my nRF54LM20 KMU post, I included the following diagram from the nRF54LM20 datasheet that describes the debug architecture, including the three Access Ports (APs) that are available via the Debug Access Port (DAP).</p>\n<p>The datasheet also includes a table describing the functionality of each access port.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>AP ID</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>0</td><td>AHB-AP</td><td>CM33 access port</td></tr><tr><td>1</td><td>AHB-AP</td><td>AUX access port</td></tr><tr><td>2</td><td>CTRL-AP</td><td>Control access port</td></tr></tbody></table></div>\n<p>As observed in the <code>JLinkExe</code> output, it opts to forego scanning the access port\nmap in favor of connecting directly to the second\nAHB-AP\n(<code>AP[1]</code>), which it knows to be the access port for the RISC-V FLPR core. If\ninstead invoking <code>JLinkExe -device nRF54LM20A_M33</code> to debug the Cortex-M33\napplication core, the following output would be observed.</p>\n<pre><code>Found SW-DP with ID 0x6BA02477\nDPIDR: 0x6BA02477\nCoreSight SoC-400 or earlier\nAP map detection skipped. Manually configured AP map found.\nAP[0]: AHB-AP (IDR: Not set, ADDR: 0x00000000)\nAP[1]: APB-AP (IDR: Not set, ADDR: 0x00000000)\nAP[2]: MEM-AP (IDR: Not set, ADDR: 0x00000000)\nIterating through AP map to find AHB-AP to use\nAP[0]: Core found\nAP[0]: AHB-AP ROM base: 0xE00FE000\nCPUID register: 0x411FD210. Implementer code: 0x41 (ARM)</code></pre>\n<p>In this case the J-Link once again reports that the AP map is known, but instead\nof selecting an access port immediately, it iterates through the map looking for\nthe Cortex-M33 core. While the underlying logic is hidden in the closed source\n<code>JLinkArm.dll</code> library that underlies most J-Link tooling, it is likely that the\ndefault behavior for Arm cores, even those on devices known to the J-Link, is to\nleverage the information offered by the Arm Debug Interface\n(ADI) to discover the core and\nits capabilities.</p>\n<p>The ADI specifies the architecture of a Debug Access Port (DAP), which consists of a Debug Port (DP) and one or more Access Ports (APs).</p>\n<p>There are multiple types of debug ports: JTAG (JTAG-DP), Serial Wire (SW-DP), and Serial Wire / JTAG (SWJ-DP). Likewise, there are multiple types of access ports, with the Advanced High-performance Bus Access Port (AHB-AP) itself being a type of Memory Access Port (MEM-AP), and the Control Access Port (CTRL-AP) being a custom AP implementation by Nordic. There are a minimum set of registers for DPs and APs respectively, which must be provided by any implementation. The following diagram outlines the interaction between the DP and MEM-APs (and thus AHB-APs).</p>\n<p>This diagram gives a hint to how a debug probe, such as the J-Link, may go about\ndiscovering the access ports that are available on a given DAP, even if the SoC\nis not already known. The first relevant register is the DP’s AP Select\n(<code>SELECT</code>) register (highlighted in the DP registers above). It can be used to\nselect an access port (<code>APSEL</code>) and the register bank (<code>APBANKSEL</code>) within the\naccess port.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>APSEL</th><th>RES0</th><th>APBANKSEL</th><th>DPBANKSEL</th></tr></thead><tbody><tr><td>XXXXXXXX</td><td>XXXXXXXXXXXXXXXX</td><td>XXXX</td><td>XXXX</td></tr><tr><td>[31:24]</td><td>[23:8]</td><td>[7:4]</td><td>[3:0]</td></tr></tbody></table></div>\n<p>If the value in the <code>APSEL</code> bits does not correspond to an access port on the\nDAP, the ADI defines the following behavior.</p>\n<p>If there is no AP with the ID APSEL, all AP transactions return zero on reads and are ignored on writes.</p>\n<p>After successfully selecting an access port, the AP’s identification\nregister\n(<code>IDR</code>) (highlighted in the MEM-AP registers above), which is the only register\nthat must be implemented by all APs, can be used to understand the attributes of\nthe AP. It is always the last register in the AP register space, located at\n<code>0xFC</code>.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Revision</th><th>Designer</th><th>Class</th><th>RES0</th><th>Variant</th><th>Type</th></tr></thead><tbody><tr><td>XXXX</td><td>XXXXXXXXXXX</td><td>XXXX</td><td>XXXXX</td><td>XXXX</td><td>XXXX</td></tr><tr><td>[31:28]</td><td>[27:17]</td><td>[16:13]</td><td>[12:8]</td><td>[7:4]</td><td>[3:0]</td></tr></tbody></table></div>\n<p>For the most part, debuggers hide the specific reads and writes to DP and AP\nregisters behind higher levels of abstractions, such as reading a memory address\nor advancing the program counter of a CPU. However, J-Link does support lower\nlevel commands to read and write directly to DPs (<code>ReadDP</code> / <code>WriteDP</code>) and APs\n(<code>ReadAP</code> / <code>WriteAP</code>). If you wanted to manually scan the AP map, you could use\nthe following sequence of commands.</p>\n<p>Write to DP register <code>2</code> (<code>SELECT</code>), setting the <code>APSEL</code> value to <code>0</code> and the\n<code>APBANKSEL</code> value to <code>0xF</code>, which will allow for targeting the <code>IDR</code> register\n(<code>0xFC</code>) on subsequent AP reads.</p>\n<pre><code>J-Link&gt; WriteDP 2 0x000000F0\nWriting DP register 2 = 0x000000F0 (0 write repetitions needed)</code></pre>\n<p>Read AP register <code>3</code> (<code>IDR</code> in bank <code>0xF</code>).</p>\n<pre><code>J-Link&gt; ReadAP 3\nReading AP register 3 = 0x84770001 (0 read repetitions needed)</code></pre>\n<p>The resulting value (<code>0x84770001</code>) can be decoded to provide information about\nthe AP, which we already know to be an AHB-AP.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Revision</th><th>Designer</th><th>Class</th><th>RES0</th><th>Variant</th><th>Type</th></tr></thead><tbody><tr><td>1000</td><td>01000111011</td><td>1000</td><td>00000</td><td>0000</td><td>0001</td></tr><tr><td>Revision 8</td><td>Arm Limited</td><td>Mem-AP</td><td>RESERVED</td><td>-</td><td>AMBA AHB3 bus</td></tr></tbody></table></div>\n<p>Issuing the same sequence of commands with <code>APSEL</code> set to <code>1</code> once again informs\nus that the second access port is also an AHB-AP.</p>\n<pre><code>J-Link&gt; WriteDP 2 0x010000F0\nWriting DP register 2 = 0x010000F0 (0 write repetitions needed)</code></pre>\n<pre><code>J-Link&gt; ReadAP 3\nReading AP register 3 = 0x84770001 (0 read repetitions needed)</code></pre>\n<p>However, reading the third access port (<code>2</code>), returns a different <code>IDR</code> value.</p>\n<pre><code>J-Link&gt; WriteDP 2 0x020000F0\nWriting DP register 2 = 0x020000F0 (0 write repetitions needed)</code></pre>\n<pre><code>J-Link&gt; ReadAP 3\nReading AP register 3 = 0x32880000 (0 read repetitions needed)</code></pre>\n<p>This is expected given that the third AP is a Nordic CTRL-AP (<code>0x32880000</code>).\nDecoding the fields in the register, we can identity Nordic’s JEDEC\nmanufacturing\ncode\nin the <code>DESIGNER</code> field, as specified in the nRF54LM20\ndatasheet.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Revision</th><th>Designer</th><th>Class</th><th>RES0</th><th>Variant</th><th>Type</th></tr></thead><tbody><tr><td>0011</td><td>00101000100</td><td>0000</td><td>00000</td><td>0000</td><td>0000</td></tr><tr><td>Revision 3</td><td>Nordic VLSI ASA</td><td>Undefined</td><td>RESERVED</td><td>-</td><td>CTRL-AP</td></tr></tbody></table></div>\n<p>An attempt to select a fourth access port (<code>3</code>) results in a read of the <code>IDR</code>\nregister returning <code>0x00000000</code>, indicating that we have likely reached the end\nof the AP map.</p>\n<p>Though silicon vendors will typically make understanding the low level details of the debug interface extraneous, having at least cursory knowledge of how a debugger enables you to access and control various components of an SoC can be useful. Direct DP and AP register accesses may be necessary in the event that you are reverse engineering an unknown chip, authoring your own debug tooling, or attempting to verify the security state of your product.</p>","headings":[]}}