This is an automated email from Gerrit.

"Name of user not set <[email protected]>" just uploaded a new patch 
set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/9786

-- gerrit

commit faedee0edf4bb469dfbe56dd8f362ae9e2fee117
Author: ImproperCatGirl <[email protected]>
Date:   Sat Jul 18 23:27:13 2026 +0800

    target/riscv: add DTM abstraction and AP backend
    
    Introduce a named RISC-V DTM object and route DMI access through backend
    operations instead of assuming that every RISC-V target owns a JTAG TAP.
    The legacy JTAG path remains the default and is implemented as a JTAG DTM
    backend.
    
    Add an ADIv5/ADIv6 AP-backed DTM backend so targets such as RP2350
    Hazard3 can expose RISC-V DMI through an AP rather than through JTAG.
    
    Keep the higher RISC-V debug-module register logic transport-neutral and
    add a minimal RP2350 RISC-V target configuration as a testable example
    for CMSIS-DAP/SWD setups.
    
    Change-Id: I47c2501023fbbe59f2f0502ff84d1c2106080a5a
    Signed-off-by: ImproperCatGirl <[email protected]>

diff --git a/src/openocd.c b/src/openocd.c
index f3e1bee48e..27e9959b96 100644
--- a/src/openocd.c
+++ b/src/openocd.c
@@ -27,6 +27,7 @@
 #include <target/arm_cti.h>
 #include <target/arm_adi_v5.h>
 #include <target/arm_tpiu_swo.h>
+#include <target/riscv/dtm.h>
 #include <rtt/rtt.h>
 
 #include <server/server.h>
@@ -147,6 +148,10 @@ COMMAND_HANDLER(handle_init_command)
        if (retval != ERROR_OK)
                return ERROR_FAIL;
 
+       retval = command_run_line(CMD_CTX, "dtm init");
+       if (retval != ERROR_OK)
+               return ERROR_FAIL;
+
        LOG_DEBUG("Examining targets...");
        if (target_examine() != ERROR_OK)
                LOG_DEBUG("target examination failed");
@@ -247,6 +252,7 @@ static int (* const command_registrants[])(struct 
command_context *cmd_ctx_value
        pld_register_commands,
        cti_register_commands,
        dap_register_commands,
+       riscv_dtm_register_commands,
        arm_tpiu_swo_register_commands,
 };
 
@@ -362,6 +368,7 @@ int openocd_main(int argc, char *argv[])
        /* free all DAP and CTI objects */
        arm_cti_cleanup_all();
        dap_cleanup_all();
+       riscv_dtm_cleanup_all();
 
        adapter_quit();
 
diff --git a/src/target/riscv/Makefile.am b/src/target/riscv/Makefile.am
index 189edb3e6e..ebe96d73c1 100644
--- a/src/target/riscv/Makefile.am
+++ b/src/target/riscv/Makefile.am
@@ -5,9 +5,12 @@ noinst_LTLIBRARIES += %D%/libriscv.la
        %D%/batch.h \
        %D%/debug_defines.h \
        %D%/debug_reg_printer.h \
+       %D%/dmi.h \
+       %D%/dtm.h \
        %D%/encoding.h \
        %D%/field_helpers.h \
        %D%/gdb_regs.h \
+       %D%/dtm/internal.h \
        %D%/opcodes.h \
        %D%/program.h \
        %D%/riscv.h \
@@ -27,6 +30,10 @@ noinst_LTLIBRARIES += %D%/libriscv.la
        %D%/riscv_reg.c \
        %D%/riscv_semihosting.c \
        %D%/debug_defines.c \
-       %D%/debug_reg_printer.c
+       %D%/debug_reg_printer.c \
+       %D%/dmi.c \
+       %D%/dtm.c \
+       %D%/dtm/adi_ap.c \
+       %D%/dtm/jtag.c
 
 STARTUP_TCL_SRCS += %D%/startup.tcl
diff --git a/src/target/riscv/batch.c b/src/target/riscv/batch.c
index 5acbddaf37..d482ab715a 100644
--- a/src/target/riscv/batch.c
+++ b/src/target/riscv/batch.c
@@ -5,453 +5,113 @@
 #endif
 
 #include "batch.h"
-#include "debug_defines.h"
-#include "debug_reg_printer.h"
-#include "riscv.h"
-#include "field_helpers.h"
+#include "dmi.h"
 
-// TODO: DTM_DMI_MAX_ADDRESS_LENGTH should be reduced to 32 (per the debug 
spec)
-#define DTM_DMI_MAX_ADDRESS_LENGTH     ((1<<DTM_DTMCS_ABITS_LENGTH)-1)
-#define DMI_SCAN_MAX_BIT_LENGTH (DTM_DMI_MAX_ADDRESS_LENGTH + 
DTM_DMI_DATA_LENGTH + DTM_DMI_OP_LENGTH)
-
-#define DMI_SCAN_BUF_SIZE (DIV_ROUND_UP(DMI_SCAN_MAX_BIT_LENGTH, 8))
-
-/* Reserve extra room in the batch (needed for the last NOP operation) */
-#define BATCH_RESERVED_SCANS 1
-
-static unsigned int get_dmi_scan_length(const struct target *target)
-{
-       const unsigned int abits = riscv_get_dmi_address_bits(target);
-       assert(abits > 0);
-       assert(abits <= DTM_DMI_MAX_ADDRESS_LENGTH);
-
-       return abits + DTM_DMI_DATA_LENGTH + DTM_DMI_OP_LENGTH;
-}
+#include <assert.h>
 
 struct riscv_batch *riscv_batch_alloc(struct target *target, size_t scans)
 {
-       scans += BATCH_RESERVED_SCANS;
-       struct riscv_batch *out = calloc(1, sizeof(*out));
-       if (!out) {
-               LOG_ERROR("Failed to allocate struct riscv_batch");
-               return NULL;
-       }
-
-       out->target = target;
-       out->allocated_scans = scans;
-       out->last_scan = RISCV_SCAN_TYPE_INVALID;
-       out->was_run = false;
-       out->last_scan_delay = 0;
-
-       out->data_out = NULL;
-       out->data_in = NULL;
-       out->fields = NULL;
-       out->delay_classes = NULL;
-       out->bscan_ctxt = NULL;
-       out->read_keys = NULL;
-
-       /* FIXME: There is potential for memory usage reduction. We could 
allocate
-        * smaller buffers than DMI_SCAN_BUF_SIZE (that is, buffers that 
correspond to
-        * the real DR scan length on the given target) */
-       out->data_out = malloc(sizeof(*out->data_out) * scans * 
DMI_SCAN_BUF_SIZE);
-       if (!out->data_out) {
-               LOG_ERROR("Failed to allocate data_out in RISC-V batch.");
-               goto alloc_error;
-       };
-       out->data_in = malloc(sizeof(*out->data_in) * scans * 
DMI_SCAN_BUF_SIZE);
-       if (!out->data_in) {
-               LOG_ERROR("Failed to allocate data_in in RISC-V batch.");
-               goto alloc_error;
-       }
-       out->fields = malloc(sizeof(*out->fields) * scans);
-       if (!out->fields) {
-               LOG_ERROR("Failed to allocate fields in RISC-V batch.");
-               goto alloc_error;
-       }
-       out->delay_classes = malloc(sizeof(*out->delay_classes) * scans);
-       if (!out->delay_classes) {
-               LOG_ERROR("Failed to allocate delay_classes in RISC-V batch.");
-               goto alloc_error;
-       }
-       if (bscan_tunnel_ir_width != 0) {
-               out->bscan_ctxt = malloc(sizeof(*out->bscan_ctxt) * scans);
-               if (!out->bscan_ctxt) {
-                       LOG_ERROR("Failed to allocate bscan_ctxt in RISC-V 
batch.");
-                       goto alloc_error;
-               }
-       }
-       out->read_keys = malloc(sizeof(*out->read_keys) * scans);
-       if (!out->read_keys) {
-               LOG_ERROR("Failed to allocate read_keys in RISC-V batch.");
-               goto alloc_error;
-       }
-
-       return out;
+       const struct riscv_dmi_backend_ops *backend = riscv_dmi_backend(target);
+       assert(backend);
+       assert(backend->batch_alloc);
 
-alloc_error:
-       riscv_batch_free(out);
-       return NULL;
+       return backend->batch_alloc(target, scans);
 }
 
 void riscv_batch_free(struct riscv_batch *batch)
-{
-       free(batch->data_in);
-       free(batch->data_out);
-       free(batch->fields);
-       free(batch->delay_classes);
-       free(batch->bscan_ctxt);
-       free(batch->read_keys);
-       free(batch);
-}
-
-bool riscv_batch_full(struct riscv_batch *batch)
-{
-       return riscv_batch_available_scans(batch) == 0;
-}
-
-static bool riscv_batch_was_scan_busy(const struct riscv_batch *batch,
-               size_t scan_idx)
-{
-       assert(batch->was_run);
-       assert(scan_idx < batch->used_scans);
-       const struct scan_field *field = batch->fields + scan_idx;
-       assert(field->in_value);
-       const uint64_t in = buf_get_u64(field->in_value, 0, field->num_bits);
-       return get_field(in, DTM_DMI_OP) == DTM_DMI_OP_BUSY;
-}
-
-static void add_idle_before_batch(const struct riscv_batch *batch, size_t 
start_idx,
-               const struct riscv_scan_delays *delays)
-{
-       if (!batch->was_run)
-               return;
-       /* Get the delay type of the scan that resulted in the busy response.
-        * Since DMI interactions always end with a NOP, if "start_idx" is zero
-        * the base delay value is used.
-        */
-       const enum riscv_scan_delay_class delay_class = start_idx > 0
-               ? batch->delay_classes[start_idx - 1]
-               : RISCV_DELAY_BASE;
-       const unsigned int new_delay = riscv_scan_get_delay(delays, 
delay_class);
-       if (new_delay <= batch->last_scan_delay)
-               return;
-       const unsigned int idle_change = new_delay - batch->last_scan_delay;
-       LOG_TARGET_DEBUG(batch->target, "Adding %u idle cycles before the 
batch.",
-                       idle_change);
-       jtag_add_runtest(idle_change, TAP_IDLE);
-}
-
-static unsigned int get_delay(const struct riscv_batch *batch, size_t scan_idx,
-               const struct riscv_scan_delays *delays, bool resets_delays,
-               size_t reset_delays_after)
 {
        assert(batch);
-       assert(scan_idx < batch->used_scans);
-       const bool delays_were_reset = resets_delays
-               && (scan_idx >= reset_delays_after);
-       const enum riscv_scan_delay_class delay_class =
-               batch->delay_classes[scan_idx];
-       const unsigned int delay = riscv_scan_get_delay(delays, delay_class);
-       return delays_were_reset ? 0 : delay;
-}
+       assert(batch->backend);
+       assert(batch->backend->batch_free);
 
-static unsigned int decode_dmi(const struct riscv_batch *batch, char *text,
-               uint32_t address, uint32_t data)
-{
-       static const struct {
-               uint32_t address;
-               enum riscv_debug_reg_ordinal ordinal;
-       } description[] = {
-               {DM_DMCONTROL, DM_DMCONTROL_ORDINAL},
-               {DM_DMSTATUS, DM_DMSTATUS_ORDINAL},
-               {DM_ABSTRACTCS, DM_ABSTRACTCS_ORDINAL},
-               {DM_COMMAND, DM_COMMAND_ORDINAL},
-               {DM_SBCS, DM_SBCS_ORDINAL}
-       };
-
-       for (unsigned int i = 0; i < ARRAY_SIZE(description); i++) {
-               if (riscv_get_dmi_address(batch->target, description[i].address)
-                               == address) {
-                       const struct riscv_debug_reg_ctx context = {
-                               .XLEN = { .value = 0, .is_set = false },
-                               .DXLEN = { .value = 0, .is_set = false },
-                               .abits = { .value = 0, .is_set = false },
-                       };
-                       return riscv_debug_reg_to_s(text, 
description[i].ordinal,
-                                       context, data, 
RISCV_DEBUG_REG_HIDE_ALL_0);
-               }
-       }
-       if (text)
-               text[0] = '\0';
-       return 0;
+       batch->backend->batch_free(batch);
 }
 
-static void log_dmi_decoded(const struct riscv_batch *batch, bool write,
-               uint32_t address, uint32_t data)
-{
-       const size_t size = decode_dmi(batch, /* text */ NULL, address, data) + 
1;
-       char * const decoded = malloc(size);
-       if (!decoded) {
-               LOG_ERROR("Not enough memory to allocate %zu bytes.", size);
-               return;
-       }
-       decode_dmi(batch, decoded, address, data);
-       LOG_DEBUG("%s: %s", write ? "write" : "read", decoded);
-       free(decoded);
-}
-
-static void log_batch(const struct riscv_batch *batch, size_t start_idx,
-               const struct riscv_scan_delays *delays, bool resets_delays,
-               size_t reset_delays_after)
+bool riscv_batch_full(struct riscv_batch *batch)
 {
-       if (!LOG_LEVEL_IS(LOG_LVL_DEBUG))
-               return;
-
-       const unsigned int abits = riscv_get_dmi_address_bits(batch->target);
-
-       /* Determine the "op" and "address" of the scan that preceded the first
-        * executed scan.
-        * FIXME: The code here assumes that there were no DMI operations 
between
-        * the last execution of the batch and the current one.
-        * Caching the info about the last executed DMI scan in "dm013_info_t"
-        * would be a more robust solution.
-        */
-       bool last_scan_was_read = false;
-       uint32_t last_scan_address = (uint32_t)(-1) /* to silence 
maybe-uninitialized */;
-       if (start_idx > 0) {
-               const struct scan_field * const field = 
&batch->fields[start_idx - 1];
-               assert(field->out_value);
-               last_scan_was_read = buf_get_u32(field->out_value, 
DTM_DMI_OP_OFFSET,
-                               DTM_DMI_OP_LENGTH) == DTM_DMI_OP_READ;
-               last_scan_address = buf_get_u32(field->out_value,
-                               DTM_DMI_ADDRESS_OFFSET, abits);
-       }
-
-       /* Decode and log every executed scan */
-       for (size_t i = start_idx; i < batch->used_scans; ++i) {
-               static const char * const op_string[] = {"-", "r", "w", "?"};
-               const unsigned int delay = get_delay(batch, i, delays, 
resets_delays,
-                               reset_delays_after);
-               const struct scan_field * const field = &batch->fields[i];
-
-               assert(field->out_value);
-               const unsigned int out_op = buf_get_u32(field->out_value,
-                               DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
-               const uint32_t out_data = buf_get_u32(field->out_value,
-                               DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH);
-               const uint32_t out_address = buf_get_u32(field->out_value,
-                               DTM_DMI_ADDRESS_OFFSET, abits);
-               if (field->in_value) {
-                       static const char * const status_string[] = {
-                               "+", "?", "F", "b"
-                       };
-                       const unsigned int in_op = buf_get_u32(field->in_value,
-                                       DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
-                       const uint32_t in_data = buf_get_u32(field->in_value,
-                                       DTM_DMI_DATA_OFFSET, 
DTM_DMI_DATA_LENGTH);
-                       const uint32_t in_address = buf_get_u32(field->in_value,
-                                       DTM_DMI_ADDRESS_OFFSET, abits);
-
-                       LOG_DEBUG("%db %s %08" PRIx32 " @%02" PRIx32
-                                       " -> %s %08" PRIx32 " @%02" PRIx32 "; 
%ui",
-                                       field->num_bits, op_string[out_op], 
out_data, out_address,
-                                       status_string[in_op], in_data, 
in_address, delay);
-
-                       if (last_scan_was_read && in_op == DTM_DMI_OP_SUCCESS)
-                               log_dmi_decoded(batch, /*write*/ false,
-                                               last_scan_address, in_data);
-               } else {
-                       LOG_DEBUG("%db %s %08" PRIx32 " @%02" PRIx32 " -> ?; 
%ui",
-                                       field->num_bits, op_string[out_op], 
out_data, out_address,
-                                       delay);
-               }
-
-               if (out_op == DTM_DMI_OP_WRITE)
-                       log_dmi_decoded(batch, /*write*/ true, out_address,
-                                       out_data);
-
-               last_scan_was_read = out_op == DTM_DMI_OP_READ;
-               last_scan_address = out_address;
-       }
+       return riscv_batch_available_scans(batch) == 0;
 }
 
 int riscv_batch_run_from(struct riscv_batch *batch, size_t start_idx,
                const struct riscv_scan_delays *delays, bool resets_delays,
                size_t reset_delays_after)
 {
-       assert(batch->used_scans);
-       assert(start_idx < batch->used_scans);
-       assert(batch->last_scan == RISCV_SCAN_TYPE_NOP);
-       assert(!batch->was_run || riscv_batch_was_scan_busy(batch, start_idx));
-       assert(start_idx == 0 || !riscv_batch_was_scan_busy(batch, start_idx - 
1));
-
-       if (batch->was_run)
-               add_idle_before_batch(batch, start_idx, delays);
-
-       LOG_TARGET_DEBUG(batch->target, "Running batch of scans [%zu, %zu)",
-                       start_idx, batch->used_scans);
-
-       unsigned int delay = 0 /* to silence maybe-uninitialized */;
-       for (size_t i = start_idx; i < batch->used_scans; ++i) {
-               if (bscan_tunnel_ir_width != 0)
-                       riscv_add_bscan_tunneled_scan(batch->target->tap, 
batch->fields + i,
-                                                       batch->bscan_ctxt + i);
-               else
-                       jtag_add_dr_scan(batch->target->tap, 1, batch->fields + 
i, TAP_IDLE);
-
-               delay = get_delay(batch, i, delays, resets_delays,
-                               reset_delays_after);
-               if (delay > 0)
-                       jtag_add_runtest(delay, TAP_IDLE);
-       }
-
-       keep_alive();
-
-       if (jtag_execute_queue() != ERROR_OK) {
-               LOG_TARGET_ERROR(batch->target, "Unable to execute JTAG queue");
-               return ERROR_FAIL;
-       }
-
-       keep_alive();
+       assert(batch);
+       assert(batch->backend);
+       assert(batch->backend->batch_run_from);
 
-       if (bscan_tunnel_ir_width != 0) {
-               /* need to right-shift "in" by one bit, because of clock skew 
between BSCAN TAP and DM TAP */
-               for (size_t i = start_idx; i < batch->used_scans; ++i) {
-                       if ((batch->fields + i)->in_value)
-                               buffer_shr((batch->fields + i)->in_value, 
DMI_SCAN_BUF_SIZE, 1);
-               }
-       }
+       int result = riscv_dmi_prepare_access(batch->target);
+       if (result != ERROR_OK)
+               return result;
 
-       log_batch(batch, start_idx, delays, resets_delays, reset_delays_after);
-       batch->was_run = true;
-       batch->last_scan_delay = delay;
-       return ERROR_OK;
+       return batch->backend->batch_run_from(batch, start_idx, delays,
+               resets_delays, reset_delays_after);
 }
 
-void riscv_batch_add_dmi_write(struct riscv_batch *batch, uint32_t address, 
uint32_t data,
-               bool read_back, enum riscv_scan_delay_class delay_class)
+void riscv_batch_add_dmi_write(struct riscv_batch *batch, uint32_t address,
+               uint32_t data, bool read_back,
+               enum riscv_scan_delay_class delay_class)
 {
-       // TODO: Check that the bit width of "address" is no more than 
dtmcs.abits,
-       // otherwise return an error (during batch creation or when the batch 
is executed).
-
-       assert(batch->used_scans < batch->allocated_scans);
-       struct scan_field *field = batch->fields + batch->used_scans;
-
-       field->num_bits = get_dmi_scan_length(batch->target);
-       assert(field->num_bits <= DMI_SCAN_MAX_BIT_LENGTH);
-
-       uint8_t *out_value = batch->data_out + batch->used_scans * 
DMI_SCAN_BUF_SIZE;
-       uint8_t *in_value = batch->data_in + batch->used_scans * 
DMI_SCAN_BUF_SIZE;
-
-       field->out_value = out_value;
-       riscv_fill_dmi_write(batch->target, out_value, address, data);
-
-       if (read_back) {
-               field->in_value = in_value;
-               riscv_fill_dm_nop(batch->target, in_value);
-       } else {
-               field->in_value = NULL;
-       }
+       assert(batch);
+       assert(batch->backend);
+       assert(batch->backend->batch_add_dmi_write);
 
-       batch->delay_classes[batch->used_scans] = delay_class;
-       batch->last_scan = RISCV_SCAN_TYPE_WRITE;
-       batch->used_scans++;
+       batch->backend->batch_add_dmi_write(batch, address, data, read_back,
+               delay_class);
 }
 
 size_t riscv_batch_add_dmi_read(struct riscv_batch *batch, uint32_t address,
                enum riscv_scan_delay_class delay_class)
 {
-       // TODO: Check that the bit width of "address" is no more than 
dtmcs.abits,
-       // otherwise return an error (during batch creation or when the batch 
is executed).
-
-       assert(batch->used_scans < batch->allocated_scans);
-       struct scan_field *field = batch->fields + batch->used_scans;
-
-       field->num_bits = get_dmi_scan_length(batch->target);
-       assert(field->num_bits <= DMI_SCAN_MAX_BIT_LENGTH);
-
-       uint8_t *out_value = batch->data_out + batch->used_scans * 
DMI_SCAN_BUF_SIZE;
-       uint8_t *in_value = batch->data_in + batch->used_scans * 
DMI_SCAN_BUF_SIZE;
-
-       field->out_value = out_value;
-       field->in_value = in_value;
-       riscv_fill_dmi_read(batch->target, out_value, address);
-       riscv_fill_dm_nop(batch->target, in_value);
-
-       batch->delay_classes[batch->used_scans] = delay_class;
-       batch->last_scan = RISCV_SCAN_TYPE_READ;
-       batch->used_scans++;
+       assert(batch);
+       assert(batch->backend);
+       assert(batch->backend->batch_add_dmi_read);
 
-       batch->read_keys[batch->read_keys_used] = batch->used_scans;
-       return batch->read_keys_used++;
+       return batch->backend->batch_add_dmi_read(batch, address, delay_class);
 }
 
 uint32_t riscv_batch_get_dmi_read_op(const struct riscv_batch *batch, size_t 
key)
 {
-       assert(key < batch->read_keys_used);
-       size_t index = batch->read_keys[key];
-       assert(index < batch->used_scans);
-       uint8_t *base = batch->data_in + DMI_SCAN_BUF_SIZE * index;
-       /* extract "op" field from the DMI read result */
-       return buf_get_u32(base, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
-}
+       assert(batch);
+       assert(batch->backend);
+       assert(batch->backend->batch_get_dmi_read_op);
 
-uint32_t riscv_batch_get_dmi_read_data(const struct riscv_batch *batch, size_t 
key)
-{
-       assert(key < batch->read_keys_used);
-       size_t index = batch->read_keys[key];
-       assert(index < batch->used_scans);
-       uint8_t *base = batch->data_in + DMI_SCAN_BUF_SIZE * index;
-       /* extract "data" field from the DMI read result */
-       return buf_get_u32(base, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH);
+       return batch->backend->batch_get_dmi_read_op(batch, key);
 }
 
-void riscv_batch_add_nop(struct riscv_batch *batch)
+uint32_t riscv_batch_get_dmi_read_data(const struct riscv_batch *batch,
+               size_t key)
 {
-       assert(batch->used_scans < batch->allocated_scans);
-       struct scan_field *field = batch->fields + batch->used_scans;
-
-       field->num_bits = get_dmi_scan_length(batch->target);
-       assert(field->num_bits <= DMI_SCAN_MAX_BIT_LENGTH);
-
-       uint8_t *out_value = batch->data_out + batch->used_scans * 
DMI_SCAN_BUF_SIZE;
-       uint8_t *in_value = batch->data_in + batch->used_scans * 
DMI_SCAN_BUF_SIZE;
-
-       field->out_value = out_value;
-       field->in_value = in_value;
-       riscv_fill_dm_nop(batch->target, out_value);
-       riscv_fill_dm_nop(batch->target, in_value);
+       assert(batch);
+       assert(batch->backend);
+       assert(batch->backend->batch_get_dmi_read_data);
 
-       /* DMI NOP never triggers any debug module operation,
-        * so the shortest (base) delay can be used. */
-       batch->delay_classes[batch->used_scans] = RISCV_DELAY_BASE;
-       batch->last_scan = RISCV_SCAN_TYPE_NOP;
-       batch->used_scans++;
+       return batch->backend->batch_get_dmi_read_data(batch, key);
 }
 
 size_t riscv_batch_available_scans(struct riscv_batch *batch)
 {
-       assert(batch->allocated_scans >= (batch->used_scans + 
BATCH_RESERVED_SCANS));
-       return batch->allocated_scans - batch->used_scans - 
BATCH_RESERVED_SCANS;
+       assert(batch);
+       assert(batch->backend);
+       assert(batch->backend->batch_available_scans);
+
+       return batch->backend->batch_available_scans(batch);
 }
 
 bool riscv_batch_was_batch_busy(const struct riscv_batch *batch)
 {
-       assert(batch->was_run);
-       assert(batch->used_scans);
-       assert(batch->last_scan == RISCV_SCAN_TYPE_NOP);
-       return riscv_batch_was_scan_busy(batch, batch->used_scans - 1);
+       assert(batch);
+       assert(batch->backend);
+       assert(batch->backend->batch_was_busy);
+
+       return batch->backend->batch_was_busy(batch);
 }
 
 size_t riscv_batch_finished_scans(const struct riscv_batch *batch)
 {
-       if (!riscv_batch_was_batch_busy(batch)) {
-               /* Whole batch succeeded. */
-               return batch->used_scans;
-       }
-       assert(batch->used_scans);
-       size_t first_busy = 0;
-       while (!riscv_batch_was_scan_busy(batch, first_busy))
-               ++first_busy;
-       return first_busy;
+       assert(batch);
+       assert(batch->backend);
+       assert(batch->backend->batch_finished_scans);
+
+       return batch->backend->batch_finished_scans(batch);
 }
diff --git a/src/target/riscv/batch.h b/src/target/riscv/batch.h
index 5d8b57234e..881a99b0d6 100644
--- a/src/target/riscv/batch.h
+++ b/src/target/riscv/batch.h
@@ -3,19 +3,21 @@
 #ifndef OPENOCD_TARGET_RISCV_BATCH_H
 #define OPENOCD_TARGET_RISCV_BATCH_H
 
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "helper/log.h"
 #include "target/target.h"
-#include "jtag/jtag.h"
 #include "riscv.h"
 
-enum riscv_scan_type {
-       RISCV_SCAN_TYPE_INVALID,
-       RISCV_SCAN_TYPE_NOP,
-       RISCV_SCAN_TYPE_READ,
-       RISCV_SCAN_TYPE_WRITE,
-};
+struct riscv_dmi_backend_ops;
 
-/* These types are used to specify how many JTAG RTI cycles to add after a
- * scan.
+/* These types are used to specify backend-specific idle delays after DMI
+ * accesses. JTAG uses them as RTI cycle classes; direct backends may ignore
+ * them when the transport has no idle-cycle concept.
  */
 enum riscv_scan_delay_class {
        /* Delay needed for accessing debug module registers: */
@@ -120,45 +122,13 @@ static inline int riscv_scan_increase_delay(struct 
riscv_scan_delays *delays,
        return ERROR_OK;
 }
 
-/* A batch of multiple JTAG scans, which are grouped together to avoid the
- * overhead of some JTAG adapters when sending single commands.  This is
- * designed to support block copies, as that's what we actually need to go
- * fast. */
+/* A batch of multiple DMI operations. Backend-specific batching state is
+ * private to the selected DTM backend.
+ */
 struct riscv_batch {
        struct target *target;
-
-       size_t allocated_scans;
-       size_t used_scans;
-
-       uint8_t *data_out;
-       uint8_t *data_in;
-       struct scan_field *fields;
-       enum riscv_scan_delay_class *delay_classes;
-
-       /* If in BSCAN mode, this field will be allocated (one per scan),
-          and utilized to tunnel all the scans in the batch.  If not in
-          BSCAN mode, this field is unallocated and stays NULL */
-       riscv_bscan_tunneled_scan_context_t *bscan_ctxt;
-
-       /* In JTAG we scan out the previous value's output when performing a
-        * scan.  This is a pain for users, so we just provide them the
-        * illusion of not having to do this by eliding all but the last NOP.
-        * */
-       enum riscv_scan_type last_scan;
-
-       /* The read keys. */
-       size_t *read_keys;
-       size_t read_keys_used;
-
-       /* Flag indicating that the last run of the batch finished without an 
error
-        * from the underlying JTAG layer of OpenOCD - all scans were performed.
-        * However, RISC-V DMI "busy" condition could still have occurred.
-        */
-       bool was_run;
-       /* Number of RTI cycles used by the last scan on the last run.
-        * Only valid when `was_run` is set.
-        */
-       unsigned int last_scan_delay;
+       const struct riscv_dmi_backend_ops *backend;
+       void *backend_priv;
 };
 
 /* Allocates (or frees) a new scan set.  "scans" is the maximum number of JTAG
@@ -169,16 +139,16 @@ void riscv_batch_free(struct riscv_batch *batch);
 /* Checks to see if this batch is full. */
 bool riscv_batch_full(struct riscv_batch *batch);
 
-/* Executes this batch of JTAG DTM DMI scans, starting form "start" scan.
+/* Executes this batch of DMI operations, starting from "start" operation.
  *
  * If batch is run for the first time, it is expected that "start" is zero.
- * It is expected that the batch ends with a DMI NOP operation.
  *
- * "idle_counts" specifies the number of JTAG Run-Test-Idle cycles to add
- * after each scan depending on the delay class of the scan.
+ * "idle_counts" specifies the number of idle cycles to add after each DMI
+ * operation depending on the delay class of the operation, if the backend
+ * uses idle cycles.
  *
  * If "resets_delays" is true, the algorithm will stop inserting idle cycles
- * (JTAG Run-Test-Idle) after "reset_delays_after" number of scans is
+ * after "reset_delays_after" number of operations is
  * performed.  This is useful for stress-testing of RISC-V algorithms in
  * OpenOCD that are based on batches.
  */
@@ -199,7 +169,7 @@ riscv_batch_add_dm_write(struct riscv_batch *batch, 
uint32_t address, uint32_t d
 {
        return riscv_batch_add_dmi_write(batch,
                        riscv_get_dmi_address(batch->target, address), data,
-                       read_back, delay_type);
+               read_back, delay_type);
 }
 
 /* DM register reads must be handled in two parts: the first one schedules a 
read and
@@ -219,9 +189,6 @@ riscv_batch_add_dm_read(struct riscv_batch *batch, uint32_t 
address,
 uint32_t riscv_batch_get_dmi_read_op(const struct riscv_batch *batch, size_t 
key);
 uint32_t riscv_batch_get_dmi_read_data(const struct riscv_batch *batch, size_t 
key);
 
-/* Scans in a NOP. */
-void riscv_batch_add_nop(struct riscv_batch *batch);
-
 /* Returns the number of available scans. */
 size_t riscv_batch_available_scans(struct riscv_batch *batch);
 
diff --git a/src/target/riscv/dmi.c b/src/target/riscv/dmi.c
new file mode 100644
index 0000000000..c561c8b7ae
--- /dev/null
+++ b/src/target/riscv/dmi.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dmi.h"
+#include "dtm.h"
+#include "riscv.h"
+
+#include "helper/log.h"
+
+#include <assert.h>
+#include <string.h>
+
+const struct riscv_dmi_backend_ops *riscv_dmi_backend(struct target *target)
+{
+       RISCV_INFO(r);
+
+       return r->dtm ? r->dtm->backend : NULL;
+}
+
+int riscv_dmi_select(struct target *target)
+{
+       RISCV_INFO(r);
+
+       if (!r->dtm && riscv_dtm_assign_implicit(target) != ERROR_OK)
+               return ERROR_FAIL;
+
+       if (!r->dtm)
+               return ERROR_FAIL;
+
+       return riscv_dtm_init(r->dtm);
+}
+
+int riscv_dmi_get_info(struct target *target, struct riscv_dmi_info *info)
+{
+       assert(info);
+       memset(info, 0, sizeof(*info));
+
+       const struct riscv_dmi_backend_ops *backend = riscv_dmi_backend(target);
+       if (!backend || !backend->get_info) {
+               LOG_TARGET_ERROR(target, "RISC-V DMI backend is not selected.");
+               return ERROR_FAIL;
+       }
+
+       int result = backend->get_info(target, info);
+       if (result == ERROR_OK) {
+               RISCV_INFO(r);
+               riscv_dtm_update_info(r->dtm, info->dtm_version, info->abits,
+                               info->idle, info->has_dtmcs);
+       }
+
+       return result;
+}
+
+int riscv_dmi_reset(struct target *target)
+{
+       const struct riscv_dmi_backend_ops *backend = riscv_dmi_backend(target);
+       if (!backend || !backend->reset)
+               return ERROR_FAIL;
+
+       return backend->reset(target);
+}
+
+int riscv_dmi_prepare_access(struct target *target)
+{
+       const struct riscv_dmi_backend_ops *backend = riscv_dmi_backend(target);
+       if (!backend || !backend->prepare_access)
+               return ERROR_FAIL;
+
+       return backend->prepare_access(target);
+}
diff --git a/src/target/riscv/dmi.h b/src/target/riscv/dmi.h
new file mode 100644
index 0000000000..6f8b910e1f
--- /dev/null
+++ b/src/target/riscv/dmi.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef OPENOCD_TARGET_RISCV_DMI_H
+#define OPENOCD_TARGET_RISCV_DMI_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "batch.h"
+
+struct target;
+struct riscv_dtm;
+struct jtag_tap;
+
+struct riscv_dmi_info {
+       unsigned int dtm_version;
+       unsigned int abits;
+       unsigned int idle;
+       bool has_dtmcs;
+};
+
+struct riscv_dmi_backend_ops {
+       const char *name;
+       int (*get_info)(struct target *target, struct riscv_dmi_info *info);
+       int (*reset)(struct target *target);
+       int (*prepare_access)(struct target *target);
+
+       struct riscv_batch *(*batch_alloc)(struct target *target, size_t scans);
+       void (*batch_free)(struct riscv_batch *batch);
+       int (*batch_run_from)(struct riscv_batch *batch, size_t start_idx,
+               const struct riscv_scan_delays *delays, bool resets_delays,
+               size_t reset_delays_after);
+       void (*batch_add_dmi_write)(struct riscv_batch *batch, uint32_t address,
+               uint32_t data, bool read_back, enum riscv_scan_delay_class 
delay_class);
+       size_t (*batch_add_dmi_read)(struct riscv_batch *batch, uint32_t 
address,
+               enum riscv_scan_delay_class delay_class);
+       uint32_t (*batch_get_dmi_read_op)(const struct riscv_batch *batch, 
size_t key);
+       uint32_t (*batch_get_dmi_read_data)(const struct riscv_batch *batch, 
size_t key);
+       size_t (*batch_available_scans)(struct riscv_batch *batch);
+       bool (*batch_was_busy)(const struct riscv_batch *batch);
+       size_t (*batch_finished_scans)(const struct riscv_batch *batch);
+};
+
+extern const struct riscv_dmi_backend_ops riscv_dmi_jtag_backend;
+extern const struct riscv_dmi_backend_ops riscv_dmi_ap_backend;
+
+int riscv_dmi_select(struct target *target);
+int riscv_dmi_get_info(struct target *target, struct riscv_dmi_info *info);
+int riscv_dmi_reset(struct target *target);
+int riscv_dmi_prepare_access(struct target *target);
+const struct riscv_dmi_backend_ops *riscv_dmi_backend(struct target *target);
+
+int riscv_dmi_jtag_set_ir(const char *ir_name, uint32_t value);
+int riscv_dmi_jtag_use_bscan_tunnel(uint8_t ir_width, int tunnel_type);
+int riscv_dmi_jtag_set_bscan_tunnel_ir(int ir_id);
+int riscv_dmi_jtag_init_tap(struct jtag_tap *tap);
+
+#endif /* OPENOCD_TARGET_RISCV_DMI_H */
diff --git a/src/target/riscv/dtm.c b/src/target/riscv/dtm.c
new file mode 100644
index 0000000000..da7b231a6e
--- /dev/null
+++ b/src/target/riscv/dtm.c
@@ -0,0 +1,683 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dtm.h"
+#include "dmi.h"
+#include "riscv.h"
+
+#include "helper/list.h"
+#include "helper/log.h"
+#include "target/arm_adi_v5.h"
+#include "transport/transport.h"
+
+#include <inttypes.h>
+#include <string.h>
+
+struct riscv_dtm_object {
+       struct list_head lh;
+       struct riscv_dtm dtm;
+       char *name;
+       bool implicit;
+};
+
+static OOCD_LIST_HEAD(all_dtm);
+static unsigned int implicit_dtm_count;
+
+static const struct command_registration dtm_instance_commands[];
+
+static const struct {
+       const char *name;
+       enum riscv_dtm_type type;
+} dtm_type_names[] = {
+       { "jtag", RISCV_DTM_TYPE_JTAG },
+       { "ap", RISCV_DTM_TYPE_AP },
+};
+
+static const char *riscv_dtm_type_name(enum riscv_dtm_type type)
+{
+       for (unsigned int i = 0; i < ARRAY_SIZE(dtm_type_names); i++) {
+               if (dtm_type_names[i].type == type)
+                       return dtm_type_names[i].name;
+       }
+       return "unknown";
+}
+
+static enum riscv_dtm_type riscv_dtm_type_by_name(const char *name)
+{
+       for (unsigned int i = 0; i < ARRAY_SIZE(dtm_type_names); i++) {
+               if (!strcmp(dtm_type_names[i].name, name))
+                       return dtm_type_names[i].type;
+       }
+       return RISCV_DTM_TYPE_UNKNOWN;
+}
+
+const char *riscv_dtm_name(const struct riscv_dtm *dtm)
+{
+       return dtm && dtm->name ? dtm->name : "<unnamed-dtm>";
+}
+
+struct riscv_dtm *riscv_dtm_by_name(const char *name)
+{
+       struct riscv_dtm_object *obj;
+
+       list_for_each_entry(obj, &all_dtm, lh) {
+               if (!strcmp(name, obj->name))
+                       return &obj->dtm;
+       }
+
+       return NULL;
+}
+
+struct riscv_dtm *riscv_dtm_by_jim_obj(Jim_Interp *interp, Jim_Obj *obj)
+{
+       return riscv_dtm_by_name(Jim_GetString(obj, NULL));
+}
+
+static int riscv_dtm_set_backend(struct riscv_dtm *dtm)
+{
+       switch (dtm->type) {
+       case RISCV_DTM_TYPE_JTAG:
+               dtm->backend = &riscv_dmi_jtag_backend;
+               return ERROR_OK;
+       case RISCV_DTM_TYPE_AP:
+               dtm->backend = &riscv_dmi_ap_backend;
+               return ERROR_OK;
+       default:
+               LOG_ERROR("Unsupported RISC-V DTM type '%s'.",
+                               riscv_dtm_type_name(dtm->type));
+               return ERROR_FAIL;
+       }
+}
+
+int riscv_dtm_init(struct riscv_dtm *dtm)
+{
+       if (!dtm)
+               return ERROR_FAIL;
+       if (dtm->initialized)
+               return ERROR_OK;
+
+       int result = riscv_dtm_set_backend(dtm);
+       if (result != ERROR_OK)
+               return result;
+
+       if (dtm->type == RISCV_DTM_TYPE_JTAG && !dtm->tap) {
+               LOG_ERROR("RISC-V DTM '%s' has no JTAG TAP.", 
riscv_dtm_name(dtm));
+               return ERROR_FAIL;
+       }
+       if (dtm->type == RISCV_DTM_TYPE_JTAG) {
+               result = riscv_dmi_jtag_init_tap(dtm->tap);
+               if (result != ERROR_OK)
+                       return result;
+       }
+       if (dtm->type == RISCV_DTM_TYPE_AP) {
+               struct riscv_dmi_ap_config *config = dtm->backend_priv;
+               if (!config || !config->dap || config->ap_num == 
DP_APSEL_INVALID) {
+                       LOG_ERROR("RISC-V DTM '%s' needs -dap and -ap-num.",
+                                       riscv_dtm_name(dtm));
+                       return ERROR_FAIL;
+               }
+               if (!config->ap) {
+                       config->ap = dap_get_ap(config->dap, config->ap_num);
+                       if (!config->ap) {
+                               LOG_ERROR("Cannot use DAP '%s' AP 0x%08" PRIx64
+                                               " for RISC-V DMI access.",
+                                               adiv5_dap_name(config->dap), 
config->ap_num);
+                               return ERROR_FAIL;
+                       }
+               }
+       }
+
+       dtm->initialized = true;
+       return ERROR_OK;
+}
+
+int riscv_dtm_init_all(void)
+{
+       struct riscv_dtm_object *obj;
+
+       list_for_each_entry(obj, &all_dtm, lh) {
+               int result = riscv_dtm_init(&obj->dtm);
+               if (result != ERROR_OK)
+                       return result;
+       }
+
+       return ERROR_OK;
+}
+
+int riscv_dtm_assign_target(struct target *target, struct riscv_dtm *dtm)
+{
+       if (!target || !dtm)
+               return ERROR_FAIL;
+
+       struct riscv_private_config *config = target->private_config;
+       if (!config) {
+               config = calloc(1, sizeof(*config));
+               if (!config)
+                       return ERROR_FAIL;
+               target->private_config = config;
+       }
+
+       config->dtm = dtm;
+       target->has_dtm = true;
+       target->dtm_configured = true;
+       if (target->arch_info) {
+               RISCV_INFO(r);
+               r->dtm = dtm;
+       }
+
+       if (dtm->type == RISCV_DTM_TYPE_JTAG && dtm->tap) {
+               target->tap = dtm->tap;
+               target->tap_configured = true;
+       }
+
+       return ERROR_OK;
+}
+
+static struct riscv_dtm *riscv_dtm_find_implicit_jtag(struct jtag_tap *tap)
+{
+       struct riscv_dtm_object *obj;
+
+       list_for_each_entry(obj, &all_dtm, lh) {
+               if (obj->implicit && obj->dtm.type == RISCV_DTM_TYPE_JTAG
+                               && obj->dtm.tap == tap)
+                       return &obj->dtm;
+       }
+
+       return NULL;
+}
+
+static struct riscv_dtm *riscv_dtm_find_implicit_ap(struct adiv5_dap *dap,
+               uint64_t ap_num)
+{
+       struct riscv_dtm_object *obj;
+
+       list_for_each_entry(obj, &all_dtm, lh) {
+               struct riscv_dmi_ap_config *config = obj->dtm.backend_priv;
+               if (obj->implicit && obj->dtm.type == RISCV_DTM_TYPE_AP
+                               && config && config->dap == dap && 
config->ap_num == ap_num)
+                       return &obj->dtm;
+       }
+
+       return NULL;
+}
+
+static struct riscv_dtm *riscv_dtm_create_object(const char *name,
+               enum riscv_dtm_type type, struct jtag_tap *tap, bool implicit)
+{
+       struct riscv_dtm_object *obj = calloc(1, sizeof(*obj));
+       if (!obj)
+               return NULL;
+
+       obj->name = strdup(name);
+       if (!obj->name) {
+               free(obj);
+               return NULL;
+       }
+
+       obj->implicit = implicit;
+       obj->dtm.name = obj->name;
+       obj->dtm.type = type;
+       obj->dtm.tap = tap;
+       obj->dtm.reset_delays_wait = -1;
+       if (riscv_dtm_set_backend(&obj->dtm) != ERROR_OK) {
+               free(obj->name);
+               free(obj);
+               return NULL;
+       }
+
+       list_add_tail(&obj->lh, &all_dtm);
+       return &obj->dtm;
+}
+
+static struct riscv_dtm_object *riscv_dtm_object_by_dtm(struct riscv_dtm *dtm)
+{
+       return container_of(dtm, struct riscv_dtm_object, dtm);
+}
+
+static void riscv_dtm_destroy_object(struct riscv_dtm_object *obj)
+{
+       struct riscv_dmi_ap_config *config = obj->dtm.backend_priv;
+       if (config) {
+               if (config->ap)
+                       dap_put_ap(config->ap);
+               free(config);
+       }
+       list_del(&obj->lh);
+       free(obj->name);
+       free(obj);
+}
+
+int riscv_dtm_configure_ap(struct riscv_dtm *dtm, struct adiv5_dap *dap,
+               uint64_t ap_num)
+{
+       if (!dtm || !dap || ap_num == DP_APSEL_INVALID)
+               return ERROR_FAIL;
+
+       struct riscv_dmi_ap_config *config = dtm->backend_priv;
+       if (!config) {
+               config = calloc(1, sizeof(*config));
+               if (!config)
+                       return ERROR_FAIL;
+               config->ap_num = DP_APSEL_INVALID;
+               dtm->backend_priv = config;
+       }
+
+       if (config->ap && (config->dap != dap || config->ap_num != ap_num)) {
+               dap_put_ap(config->ap);
+               config->ap = NULL;
+       }
+       config->dap = dap;
+       config->ap_num = ap_num;
+       dtm->initialized = false;
+       return ERROR_OK;
+}
+
+int riscv_dtm_assign_implicit(struct target *target)
+{
+       if (!target)
+               return ERROR_FAIL;
+
+       struct riscv_private_config *config = target->private_config;
+       if (config && config->dtm)
+               return riscv_dtm_assign_target(target, config->dtm);
+
+       struct riscv_dtm *dtm = NULL;
+       enum riscv_dtm_type type = RISCV_DTM_TYPE_UNKNOWN;
+       if (transport_is_jtag()) {
+               type = RISCV_DTM_TYPE_JTAG;
+               dtm = riscv_dtm_find_implicit_jtag(target->tap);
+       } else {
+               LOG_TARGET_ERROR(target, "Unsupported RISC-V DTM transport 
'%s'.",
+                               get_current_transport_name());
+               return ERROR_FAIL;
+       }
+
+       if (!dtm) {
+               char name[64];
+               snprintf(name, sizeof(name), "__implicit_riscv_dtm_%u",
+                               implicit_dtm_count++);
+               dtm = riscv_dtm_create_object(name, type, target->tap, true);
+               if (!dtm)
+                       return ERROR_FAIL;
+       }
+
+       return riscv_dtm_assign_target(target, dtm);
+}
+
+int riscv_dtm_assign_ap_target(struct target *target, struct adiv5_dap *dap,
+               uint64_t ap_num)
+{
+       if (!target || !dap || ap_num == DP_APSEL_INVALID)
+               return ERROR_FAIL;
+
+       struct riscv_dtm *dtm = riscv_dtm_find_implicit_ap(dap, ap_num);
+       if (!dtm) {
+               char name[64];
+               snprintf(name, sizeof(name), "__implicit_riscv_ap_dtm_%u",
+                               implicit_dtm_count++);
+               dtm = riscv_dtm_create_object(name, RISCV_DTM_TYPE_AP, NULL, 
true);
+               if (!dtm)
+                       return ERROR_FAIL;
+               if (riscv_dtm_configure_ap(dtm, dap, ap_num) != ERROR_OK) {
+                       riscv_dtm_destroy_object(riscv_dtm_object_by_dtm(dtm));
+                       return ERROR_FAIL;
+               }
+       }
+
+       return riscv_dtm_assign_target(target, dtm);
+}
+
+void riscv_dtm_update_info(struct riscv_dtm *dtm,
+               unsigned int dtm_version, unsigned int abits, unsigned int idle,
+               bool has_dtmcs)
+{
+       if (!dtm)
+               return;
+       dtm->dtm_version = dtm_version;
+       dtm->abits = abits;
+       dtm->idle = idle;
+       dtm->has_dtmcs = has_dtmcs;
+}
+
+void riscv_dtm_reset_delays(struct riscv_dtm *dtm, int wait)
+{
+       if (dtm)
+               dtm->reset_delays_wait = wait;
+}
+
+void riscv_dtm_cleanup_all(void)
+{
+       struct riscv_dtm_object *obj, *tmp;
+
+       list_for_each_entry_safe(obj, tmp, &all_dtm, lh) {
+               riscv_dtm_destroy_object(obj);
+       }
+}
+
+static int dtm_parse_options(struct command_invocation *cmd,
+               struct riscv_dtm *dtm, unsigned int start_arg)
+{
+       for (unsigned int i = start_arg; i < CMD_ARGC; ) {
+               if (!strcmp(CMD_ARGV[i], "-type")) {
+                       if (++i == CMD_ARGC)
+                               return ERROR_COMMAND_SYNTAX_ERROR;
+                       enum riscv_dtm_type type = 
riscv_dtm_type_by_name(CMD_ARGV[i++]);
+                       if (type == RISCV_DTM_TYPE_UNKNOWN) {
+                               command_print(cmd, "Unknown DTM type '%s'", 
CMD_ARGV[i - 1]);
+                               return ERROR_COMMAND_ARGUMENT_INVALID;
+                       }
+                       dtm->type = type;
+               } else if (!strcmp(CMD_ARGV[i], "-chain-position")) {
+                       if (++i == CMD_ARGC)
+                               return ERROR_COMMAND_SYNTAX_ERROR;
+                       dtm->tap = jtag_tap_by_string(CMD_ARGV[i]);
+                       if (!dtm->tap) {
+                               command_print(cmd, "Tap '%s' could not be 
found", CMD_ARGV[i]);
+                               return ERROR_COMMAND_ARGUMENT_INVALID;
+                       }
+                       i++;
+               } else if (!strcmp(CMD_ARGV[i], "-dap")) {
+                       if (++i == CMD_ARGC)
+                               return ERROR_COMMAND_SYNTAX_ERROR;
+                       struct adiv5_dap *dap = 
dap_instance_by_jim_obj(cmd->ctx->interp,
+                                       CMD_JIMTCL_ARGV[i]);
+                       if (!dap) {
+                               command_print(cmd, "DAP '%s' could not be 
found", CMD_ARGV[i]);
+                               return ERROR_COMMAND_ARGUMENT_INVALID;
+                       }
+                       struct riscv_dmi_ap_config *config = dtm->backend_priv;
+                       if (!config) {
+                               config = calloc(1, sizeof(*config));
+                               if (!config)
+                                       return ERROR_FAIL;
+                               config->ap_num = DP_APSEL_INVALID;
+                               dtm->backend_priv = config;
+                       }
+                       if (config->ap && config->dap != dap) {
+                               dap_put_ap(config->ap);
+                               config->ap = NULL;
+                       }
+                       config->dap = dap;
+                       i++;
+               } else if (!strcmp(CMD_ARGV[i], "-ap-num")) {
+                       if (++i == CMD_ARGC)
+                               return ERROR_COMMAND_SYNTAX_ERROR;
+                       jim_wide ap_num;
+                       int e = Jim_GetWide(cmd->ctx->interp, 
CMD_JIMTCL_ARGV[i], &ap_num);
+                       if (e != JIM_OK)
+                               return ERROR_COMMAND_ARGUMENT_INVALID;
+                       if (ap_num < 0 || (ap_num > DP_APSEL_MAX && (ap_num & 
0xfff))) {
+                               command_print(cmd, "Invalid AP number");
+                               return ERROR_COMMAND_ARGUMENT_INVALID;
+                       }
+                       struct riscv_dmi_ap_config *config = dtm->backend_priv;
+                       if (!config) {
+                               config = calloc(1, sizeof(*config));
+                               if (!config)
+                                       return ERROR_FAIL;
+                               dtm->backend_priv = config;
+                       }
+                       if (config->ap && config->ap_num != (uint64_t)ap_num) {
+                               dap_put_ap(config->ap);
+                               config->ap = NULL;
+                       }
+                       config->ap_num = ap_num;
+                       i++;
+               } else {
+                       command_print(cmd, "Unknown DTM option '%s'", 
CMD_ARGV[i]);
+                       return ERROR_COMMAND_ARGUMENT_INVALID;
+               }
+       }
+
+       if (dtm->type == RISCV_DTM_TYPE_JTAG && !dtm->tap) {
+               command_print(cmd, "-chain-position is required for JTAG DTM");
+               return ERROR_COMMAND_ARGUMENT_INVALID;
+       }
+       if (dtm->type == RISCV_DTM_TYPE_AP) {
+               struct riscv_dmi_ap_config *config = dtm->backend_priv;
+               if (!config || !config->dap || config->ap_num == 
DP_APSEL_INVALID) {
+                       command_print(cmd, "-dap and -ap-num are required for 
AP DTM");
+                       return ERROR_COMMAND_ARGUMENT_INVALID;
+               }
+       }
+
+       return riscv_dtm_set_backend(dtm);
+}
+
+COMMAND_HANDLER(handle_dtm_create)
+{
+       if (CMD_ARGC < 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       if (riscv_dtm_by_name(CMD_ARGV[0])) {
+               command_print(CMD, "DTM '%s' already exists", CMD_ARGV[0]);
+               return ERROR_COMMAND_ARGUMENT_INVALID;
+       }
+
+       if (Jim_GetCommand(CMD_CTX->interp, CMD_JIMTCL_ARGV[0], JIM_NONE)) {
+               command_print(CMD, "Command '%s' already exists", CMD_ARGV[0]);
+               return ERROR_COMMAND_ARGUMENT_INVALID;
+       }
+
+       struct riscv_dtm *dtm = riscv_dtm_create_object(CMD_ARGV[0],
+                       RISCV_DTM_TYPE_JTAG, NULL, false);
+       if (!dtm)
+               return ERROR_FAIL;
+
+       int result = dtm_parse_options(CMD, dtm, 1);
+       if (result != ERROR_OK) {
+               riscv_dtm_destroy_object(riscv_dtm_object_by_dtm(dtm));
+               return result;
+       }
+
+       struct riscv_dtm_object *obj = riscv_dtm_object_by_dtm(dtm);
+       const struct command_registration instance_commands[] = {
+               {
+                       .name = CMD_ARGV[0],
+                       .mode = COMMAND_ANY,
+                       .usage = "",
+                       .help = "RISC-V DTM instance command group",
+                       .chain = dtm_instance_commands,
+               },
+               COMMAND_REGISTRATION_DONE
+       };
+
+       return register_commands_with_data(CMD_CTX, NULL, instance_commands, 
obj);
+}
+
+COMMAND_HANDLER(handle_dtm_names)
+{
+       struct riscv_dtm_object *obj;
+
+       list_for_each_entry(obj, &all_dtm, lh)
+               command_print(CMD, "%s", obj->name);
+
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(handle_dtm_init)
+{
+       return riscv_dtm_init_all();
+}
+
+COMMAND_HANDLER(handle_dtm_info)
+{
+       struct riscv_dtm *dtm;
+       if (CMD_DATA) {
+               if (CMD_ARGC != 0)
+                       return ERROR_COMMAND_SYNTAX_ERROR;
+               dtm = &((struct riscv_dtm_object *)CMD_DATA)->dtm;
+       } else {
+               if (CMD_ARGC != 1)
+                       return ERROR_COMMAND_SYNTAX_ERROR;
+
+               dtm = riscv_dtm_by_name(CMD_ARGV[0]);
+               if (!dtm) {
+                       command_print(CMD, "DTM '%s' not found", CMD_ARGV[0]);
+                       return ERROR_COMMAND_ARGUMENT_INVALID;
+               }
+       }
+
+       command_print(CMD, "name %s", riscv_dtm_name(dtm));
+       command_print(CMD, "type %s", riscv_dtm_type_name(dtm->type));
+       if (dtm->type == RISCV_DTM_TYPE_AP) {
+               struct riscv_dmi_ap_config *config = dtm->backend_priv;
+               command_print(CMD, "dap %s", config && config->dap
+                               ? adiv5_dap_name(config->dap) : "<none>");
+               command_print(CMD, "ap-num 0x%08" PRIx64,
+                               config ? config->ap_num : 
(uint64_t)DP_APSEL_INVALID);
+       }
+       command_print(CMD, "initialized %u", dtm->initialized ? 1 : 0);
+       command_print(CMD, "version %u", dtm->dtm_version);
+       command_print(CMD, "abits %u", dtm->abits);
+       command_print(CMD, "idle %u", dtm->idle);
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(handle_dtm_configure)
+{
+       if (!CMD_DATA)
+               return ERROR_FAIL;
+
+       struct riscv_dtm *dtm = &((struct riscv_dtm_object *)CMD_DATA)->dtm;
+       const bool configure = !strcmp(CMD_NAME, "configure");
+       if (!configure) {
+               if (CMD_ARGC != 1)
+                       return ERROR_COMMAND_SYNTAX_ERROR;
+               if (!strcmp(CMD_ARGV[0], "-type")) {
+                       command_print(CMD, "%s", 
riscv_dtm_type_name(dtm->type));
+               } else if (!strcmp(CMD_ARGV[0], "-chain-position") && dtm->tap) 
{
+                       command_print(CMD, "%s", dtm->tap->dotted_name);
+               } else if (!strcmp(CMD_ARGV[0], "-dap") && dtm->type == 
RISCV_DTM_TYPE_AP) {
+                       struct riscv_dmi_ap_config *config = dtm->backend_priv;
+                       if (config && config->dap)
+                               command_print(CMD, "%s", 
adiv5_dap_name(config->dap));
+               } else if (!strcmp(CMD_ARGV[0], "-ap-num") && dtm->type == 
RISCV_DTM_TYPE_AP) {
+                       struct riscv_dmi_ap_config *config = dtm->backend_priv;
+                       if (config)
+                               command_print(CMD, "0x%08" PRIx64, 
config->ap_num);
+               } else {
+                       return ERROR_COMMAND_ARGUMENT_INVALID;
+               }
+               return ERROR_OK;
+       }
+
+       int result = dtm_parse_options(CMD, dtm, 0);
+       if (result == ERROR_OK)
+               dtm->initialized = false;
+       return result;
+}
+
+COMMAND_HANDLER(handle_dtm_reset_delays)
+{
+       if (!CMD_DATA)
+               return ERROR_FAIL;
+
+       if (CMD_ARGC > 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       int wait = 0;
+       if (CMD_ARGC == 1)
+               COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], wait);
+
+       riscv_dtm_reset_delays(&((struct riscv_dtm_object *)CMD_DATA)->dtm, 
wait);
+       return ERROR_OK;
+}
+
+static const struct command_registration dtm_instance_commands[] = {
+       {
+               .name = "configure",
+               .handler = handle_dtm_configure,
+               .mode = COMMAND_ANY,
+               .usage = "[-type jtag|ap] [-chain-position tap] [-dap dap] 
[-ap-num num]",
+               .help = "Configure a RISC-V DTM instance.",
+       },
+       {
+               .name = "cget",
+               .handler = handle_dtm_configure,
+               .mode = COMMAND_ANY,
+               .usage = "-type|-chain-position|-dap|-ap-num",
+               .help = "Read a RISC-V DTM instance option.",
+       },
+       {
+               .name = "info",
+               .handler = handle_dtm_info,
+               .mode = COMMAND_ANY,
+               .usage = "",
+               .help = "Show RISC-V DTM information.",
+       },
+       {
+               .name = "reset_delays",
+               .handler = handle_dtm_reset_delays,
+               .mode = COMMAND_ANY,
+               .usage = "[wait]",
+               .help = "Reset learned RISC-V DMI delays for this DTM.",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+int riscv_dtm_set_ir(const char *ir_name, uint32_t value)
+{
+       return riscv_dmi_jtag_set_ir(ir_name, value);
+}
+
+int riscv_dtm_use_bscan_tunnel(uint8_t ir_width, int tunnel_type)
+{
+       return riscv_dmi_jtag_use_bscan_tunnel(ir_width, tunnel_type);
+}
+
+int riscv_dtm_set_bscan_tunnel_ir(int ir_id)
+{
+       return riscv_dmi_jtag_set_bscan_tunnel_ir(ir_id);
+}
+
+static const struct command_registration dtm_subcommand_handlers[] = {
+       {
+               .name = "create",
+               .handler = handle_dtm_create,
+               .mode = COMMAND_CONFIG,
+               .usage = "name [-type jtag|ap] [-chain-position tap] [-dap dap] 
[-ap-num num]",
+               .help = "Create a named RISC-V DTM instance.",
+       },
+       {
+               .name = "names",
+               .handler = handle_dtm_names,
+               .mode = COMMAND_ANY,
+               .usage = "",
+               .help = "List RISC-V DTM instances.",
+       },
+       {
+               .name = "init",
+               .handler = handle_dtm_init,
+               .mode = COMMAND_ANY,
+               .usage = "",
+               .help = "Initialize RISC-V DTM instances.",
+       },
+       {
+               .name = "info",
+               .handler = handle_dtm_info,
+               .mode = COMMAND_ANY,
+               .usage = "name",
+               .help = "Show RISC-V DTM information.",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration dtm_commands[] = {
+       {
+               .name = "dtm",
+               .mode = COMMAND_ANY,
+               .usage = "",
+               .help = "RISC-V DTM command group",
+               .chain = dtm_subcommand_handlers,
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+int riscv_dtm_register_commands(struct command_context *cmd_ctx)
+{
+       return register_commands(cmd_ctx, NULL, dtm_commands);
+}
diff --git a/src/target/riscv/dtm.h b/src/target/riscv/dtm.h
new file mode 100644
index 0000000000..5f913d00eb
--- /dev/null
+++ b/src/target/riscv/dtm.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef OPENOCD_TARGET_RISCV_DTM_H
+#define OPENOCD_TARGET_RISCV_DTM_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "jtag/jtag.h"
+#include "helper/command.h"
+
+struct target;
+struct riscv_dmi_backend_ops;
+struct adiv5_ap;
+struct adiv5_dap;
+
+enum riscv_dtm_type {
+       RISCV_DTM_TYPE_JTAG,
+       RISCV_DTM_TYPE_AP,
+       RISCV_DTM_TYPE_UNKNOWN,
+};
+
+struct riscv_dmi_ap_config {
+       struct adiv5_dap *dap;
+       uint64_t ap_num;
+       struct adiv5_ap *ap;
+};
+
+struct riscv_dtm {
+       const char *name;
+       enum riscv_dtm_type type;
+       const struct riscv_dmi_backend_ops *backend;
+       void *backend_priv;
+       struct jtag_tap *tap;
+       bool initialized;
+
+       unsigned int dtm_version;
+       unsigned int abits;
+       unsigned int idle;
+       bool has_dtmcs;
+       int reset_delays_wait;
+};
+
+const char *riscv_dtm_name(const struct riscv_dtm *dtm);
+struct riscv_dtm *riscv_dtm_by_name(const char *name);
+struct riscv_dtm *riscv_dtm_by_jim_obj(Jim_Interp *interp, Jim_Obj *obj);
+int riscv_dtm_init(struct riscv_dtm *dtm);
+int riscv_dtm_init_all(void);
+int riscv_dtm_assign_target(struct target *target, struct riscv_dtm *dtm);
+int riscv_dtm_assign_ap_target(struct target *target, struct adiv5_dap *dap,
+               uint64_t ap_num);
+int riscv_dtm_assign_implicit(struct target *target);
+void riscv_dtm_update_info(struct riscv_dtm *dtm,
+               unsigned int dtm_version, unsigned int abits, unsigned int idle,
+               bool has_dtmcs);
+
+int riscv_dtm_register_commands(struct command_context *cmd_ctx);
+void riscv_dtm_cleanup_all(void);
+
+int riscv_dtm_set_ir(const char *ir_name, uint32_t value);
+int riscv_dtm_use_bscan_tunnel(uint8_t ir_width, int tunnel_type);
+int riscv_dtm_set_bscan_tunnel_ir(int ir_id);
+void riscv_dtm_reset_delays(struct riscv_dtm *dtm, int wait);
+int riscv_dtm_configure_ap(struct riscv_dtm *dtm, struct adiv5_dap *dap,
+                       uint64_t ap_num);
+
+#endif /* OPENOCD_TARGET_RISCV_DTM_H */
diff --git a/src/target/riscv/dtm/adi_ap.c b/src/target/riscv/dtm/adi_ap.c
new file mode 100644
index 0000000000..f2f4185c46
--- /dev/null
+++ b/src/target/riscv/dtm/adi_ap.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "internal.h"
+
+#include "target/riscv/debug_defines.h"
+#include "target/riscv/riscv.h"
+
+#include "helper/log.h"
+#include "target/arm_adi_v5.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+struct riscv_batch *riscv_dmi_batch_alloc_common(struct target *target,
+               size_t scans, const struct riscv_dmi_backend_ops *backend)
+{
+       struct riscv_batch *batch = calloc(1, sizeof(*batch));
+       if (!batch)
+               return NULL;
+
+       batch->target = target;
+       batch->backend = backend;
+       return batch;
+}
+
+void riscv_dmi_batch_free_common(struct riscv_batch *batch)
+{
+       free(batch);
+}
+
+struct riscv_batch *riscv_dmi_direct_batch_alloc(struct target *target,
+               size_t scans, const struct riscv_dmi_backend_ops *backend)
+{
+       struct riscv_batch *batch = riscv_dmi_batch_alloc_common(target, scans,
+                       backend);
+       if (!batch)
+               return NULL;
+
+       struct riscv_dmi_direct_batch *direct = calloc(1, sizeof(*direct));
+       if (!direct) {
+               riscv_dmi_batch_free_common(batch);
+               return NULL;
+       }
+
+       direct->allocated_ops = scans;
+       direct->ops = calloc(scans, sizeof(*direct->ops));
+       direct->read_keys = calloc(scans, sizeof(*direct->read_keys));
+       if (!direct->ops || !direct->read_keys) {
+               free(direct->ops);
+               free(direct->read_keys);
+               free(direct);
+               riscv_dmi_batch_free_common(batch);
+               return NULL;
+       }
+
+       batch->backend_priv = direct;
+       return batch;
+}
+
+void riscv_dmi_direct_batch_free(struct riscv_batch *batch)
+{
+       struct riscv_dmi_direct_batch *direct = batch->backend_priv;
+       if (direct) {
+               free(direct->ops);
+               free(direct->read_keys);
+               free(direct);
+       }
+       riscv_dmi_batch_free_common(batch);
+}
+
+void riscv_dmi_direct_batch_add_write(struct riscv_batch *batch,
+               uint32_t address, uint32_t data, bool read_back,
+               enum riscv_scan_delay_class delay_class)
+{
+       struct riscv_dmi_direct_batch *direct = batch->backend_priv;
+       assert(direct);
+       assert(direct->used_ops < direct->allocated_ops);
+
+       struct riscv_dmi_direct_op *op = &direct->ops[direct->used_ops++];
+       op->opcode = RV_OP_WRITE;
+       op->params.write.addr = address;
+       op->params.write.data_to_target = data;
+}
+
+size_t riscv_dmi_direct_batch_add_read(struct riscv_batch *batch,
+               uint32_t address, enum riscv_scan_delay_class delay_class)
+{
+       struct riscv_dmi_direct_batch *direct = batch->backend_priv;
+       assert(direct);
+       assert(direct->used_ops < direct->allocated_ops);
+
+       struct riscv_dmi_direct_op *op = &direct->ops[direct->used_ops++];
+       op->opcode = RV_OP_READ;
+       op->params.read.addr = address;
+       op->params.read.data_from_target = 0;
+
+       direct->read_keys[direct->read_keys_used] = direct->used_ops - 1;
+       return direct->read_keys_used++;
+}
+
+uint32_t riscv_dmi_direct_batch_get_read_op(const struct riscv_batch *batch,
+               size_t key)
+{
+       return DTM_DMI_OP_SUCCESS;
+}
+
+uint32_t riscv_dmi_direct_batch_get_read_data(const struct riscv_batch *batch,
+               size_t key)
+{
+       const struct riscv_dmi_direct_batch *direct = batch->backend_priv;
+       assert(direct);
+       assert(key < direct->read_keys_used);
+       size_t index = direct->read_keys[key];
+       assert(index < direct->used_ops);
+
+       return direct->ops[index].params.read.data_from_target;
+}
+
+size_t riscv_dmi_direct_batch_available_scans(struct riscv_batch *batch)
+{
+       const struct riscv_dmi_direct_batch *direct = batch->backend_priv;
+       assert(direct);
+
+       if (direct->used_ops >= direct->allocated_ops)
+               return 0;
+       return direct->allocated_ops - direct->used_ops;
+}
+
+bool riscv_dmi_direct_batch_was_busy(const struct riscv_batch *batch)
+{
+       return false;
+}
+
+size_t riscv_dmi_direct_batch_finished_scans(const struct riscv_batch *batch)
+{
+       const struct riscv_dmi_direct_batch *direct = batch->backend_priv;
+       assert(direct);
+
+       return direct->used_ops;
+}
+
+static struct riscv_dmi_ap_config *riscv_dmi_ap_config(struct target *target)
+{
+       RISCV_INFO(r);
+
+       if (!r->dtm)
+               return NULL;
+       return r->dtm->backend_priv;
+}
+
+static int riscv_dmi_get_info_ap(struct target *target,
+               struct riscv_dmi_info *info)
+{
+       info->dtm_version = DTM_DTMCS_VERSION_1_0;
+       info->abits = RISCV013_DTMCS_ABITS_MAX;
+       info->idle = 0;
+       info->has_dtmcs = false;
+       return ERROR_OK;
+}
+
+static int riscv_dmi_reset_ap(struct target *target)
+{
+       return ERROR_OK;
+}
+
+static int riscv_dmi_prepare_access_ap(struct target *target)
+{
+       struct riscv_dmi_ap_config *config = riscv_dmi_ap_config(target);
+       if (!config || !config->ap) {
+               LOG_TARGET_ERROR(target, "RISC-V AP-backed DTM is not 
initialized.");
+               return ERROR_FAIL;
+       }
+
+       return ERROR_OK;
+}
+
+static struct riscv_batch *riscv_batch_alloc_ap(struct target *target,
+               size_t scans)
+{
+       return riscv_dmi_direct_batch_alloc(target, scans, 
&riscv_dmi_ap_backend);
+}
+
+static int riscv_batch_run_from_ap(struct riscv_batch *batch, size_t start_idx,
+               const struct riscv_scan_delays *delays, bool resets_delays,
+               size_t reset_delays_after)
+{
+       assert(start_idx == 0);
+
+       struct riscv_dmi_ap_config *config = riscv_dmi_ap_config(batch->target);
+       if (!config || !config->ap)
+               return ERROR_FAIL;
+
+       struct riscv_dmi_direct_batch *direct = batch->backend_priv;
+       assert(direct);
+
+       keep_alive();
+       for (size_t i = 0; i < direct->used_ops; ++i) {
+               int result = ERROR_OK;
+               struct riscv_dmi_direct_op *op = &direct->ops[i];
+
+               if (op->opcode == RV_OP_READ) {
+                       result = mem_ap_read_u32(config->ap, 
op->params.read.addr * 4,
+                               &op->params.read.data_from_target);
+               }
+               if (op->opcode == RV_OP_WRITE) {
+                       result = mem_ap_write_u32(config->ap, 
op->params.write.addr * 4,
+                               op->params.write.data_to_target);
+               }
+               if (result != ERROR_OK)
+                       return result;
+       }
+
+       int result = dap_run(config->ap->dap);
+       if (result != ERROR_OK)
+               return result;
+
+       keep_alive();
+       return ERROR_OK;
+}
+
+const struct riscv_dmi_backend_ops riscv_dmi_ap_backend = {
+       .name = "ap",
+       .get_info = riscv_dmi_get_info_ap,
+       .reset = riscv_dmi_reset_ap,
+       .prepare_access = riscv_dmi_prepare_access_ap,
+       .batch_alloc = riscv_batch_alloc_ap,
+       .batch_free = riscv_dmi_direct_batch_free,
+       .batch_run_from = riscv_batch_run_from_ap,
+       .batch_add_dmi_write = riscv_dmi_direct_batch_add_write,
+       .batch_add_dmi_read = riscv_dmi_direct_batch_add_read,
+       .batch_get_dmi_read_op = riscv_dmi_direct_batch_get_read_op,
+       .batch_get_dmi_read_data = riscv_dmi_direct_batch_get_read_data,
+       .batch_available_scans = riscv_dmi_direct_batch_available_scans,
+       .batch_was_busy = riscv_dmi_direct_batch_was_busy,
+       .batch_finished_scans = riscv_dmi_direct_batch_finished_scans,
+};
diff --git a/src/target/riscv/dtm/internal.h b/src/target/riscv/dtm/internal.h
new file mode 100644
index 0000000000..389578248d
--- /dev/null
+++ b/src/target/riscv/dtm/internal.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef OPENOCD_TARGET_RISCV_DTM_INTERNAL_H
+#define OPENOCD_TARGET_RISCV_DTM_INTERNAL_H
+
+#include "target/riscv/batch.h"
+#include "target/riscv/dmi.h"
+#include "target/riscv/dtm.h"
+
+enum riscv_dmi_direct_opcode {
+       RV_OP_INVALID = 0,
+       RV_OP_READ,
+       RV_OP_WRITE,
+};
+
+struct riscv_dmi_direct_op {
+       enum riscv_dmi_direct_opcode opcode;
+       union {
+               struct {
+                       unsigned int addr;
+                       uint32_t data_from_target;
+               } read;
+               struct {
+                       unsigned int addr;
+                       uint32_t data_to_target;
+               } write;
+       } params;
+};
+
+struct riscv_dmi_direct_batch {
+       size_t allocated_ops;
+       size_t used_ops;
+       struct riscv_dmi_direct_op *ops;
+       size_t *read_keys;
+       size_t read_keys_used;
+};
+
+struct riscv_batch *riscv_dmi_batch_alloc_common(struct target *target,
+               size_t scans, const struct riscv_dmi_backend_ops *backend);
+void riscv_dmi_batch_free_common(struct riscv_batch *batch);
+
+struct riscv_batch *riscv_dmi_direct_batch_alloc(struct target *target,
+               size_t scans, const struct riscv_dmi_backend_ops *backend);
+void riscv_dmi_direct_batch_free(struct riscv_batch *batch);
+void riscv_dmi_direct_batch_add_write(struct riscv_batch *batch,
+               uint32_t address, uint32_t data, bool read_back,
+               enum riscv_scan_delay_class delay_class);
+size_t riscv_dmi_direct_batch_add_read(struct riscv_batch *batch,
+               uint32_t address, enum riscv_scan_delay_class delay_class);
+uint32_t riscv_dmi_direct_batch_get_read_op(const struct riscv_batch *batch,
+               size_t key);
+uint32_t riscv_dmi_direct_batch_get_read_data(const struct riscv_batch *batch,
+               size_t key);
+size_t riscv_dmi_direct_batch_available_scans(struct riscv_batch *batch);
+bool riscv_dmi_direct_batch_was_busy(const struct riscv_batch *batch);
+size_t riscv_dmi_direct_batch_finished_scans(const struct riscv_batch *batch);
+
+#endif /* OPENOCD_TARGET_RISCV_DTM_INTERNAL_H */
diff --git a/src/target/riscv/dtm/jtag.c b/src/target/riscv/dtm/jtag.c
new file mode 100644
index 0000000000..605b7c9a36
--- /dev/null
+++ b/src/target/riscv/dtm/jtag.c
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "internal.h"
+
+#include "target/riscv/debug_defines.h"
+#include "target/riscv/debug_reg_printer.h"
+#include "target/riscv/field_helpers.h"
+#include "target/riscv/riscv.h"
+
+#include "helper/binarybuffer.h"
+#include "helper/log.h"
+#include "jtag/jtag.h"
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+#define DTM_DMI_MAX_ADDRESS_LENGTH     ((1 << DTM_DTMCS_ABITS_LENGTH) - 1)
+#define DMI_SCAN_MAX_BIT_LENGTH \
+       (DTM_DMI_MAX_ADDRESS_LENGTH + DTM_DMI_DATA_LENGTH + DTM_DMI_OP_LENGTH)
+#define DMI_SCAN_BUF_SIZE (DIV_ROUND_UP(DMI_SCAN_MAX_BIT_LENGTH, 8))
+
+/* Reserve extra room in the batch for the final DMI NOP. */
+#define BATCH_RESERVED_SCANS 1
+
+enum riscv_scan_type {
+       RISCV_SCAN_TYPE_INVALID,
+       RISCV_SCAN_TYPE_NOP,
+       RISCV_SCAN_TYPE_READ,
+       RISCV_SCAN_TYPE_WRITE,
+};
+
+struct riscv_dmi_jtag_batch {
+       size_t allocated_scans;
+       size_t used_scans;
+
+       uint8_t *data_out;
+       uint8_t *data_in;
+       struct scan_field *fields;
+       enum riscv_scan_delay_class *delay_classes;
+
+       riscv_bscan_tunneled_scan_context_t *bscan_ctxt;
+
+       enum riscv_scan_type last_scan;
+       size_t *read_keys;
+       size_t read_keys_used;
+
+       bool was_run;
+       unsigned int last_scan_delay;
+       bool finalized;
+};
+
+static struct riscv_dmi_jtag_batch *riscv_jtag_batch(struct riscv_batch *batch)
+{
+       return batch->backend_priv;
+}
+
+static const struct riscv_dmi_jtag_batch *riscv_jtag_batch_const(const struct 
riscv_batch *batch)
+{
+       return batch->backend_priv;
+}
+
+static unsigned int get_dmi_scan_length(const struct target *target)
+{
+       const unsigned int abits = riscv_get_dmi_address_bits(target);
+       assert(abits > 0);
+       assert(abits <= DTM_DMI_MAX_ADDRESS_LENGTH);
+
+       return abits + DTM_DMI_DATA_LENGTH + DTM_DMI_OP_LENGTH;
+}
+
+static int riscv_dmi_get_info_jtag(struct target *target,
+               struct riscv_dmi_info *info)
+{
+       uint32_t dtmcontrol;
+       if (dtmcs_scan(target->tap, 0, &dtmcontrol) != ERROR_OK || dtmcontrol 
== 0) {
+               LOG_TARGET_ERROR(target,
+                       "Could not read dtmcontrol. Check JTAG 
connectivity/board power.");
+               return ERROR_FAIL;
+       }
+
+       LOG_TARGET_DEBUG(target, "dtmcontrol=0x%x", dtmcontrol);
+       info->dtm_version = get_field(dtmcontrol, DTM_DTMCS_VERSION);
+       info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
+       info->idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
+       info->has_dtmcs = true;
+       return ERROR_OK;
+}
+
+static int riscv_dmi_reset_jtag(struct target *target)
+{
+       return dtmcs_scan(target->tap, DTM_DTMCS_DMIRESET, NULL);
+}
+
+static int riscv_dmi_prepare_access_jtag(struct target *target)
+{
+       struct jtag_tap *tap = target->tap;
+
+       if (bscan_tunnel_ir_width != 0) {
+               select_dmi_via_bscan(tap);
+               return ERROR_OK;
+       }
+       if (!tap->enabled)
+               LOG_ERROR("BUG: Target's TAP '%s' is disabled!", 
jtag_tap_name(tap));
+
+       bool need_ir_scan = false;
+       for (struct jtag_tap *other_tap = jtag_tap_next_enabled(NULL);
+                       other_tap; other_tap = 
jtag_tap_next_enabled(other_tap)) {
+               if (other_tap != tap) {
+                       if (!other_tap->bypass) {
+                               need_ir_scan = true;
+                               break;
+                       }
+               } else if (!buf_eq(tap->cur_instr, select_dbus.out_value, 
tap->ir_length)) {
+                       need_ir_scan = true;
+                       break;
+               }
+       }
+
+       if (need_ir_scan)
+               jtag_add_ir_scan(tap, &select_dbus, TAP_IDLE);
+
+       return ERROR_OK;
+}
+
+static void riscv_batch_add_nop_jtag(struct riscv_batch *batch);
+
+static struct riscv_batch *riscv_batch_alloc_jtag(struct target *target,
+               size_t scans)
+{
+       scans += BATCH_RESERVED_SCANS;
+       struct riscv_batch *out = riscv_dmi_batch_alloc_common(target, scans,
+                       &riscv_dmi_jtag_backend);
+       if (!out) {
+               LOG_ERROR("Failed to allocate struct riscv_batch");
+               return NULL;
+       }
+
+       struct riscv_dmi_jtag_batch *jtag = calloc(1, sizeof(*jtag));
+       if (!jtag) {
+               LOG_ERROR("Failed to allocate RISC-V JTAG batch state.");
+               riscv_dmi_batch_free_common(out);
+               return NULL;
+       }
+
+       out->backend_priv = jtag;
+       jtag->allocated_scans = scans;
+       jtag->last_scan = RISCV_SCAN_TYPE_INVALID;
+       jtag->was_run = false;
+       jtag->last_scan_delay = 0;
+
+       /* FIXME: Allocate only enough buffer space for the target's real DMI
+        * scan length instead of the architectural maximum.
+        */
+       jtag->data_out = malloc(sizeof(*jtag->data_out) * scans * 
DMI_SCAN_BUF_SIZE);
+       if (!jtag->data_out) {
+               LOG_ERROR("Failed to allocate data_out in RISC-V batch.");
+               goto alloc_error;
+       }
+       jtag->data_in = malloc(sizeof(*jtag->data_in) * scans * 
DMI_SCAN_BUF_SIZE);
+       if (!jtag->data_in) {
+               LOG_ERROR("Failed to allocate data_in in RISC-V batch.");
+               goto alloc_error;
+       }
+       jtag->fields = malloc(sizeof(*jtag->fields) * scans);
+       if (!jtag->fields) {
+               LOG_ERROR("Failed to allocate fields in RISC-V batch.");
+               goto alloc_error;
+       }
+       jtag->delay_classes = malloc(sizeof(*jtag->delay_classes) * scans);
+       if (!jtag->delay_classes) {
+               LOG_ERROR("Failed to allocate delay_classes in RISC-V batch.");
+               goto alloc_error;
+       }
+       if (bscan_tunnel_ir_width != 0) {
+               jtag->bscan_ctxt = malloc(sizeof(*jtag->bscan_ctxt) * scans);
+               if (!jtag->bscan_ctxt) {
+                       LOG_ERROR("Failed to allocate bscan_ctxt in RISC-V 
batch.");
+                       goto alloc_error;
+               }
+       }
+       jtag->read_keys = malloc(sizeof(*jtag->read_keys) * scans);
+       if (!jtag->read_keys) {
+               LOG_ERROR("Failed to allocate read_keys in RISC-V batch.");
+               goto alloc_error;
+       }
+
+       return out;
+
+alloc_error:
+       riscv_dmi_jtag_backend.batch_free(out);
+       return NULL;
+}
+
+static void riscv_batch_free_jtag(struct riscv_batch *batch)
+{
+       struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch(batch);
+       if (jtag) {
+               free(jtag->data_in);
+               free(jtag->data_out);
+               free(jtag->fields);
+               free(jtag->delay_classes);
+               free(jtag->bscan_ctxt);
+               free(jtag->read_keys);
+               free(jtag);
+       }
+       riscv_dmi_batch_free_common(batch);
+}
+
+static bool riscv_batch_was_scan_busy(const struct riscv_batch *batch,
+               size_t scan_idx)
+{
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       assert(jtag);
+       assert(jtag->was_run);
+       assert(scan_idx < jtag->used_scans);
+       const struct scan_field *field = jtag->fields + scan_idx;
+       assert(field->in_value);
+       const uint64_t in = buf_get_u64(field->in_value, 0, field->num_bits);
+       return get_field(in, DTM_DMI_OP) == DTM_DMI_OP_BUSY;
+}
+
+static void add_idle_before_batch(const struct riscv_batch *batch, size_t 
start_idx,
+               const struct riscv_scan_delays *delays)
+{
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       if (!jtag->was_run)
+               return;
+
+       const enum riscv_scan_delay_class delay_class = start_idx > 0
+               ? jtag->delay_classes[start_idx - 1]
+               : RISCV_DELAY_BASE;
+       const unsigned int new_delay = riscv_scan_get_delay(delays, 
delay_class);
+       if (new_delay <= jtag->last_scan_delay)
+               return;
+       const unsigned int idle_change = new_delay - jtag->last_scan_delay;
+       LOG_TARGET_DEBUG(batch->target, "Adding %u idle cycles before the 
batch.",
+                       idle_change);
+       jtag_add_runtest(idle_change, TAP_IDLE);
+}
+
+static unsigned int get_delay(const struct riscv_batch *batch, size_t scan_idx,
+               const struct riscv_scan_delays *delays, bool resets_delays,
+               size_t reset_delays_after)
+{
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       assert(jtag);
+       assert(scan_idx < jtag->used_scans);
+       const bool delays_were_reset = resets_delays
+               && (scan_idx >= reset_delays_after);
+       const enum riscv_scan_delay_class delay_class =
+               jtag->delay_classes[scan_idx];
+       const unsigned int delay = riscv_scan_get_delay(delays, delay_class);
+       return delays_were_reset ? 0 : delay;
+}
+
+static unsigned int decode_dmi(const struct riscv_batch *batch, char *text,
+               uint32_t address, uint32_t data)
+{
+       static const struct {
+               uint32_t address;
+               enum riscv_debug_reg_ordinal ordinal;
+       } description[] = {
+               {DM_DMCONTROL, DM_DMCONTROL_ORDINAL},
+               {DM_DMSTATUS, DM_DMSTATUS_ORDINAL},
+               {DM_ABSTRACTCS, DM_ABSTRACTCS_ORDINAL},
+               {DM_COMMAND, DM_COMMAND_ORDINAL},
+               {DM_SBCS, DM_SBCS_ORDINAL}
+       };
+
+       for (unsigned int i = 0; i < ARRAY_SIZE(description); i++) {
+               if (riscv_get_dmi_address(batch->target, description[i].address)
+                               == address) {
+                       const struct riscv_debug_reg_ctx context = {
+                               .XLEN = { .value = 0, .is_set = false },
+                               .DXLEN = { .value = 0, .is_set = false },
+                               .abits = { .value = 0, .is_set = false },
+                       };
+                       return riscv_debug_reg_to_s(text, 
description[i].ordinal,
+                                       context, data, 
RISCV_DEBUG_REG_HIDE_ALL_0);
+               }
+       }
+       if (text)
+               text[0] = '\0';
+       return 0;
+}
+
+static void log_dmi_decoded(const struct riscv_batch *batch, bool write,
+               uint32_t address, uint32_t data)
+{
+       const size_t size = decode_dmi(batch, NULL, address, data) + 1;
+       char * const decoded = malloc(size);
+       if (!decoded) {
+               LOG_ERROR("Not enough memory to allocate %zu bytes.", size);
+               return;
+       }
+       decode_dmi(batch, decoded, address, data);
+       LOG_DEBUG("%s: %s", write ? "write" : "read", decoded);
+       free(decoded);
+}
+
+static void log_batch(const struct riscv_batch *batch, size_t start_idx,
+               const struct riscv_scan_delays *delays, bool resets_delays,
+               size_t reset_delays_after)
+{
+       if (!LOG_LEVEL_IS(LOG_LVL_DEBUG))
+               return;
+
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       const unsigned int abits = riscv_get_dmi_address_bits(batch->target);
+
+       bool last_scan_was_read = false;
+       uint32_t last_scan_address = (uint32_t)(-1);
+       if (start_idx > 0) {
+               const struct scan_field * const field = &jtag->fields[start_idx 
- 1];
+               assert(field->out_value);
+               last_scan_was_read = buf_get_u32(field->out_value, 
DTM_DMI_OP_OFFSET,
+                               DTM_DMI_OP_LENGTH) == DTM_DMI_OP_READ;
+               last_scan_address = buf_get_u32(field->out_value,
+                               DTM_DMI_ADDRESS_OFFSET, abits);
+       }
+
+       for (size_t i = start_idx; i < jtag->used_scans; ++i) {
+               static const char * const op_string[] = {"-", "r", "w", "?"};
+               const unsigned int delay = get_delay(batch, i, delays, 
resets_delays,
+                               reset_delays_after);
+               const struct scan_field * const field = &jtag->fields[i];
+
+               assert(field->out_value);
+               const unsigned int out_op = buf_get_u32(field->out_value,
+                               DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
+               const uint32_t out_data = buf_get_u32(field->out_value,
+                               DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH);
+               const uint32_t out_address = buf_get_u32(field->out_value,
+                               DTM_DMI_ADDRESS_OFFSET, abits);
+               if (field->in_value) {
+                       static const char * const status_string[] = {
+                               "+", "?", "F", "b"
+                       };
+                       const unsigned int in_op = buf_get_u32(field->in_value,
+                                       DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
+                       const uint32_t in_data = buf_get_u32(field->in_value,
+                                       DTM_DMI_DATA_OFFSET, 
DTM_DMI_DATA_LENGTH);
+                       const uint32_t in_address = buf_get_u32(field->in_value,
+                                       DTM_DMI_ADDRESS_OFFSET, abits);
+
+                       LOG_DEBUG("%db %s %08" PRIx32 " @%02" PRIx32
+                                       " -> %s %08" PRIx32 " @%02" PRIx32 "; 
%ui",
+                                       field->num_bits, op_string[out_op], 
out_data, out_address,
+                                       status_string[in_op], in_data, 
in_address, delay);
+
+                       if (last_scan_was_read && in_op == DTM_DMI_OP_SUCCESS)
+                               log_dmi_decoded(batch, false, 
last_scan_address, in_data);
+               } else {
+                       LOG_DEBUG("%db %s %08" PRIx32 " @%02" PRIx32 " -> ?; 
%ui",
+                                       field->num_bits, op_string[out_op], 
out_data, out_address,
+                                       delay);
+               }
+
+               if (out_op == DTM_DMI_OP_WRITE)
+                       log_dmi_decoded(batch, true, out_address, out_data);
+
+               last_scan_was_read = out_op == DTM_DMI_OP_READ;
+               last_scan_address = out_address;
+       }
+}
+
+static int riscv_batch_run_from_jtag(struct riscv_batch *batch, size_t 
start_idx,
+               const struct riscv_scan_delays *delays, bool resets_delays,
+               size_t reset_delays_after)
+{
+       struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch(batch);
+       if (!jtag->finalized)
+               riscv_batch_add_nop_jtag(batch);
+
+       assert(jtag->used_scans);
+       assert(start_idx < jtag->used_scans);
+       assert(jtag->last_scan == RISCV_SCAN_TYPE_NOP);
+       assert(!jtag->was_run || riscv_batch_was_scan_busy(batch, start_idx));
+       assert(start_idx == 0 || !riscv_batch_was_scan_busy(batch, start_idx - 
1));
+
+       if (jtag->was_run)
+               add_idle_before_batch(batch, start_idx, delays);
+
+       LOG_TARGET_DEBUG(batch->target, "Running batch of scans [%zu, %zu)",
+                       start_idx, jtag->used_scans);
+
+       unsigned int delay = 0;
+       for (size_t i = start_idx; i < jtag->used_scans; ++i) {
+               if (bscan_tunnel_ir_width != 0)
+                       riscv_add_bscan_tunneled_scan(batch->target->tap,
+                                       jtag->fields + i, jtag->bscan_ctxt + i);
+               else
+                       jtag_add_dr_scan(batch->target->tap, 1, jtag->fields + 
i,
+                                       TAP_IDLE);
+
+               delay = get_delay(batch, i, delays, resets_delays,
+                               reset_delays_after);
+               if (delay > 0)
+                       jtag_add_runtest(delay, TAP_IDLE);
+       }
+
+       keep_alive();
+
+       if (jtag_execute_queue() != ERROR_OK) {
+               LOG_TARGET_ERROR(batch->target, "Unable to execute JTAG queue");
+               return ERROR_FAIL;
+       }
+
+       keep_alive();
+
+       if (bscan_tunnel_ir_width != 0) {
+               for (size_t i = start_idx; i < jtag->used_scans; ++i) {
+                       if ((jtag->fields + i)->in_value)
+                               buffer_shr((jtag->fields + i)->in_value, 
DMI_SCAN_BUF_SIZE, 1);
+               }
+       }
+
+       log_batch(batch, start_idx, delays, resets_delays, reset_delays_after);
+       jtag->was_run = true;
+       jtag->last_scan_delay = delay;
+       return ERROR_OK;
+}
+
+static void riscv_batch_add_dmi_write_jtag(struct riscv_batch *batch,
+               uint32_t address, uint32_t data, bool read_back,
+               enum riscv_scan_delay_class delay_class)
+{
+       struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch(batch);
+       assert(jtag->used_scans < jtag->allocated_scans);
+       struct scan_field *field = jtag->fields + jtag->used_scans;
+
+       field->num_bits = get_dmi_scan_length(batch->target);
+       assert(field->num_bits <= DMI_SCAN_MAX_BIT_LENGTH);
+
+       uint8_t *out_value = jtag->data_out + jtag->used_scans * 
DMI_SCAN_BUF_SIZE;
+       uint8_t *in_value = jtag->data_in + jtag->used_scans * 
DMI_SCAN_BUF_SIZE;
+
+       field->out_value = out_value;
+       riscv_fill_dmi_write(batch->target, out_value, address, data);
+
+       if (read_back) {
+               field->in_value = in_value;
+               riscv_fill_dm_nop(batch->target, in_value);
+       } else {
+               field->in_value = NULL;
+       }
+
+       jtag->delay_classes[jtag->used_scans] = delay_class;
+       jtag->last_scan = RISCV_SCAN_TYPE_WRITE;
+       jtag->used_scans++;
+}
+
+static size_t riscv_batch_add_dmi_read_jtag(struct riscv_batch *batch,
+               uint32_t address, enum riscv_scan_delay_class delay_class)
+{
+       struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch(batch);
+       assert(jtag->used_scans < jtag->allocated_scans);
+       struct scan_field *field = jtag->fields + jtag->used_scans;
+
+       field->num_bits = get_dmi_scan_length(batch->target);
+       assert(field->num_bits <= DMI_SCAN_MAX_BIT_LENGTH);
+
+       uint8_t *out_value = jtag->data_out + jtag->used_scans * 
DMI_SCAN_BUF_SIZE;
+       uint8_t *in_value = jtag->data_in + jtag->used_scans * 
DMI_SCAN_BUF_SIZE;
+
+       field->out_value = out_value;
+       field->in_value = in_value;
+       riscv_fill_dmi_read(batch->target, out_value, address);
+       riscv_fill_dm_nop(batch->target, in_value);
+
+       jtag->delay_classes[jtag->used_scans] = delay_class;
+       jtag->last_scan = RISCV_SCAN_TYPE_READ;
+       jtag->used_scans++;
+
+       jtag->read_keys[jtag->read_keys_used] = jtag->used_scans;
+       return jtag->read_keys_used++;
+}
+
+static uint32_t riscv_batch_get_dmi_read_op_jtag(const struct riscv_batch 
*batch, size_t key)
+{
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       assert(key < jtag->read_keys_used);
+       size_t index = jtag->read_keys[key];
+       assert(index < jtag->used_scans);
+       uint8_t *base = jtag->data_in + DMI_SCAN_BUF_SIZE * index;
+       return buf_get_u32(base, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
+}
+
+static uint32_t riscv_batch_get_dmi_read_data_jtag(const struct riscv_batch 
*batch, size_t key)
+{
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       assert(key < jtag->read_keys_used);
+       size_t index = jtag->read_keys[key];
+       assert(index < jtag->used_scans);
+       uint8_t *base = jtag->data_in + DMI_SCAN_BUF_SIZE * index;
+       return buf_get_u32(base, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH);
+}
+
+static void riscv_batch_add_nop_jtag(struct riscv_batch *batch)
+{
+       struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch(batch);
+       if (jtag->finalized)
+               return;
+
+       assert(jtag->used_scans < jtag->allocated_scans);
+       struct scan_field *field = jtag->fields + jtag->used_scans;
+
+       field->num_bits = get_dmi_scan_length(batch->target);
+       assert(field->num_bits <= DMI_SCAN_MAX_BIT_LENGTH);
+
+       uint8_t *out_value = jtag->data_out + jtag->used_scans * 
DMI_SCAN_BUF_SIZE;
+       uint8_t *in_value = jtag->data_in + jtag->used_scans * 
DMI_SCAN_BUF_SIZE;
+
+       field->out_value = out_value;
+       field->in_value = in_value;
+       riscv_fill_dm_nop(batch->target, out_value);
+       riscv_fill_dm_nop(batch->target, in_value);
+
+       jtag->delay_classes[jtag->used_scans] = RISCV_DELAY_BASE;
+       jtag->last_scan = RISCV_SCAN_TYPE_NOP;
+       jtag->used_scans++;
+       jtag->finalized = true;
+}
+
+static size_t riscv_batch_available_scans_jtag(struct riscv_batch *batch)
+{
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       assert(jtag->allocated_scans >= (jtag->used_scans + 
BATCH_RESERVED_SCANS));
+       return jtag->allocated_scans - jtag->used_scans - BATCH_RESERVED_SCANS;
+}
+
+static bool riscv_batch_was_batch_busy_jtag(const struct riscv_batch *batch)
+{
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       assert(jtag->was_run);
+       assert(jtag->used_scans);
+       assert(jtag->last_scan == RISCV_SCAN_TYPE_NOP);
+       return riscv_batch_was_scan_busy(batch, jtag->used_scans - 1);
+}
+
+static size_t riscv_batch_finished_scans_jtag(const struct riscv_batch *batch)
+{
+       const struct riscv_dmi_jtag_batch *jtag = riscv_jtag_batch_const(batch);
+       if (!riscv_batch_was_batch_busy(batch))
+               return jtag->used_scans;
+
+       assert(jtag->used_scans);
+       size_t first_busy = 0;
+       while (!riscv_batch_was_scan_busy(batch, first_busy))
+               ++first_busy;
+       return first_busy;
+}
+
+const struct riscv_dmi_backend_ops riscv_dmi_jtag_backend = {
+       .name = "jtag",
+       .get_info = riscv_dmi_get_info_jtag,
+       .reset = riscv_dmi_reset_jtag,
+       .prepare_access = riscv_dmi_prepare_access_jtag,
+       .batch_alloc = riscv_batch_alloc_jtag,
+       .batch_free = riscv_batch_free_jtag,
+       .batch_run_from = riscv_batch_run_from_jtag,
+       .batch_add_dmi_write = riscv_batch_add_dmi_write_jtag,
+       .batch_add_dmi_read = riscv_batch_add_dmi_read_jtag,
+       .batch_get_dmi_read_op = riscv_batch_get_dmi_read_op_jtag,
+       .batch_get_dmi_read_data = riscv_batch_get_dmi_read_data_jtag,
+       .batch_available_scans = riscv_batch_available_scans_jtag,
+       .batch_was_busy = riscv_batch_was_batch_busy_jtag,
+       .batch_finished_scans = riscv_batch_finished_scans_jtag,
+};
diff --git a/src/target/riscv/riscv-013.c b/src/target/riscv/riscv-013.c
index b4664d2a7f..725cd5d7f8 100644
--- a/src/target/riscv/riscv-013.c
+++ b/src/target/riscv/riscv-013.c
@@ -32,6 +32,7 @@
 #include "program.h"
 #include "batch.h"
 #include "debug_reg_printer.h"
+#include "dmi.h"
 #include "field_helpers.h"
 
 static int riscv013_on_step_or_resume(struct target *target, bool step);
@@ -403,46 +404,11 @@ static uint32_t set_dmcontrol_hartsel(uint32_t initial, 
int hart_index)
        return initial;
 }
 
-/*** Utility functions. ***/
-
-static void select_dmi(struct jtag_tap *tap)
-{
-       if (bscan_tunnel_ir_width != 0) {
-               select_dmi_via_bscan(tap);
-               return;
-       }
-       if (!tap->enabled)
-               LOG_ERROR("BUG: Target's TAP '%s' is disabled!", 
jtag_tap_name(tap));
-
-       bool need_ir_scan = false;
-       /* FIXME: make "tap" a const pointer. */
-       for (struct jtag_tap *other_tap = jtag_tap_next_enabled(NULL);
-                       other_tap; other_tap = 
jtag_tap_next_enabled(other_tap)) {
-               if (other_tap != tap) {
-                       /* Different TAP than ours - check if it is in bypass */
-                       if (!other_tap->bypass) {
-                               need_ir_scan = true;
-                               break;
-                       }
-               } else {
-                       /* Our TAP - check if the correct instruction is 
already loaded */
-                       if (!buf_eq(tap->cur_instr, select_dbus.out_value, 
tap->ir_length)) {
-                               need_ir_scan = true;
-                               break;
-                       }
-               }
-       }
-
-       if (need_ir_scan)
-               jtag_add_ir_scan(tap, &select_dbus, TAP_IDLE);
-}
-
 static int increase_dmi_busy_delay(struct target *target)
 {
        RISCV013_INFO(info);
 
-       int res = dtmcs_scan(target->tap, DTM_DTMCS_DMIRESET,
-                       NULL /* discard result */);
+       int res = riscv_dmi_reset(target);
        if (res != ERROR_OK)
                return res;
 
@@ -2026,34 +1992,21 @@ static int examine_dm(struct target *target)
 
 static int examine(struct target *target)
 {
+       riscv013_info_t *info = get_info(target);
        /* We reset target state in case if something goes wrong during examine:
         * DTM/DM scans could fail or hart may fail to halt. */
        target->state = TARGET_UNKNOWN;
        target->debug_reason = DBG_REASON_UNDEFINED;
 
-       /* Don't need to select dbus, since the first thing we do is read 
dtmcontrol. */
+       info->index = target->coreid;
        LOG_TARGET_DEBUG(target, "dbgbase=0x%x", target->dbgbase);
 
-       uint32_t dtmcontrol;
-       if (dtmcs_scan(target->tap, 0, &dtmcontrol) != ERROR_OK || dtmcontrol 
== 0) {
-               LOG_TARGET_ERROR(target, "Could not scan dtmcontrol. Check JTAG 
connectivity/board power.");
+       struct riscv_dmi_info dmi_info;
+       if (riscv_dmi_get_info(target, &dmi_info) != ERROR_OK)
                return ERROR_FAIL;
-       }
 
-       LOG_TARGET_DEBUG(target, "dtmcontrol=0x%x", dtmcontrol);
-       LOG_DEBUG_REG(target, DTM_DTMCS, dtmcontrol);
-
-       if (get_field(dtmcontrol, DTM_DTMCS_VERSION) != 1) {
-               LOG_TARGET_ERROR(target, "Unsupported DTM version %" PRIu32 ". 
(dtmcontrol=0x%" PRIx32 ")",
-                               get_field32(dtmcontrol, DTM_DTMCS_VERSION), 
dtmcontrol);
-               return ERROR_FAIL;
-       }
-
-       riscv013_info_t *info = get_info(target);
-
-       info->index = target->coreid;
-       info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
-       info->dtmcs_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
+       info->abits = dmi_info.abits;
+       info->dtmcs_idle = dmi_info.idle;
 
        if (info->abits > RISCV013_DTMCS_ABITS_MAX) {
                /* Max. address width given by the debug specification is 
exceeded */
@@ -2066,7 +2019,7 @@ static int examine(struct target *target)
 
        if (info->abits == 0) {
                LOG_TARGET_ERROR(target,
-                               "dtmcs.abits is zero. Check JTAG 
connectivity/board power");
+                               "The target's debug bus (DMI) address width is 
zero.");
                return ERROR_FAIL;
        }
        if (info->abits < RISCV013_DTMCS_ABITS_MIN) {
@@ -2559,17 +2512,12 @@ static int batch_run(struct target *target, struct 
riscv_batch *batch)
 {
        RISCV_INFO(r);
        RISCV013_INFO(info);
-       select_dmi(target->tap);
-       riscv_batch_add_nop(batch);
-       const int result = riscv_batch_run_from(batch, 0, &info->learned_delays,
+       int result = riscv_batch_run_from(batch, 0, &info->learned_delays,
                        /*resets_delays*/  r->reset_delays_wait >= 0,
                        r->reset_delays_wait);
        if (result != ERROR_OK)
                return result;
-       /* TODO: To use `riscv_batch_finished_scans()` here, it is needed for
-        * all scans to not discard input, meaning
-        * "riscv_batch_add_dm_write(..., false)" should not be used. */
-       const size_t finished_scans = batch->used_scans;
+       const size_t finished_scans = riscv_batch_finished_scans(batch);
        decrement_reset_delays_counter(target, finished_scans);
        if (riscv_batch_was_batch_busy(batch))
                return increase_dmi_busy_delay(target);
@@ -2582,8 +2530,6 @@ static int batch_run(struct target *target, struct 
riscv_batch *batch)
 static int batch_run_timeout(struct target *target, struct riscv_batch *batch)
 {
        RISCV013_INFO(info);
-       select_dmi(target->tap);
-       riscv_batch_add_nop(batch);
 
        size_t finished_scans = 0;
        int64_t then = timeval_ms() + 1000 * riscv_get_command_timeout_sec();
@@ -2603,7 +2549,6 @@ static int batch_run_timeout(struct target *target, 
struct riscv_batch *batch)
                decrement_reset_delays_counter(target, new_finished_scans - 
finished_scans);
                finished_scans = new_finished_scans;
                if (!riscv_batch_was_batch_busy(batch)) {
-                       assert(finished_scans == batch->used_scans);
                        return ERROR_OK;
                }
                result = increase_dmi_busy_delay(target);
@@ -2951,9 +2896,9 @@ static int init_target(struct command_context *cmd_ctx,
 static int assert_reset(struct target *target)
 {
        RISCV013_INFO(info);
-       int result;
-
-       select_dmi(target->tap);
+       int result = riscv_dmi_prepare_access(target);
+       if (result != ERROR_OK)
+               return result;
 
        if (target_has_event_action(target, TARGET_EVENT_RESET_ASSERT)) {
                /* Run the user-supplied script if there is one. */
@@ -3005,9 +2950,10 @@ static int deassert_reset(struct target *target)
        dm013_info_t *dm = get_dm(target);
        if (!dm)
                return ERROR_FAIL;
-       int result;
 
-       select_dmi(target->tap);
+       int result = riscv_dmi_prepare_access(target);
+       if (result != ERROR_OK)
+               return result;
        /* Clear the reset, but make sure haltreq is still set */
        uint32_t control = 0;
        control = set_field(control, DM_DMCONTROL_DMACTIVE, 1);
@@ -3449,14 +3395,17 @@ static int read_memory_bus_v1(struct target *target, 
const struct riscv_mem_acce
                for (uint32_t i = (next_address - address) / size; i < count - 
1; i++) {
                        const uint32_t size_in_words = DIV_ROUND_UP(size, 4);
                        struct riscv_batch *batch = riscv_batch_alloc(target, 
size_in_words);
+                       size_t read_keys[ARRAY_SIZE(sbdata)];
                        /* Read of sbdata0 must be performed as last because it
                         * starts the new bus data transfer
                         * (in case "sbcs.sbreadondata" was set above).
                         * We don't want to start the next bus read before we
                         * fetch all the data from the last bus read. */
                        for (uint32_t j = size_in_words - 1; j > 0; --j)
-                               riscv_batch_add_dm_read(batch, sbdata[j], 
RISCV_DELAY_BASE);
-                       riscv_batch_add_dm_read(batch, sbdata[0], 
RISCV_DELAY_SYSBUS_READ);
+                               read_keys[j] = riscv_batch_add_dm_read(batch, 
sbdata[j],
+                                               RISCV_DELAY_BASE);
+                       read_keys[0] = riscv_batch_add_dm_read(batch, sbdata[0],
+                                       RISCV_DELAY_SYSBUS_READ);
 
                        int res = batch_run_timeout(target, batch);
                        if (res != ERROR_OK) {
@@ -3464,9 +3413,8 @@ static int read_memory_bus_v1(struct target *target, 
const struct riscv_mem_acce
                                return res;
                        }
 
-                       const size_t last_key = batch->read_keys_used - 1;
-                       for (size_t k = 0; k <= last_key; ++k) {
-                               sbvalue[k] = 
riscv_batch_get_dmi_read_data(batch, last_key - k);
+                       for (size_t k = 0; k < size_in_words; ++k) {
+                               sbvalue[k] = 
riscv_batch_get_dmi_read_data(batch, read_keys[k]);
                                buf_set_u32(buffer + i * size + k * 4, 0, 
MIN(32, 8 * size), sbvalue[k]);
                        }
 
@@ -4494,7 +4442,8 @@ read_memory_progbuf(struct target *target, const struct 
riscv_mem_access_args ar
 {
        assert(riscv_mem_access_is_read(args));
 
-       select_dmi(target->tap);
+       if (riscv_dmi_prepare_access(target) != ERROR_OK)
+               return mem_access_result(MEM_ACCESS_FAILED);
        memset(args.read_buffer, 0, args.count * args.size);
 
        if (execute_autofence(target) != ERROR_OK)
diff --git a/src/target/riscv/riscv.c b/src/target/riscv/riscv.c
index 30094b34b5..a5a7958327 100644
--- a/src/target/riscv/riscv.c
+++ b/src/target/riscv/riscv.c
@@ -13,12 +13,15 @@
 #include "target/target.h"
 #include "target/algorithm.h"
 #include "target/target_type.h"
+#include "target/arm_adi_v5.h"
 #include <target/smp.h>
 #include "jtag/jtag.h"
 #include "target/register.h"
 #include "target/breakpoints.h"
 #include "helper/base64.h"
 #include "helper/time_support.h"
+#include "dmi.h"
+#include "dtm.h"
 #include "riscv.h"
 #include "riscv_reg.h"
 #include "program.h"
@@ -461,6 +464,7 @@ static struct target_type *get_target_type(struct target 
*target)
        }
 
        RISCV_INFO(info);
+
        switch (info->dtm_version) {
        case DTM_DTMCS_VERSION_0_11:
                return &riscv011_target;
@@ -477,7 +481,7 @@ static struct target_type *get_target_type(struct target 
*target)
 
 static struct riscv_private_config *alloc_default_riscv_private_config(void)
 {
-       struct riscv_private_config * const config = malloc(sizeof(*config));
+       struct riscv_private_config * const config = calloc(1, sizeof(*config));
        if (!config) {
                LOG_ERROR("Out of memory!");
                return NULL;
@@ -505,6 +509,8 @@ static int riscv_create_target(struct target *target)
                return ERROR_FAIL;
        }
        riscv_info_init(target, target->arch_info);
+       RISCV_INFO(info);
+       info->dtm = config->dtm;
        return ERROR_OK;
 }
 
@@ -611,13 +617,27 @@ static int jim_report_ebreak_config(const struct 
riscv_private_config *config,
        return JIM_OK;
 }
 
+static struct adiv5_private_config *riscv_get_adiv5_config(struct 
riscv_private_config *config)
+{
+       if (!config->adiv5_config) {
+               config->adiv5_config = calloc(1, sizeof(*config->adiv5_config));
+               if (!config->adiv5_config)
+                       return NULL;
+               config->adiv5_config->ap_num = DP_APSEL_INVALID;
+       }
+
+       return config->adiv5_config;
+}
+
 enum riscv_cfg_opts {
        RISCV_CFG_EBREAK,
+       RISCV_CFG_DTM,
        RISCV_CFG_INVALID = -1
 };
 
 static struct jim_nvp nvp_config_opts[] = {
        { .name = "-ebreak", .value = RISCV_CFG_EBREAK },
+       { .name = "-dtm", .value = RISCV_CFG_DTM },
        { .name = NULL, .value = RISCV_CFG_INVALID }
 };
 
@@ -637,8 +657,23 @@ static int riscv_jim_configure(struct target *target,
        struct jim_nvp *n;
        int e = jim_nvp_name2value_obj(goi->interp, nvp_config_opts,
                                goi->argv[0], &n);
-       if (e != JIM_OK)
-               return JIM_CONTINUE;
+       if (e != JIM_OK) {
+               struct adiv5_private_config *adiv5_config =
+                       riscv_get_adiv5_config(config);
+               if (!adiv5_config)
+                       return JIM_ERR;
+
+               int adiv5_result = adiv5_jim_configure_ext(target, goi,
+                               adiv5_config, ADI_CONFIGURE_DAP_OPTIONAL);
+               if (adiv5_result == JIM_OK && goi->is_configure
+                               && adiv5_config->dap
+                               && adiv5_config->ap_num != DP_APSEL_INVALID) {
+                       if (riscv_dtm_assign_ap_target(target, 
adiv5_config->dap,
+                                       adiv5_config->ap_num) != ERROR_OK)
+                               return JIM_ERR;
+               }
+               return adiv5_result;
+       }
 
        e = jim_getopt_obj(goi, NULL);
        if (e != JIM_OK)
@@ -654,6 +689,27 @@ static int riscv_jim_configure(struct target *target,
                return goi->is_configure
                        ? jim_configure_ebreak(config, goi)
                        : jim_report_ebreak_config(config, goi->interp);
+       case RISCV_CFG_DTM:
+               if (goi->is_configure) {
+                       Jim_Obj *dtm_obj;
+                       e = jim_getopt_obj(goi, &dtm_obj);
+                       if (e != JIM_OK)
+                               return e;
+                       struct riscv_dtm *dtm = 
riscv_dtm_by_jim_obj(goi->interp, dtm_obj);
+                       if (!dtm) {
+                               Jim_SetResultString(goi->interp, "RISC-V DTM 
not found", -1);
+                               return JIM_ERR;
+                       }
+                       if (riscv_dtm_assign_target(target, dtm) != ERROR_OK)
+                               return JIM_ERR;
+                       return JIM_OK;
+               }
+               if (!config->dtm) {
+                       Jim_SetResultString(goi->interp, "", -1);
+                       return JIM_OK;
+               }
+               Jim_SetResultString(goi->interp, riscv_dtm_name(config->dtm), 
-1);
+               return JIM_OK;
        default:
                assert(false && "'jim_getopt_nvp' should have returned an 
error.");
        }
@@ -668,29 +724,37 @@ static int riscv_init_target(struct command_context 
*cmd_ctx,
        info->cmd_ctx = cmd_ctx;
        info->reset_delays_wait = -1;
 
-       select_dtmcontrol.num_bits = target->tap->ir_length;
-       select_dbus.num_bits = target->tap->ir_length;
-       select_idcode.num_bits = target->tap->ir_length;
+       riscv_semihosting_init(target);
+
+       target->debug_reason = DBG_REASON_DBGRQ;
+
+       return ERROR_OK;
+}
+
+int riscv_dmi_jtag_init_tap(struct jtag_tap *tap)
+{
+       if (!tap)
+               return ERROR_FAIL;
+
+       select_dtmcontrol.num_bits = tap->ir_length;
+       select_dbus.num_bits = tap->ir_length;
+       select_idcode.num_bits = tap->ir_length;
 
        if (bscan_tunnel_ir_width != 0) {
                uint32_t ir_user4_raw = bscan_tunnel_ir_id;
-               /* Provide a default value which target some Xilinx FPGA USER4 
IR */
+               /* Provide a default value which targets some Xilinx FPGA USER4 
IR. */
                if (ir_user4_raw == 0) {
-                       assert(target->tap->ir_length >= 6);
-                       ir_user4_raw = 0x23 << (target->tap->ir_length - 6);
+                       assert(tap->ir_length >= 6);
+                       ir_user4_raw = 0x23 << (tap->ir_length - 6);
                }
                h_u32_to_le(ir_user4, ir_user4_raw);
-               select_user4.num_bits = target->tap->ir_length;
+               select_user4.num_bits = tap->ir_length;
                if (bscan_tunnel_type == BSCAN_TUNNEL_DATA_REGISTER)
                        bscan_tunnel_data_register_select_dmi[1].num_bits = 
bscan_tunnel_ir_width;
                else /* BSCAN_TUNNEL_NESTED_TAP */
                        bscan_tunnel_nested_tap_select_dmi[2].num_bits = 
bscan_tunnel_ir_width;
        }
 
-       riscv_semihosting_init(target);
-
-       target->debug_reason = DBG_REASON_DBGRQ;
-
        return ERROR_OK;
 }
 
@@ -717,6 +781,9 @@ static void riscv_deinit_target(struct target *target)
 {
        LOG_TARGET_DEBUG(target, "riscv_deinit_target()");
 
+       struct riscv_private_config *config = target->private_config;
+       if (config)
+               free(config->adiv5_config);
        free(target->private_config);
 
        struct riscv_info *info = target->arch_info;
@@ -2476,17 +2543,15 @@ static int riscv_examine(struct target *target)
                LOG_TARGET_DEBUG(target, "Target was already examined.");
                return ERROR_OK;
        }
-
-       /* Don't need to select dbus, since the first thing we do is read 
dtmcontrol. */
-
        RISCV_INFO(info);
-       uint32_t dtmcontrol;
-       if (dtmcs_scan(target->tap, 0, &dtmcontrol) != ERROR_OK || dtmcontrol 
== 0) {
-               LOG_TARGET_ERROR(target, "Could not read dtmcontrol. Check JTAG 
connectivity/board power.");
+       if (riscv_dmi_select(target) != ERROR_OK)
                return ERROR_FAIL;
-       }
-       LOG_TARGET_DEBUG(target, "dtmcontrol=0x%" PRIx32, dtmcontrol);
-       uint32_t dtm_version = get_field(dtmcontrol, DTMCONTROL_VERSION);
+
+       struct riscv_dmi_info dmi_info;
+       if (riscv_dmi_get_info(target, &dmi_info) != ERROR_OK)
+               return ERROR_FAIL;
+
+       uint32_t dtm_version = dmi_info.dtm_version;
        LOG_TARGET_DEBUG(target, "version=0x%" PRIx32, dtm_version);
 
        struct target_type *tt;
@@ -4797,22 +4862,17 @@ COMMAND_HANDLER(riscv_reset_delays)
        struct target *target = get_current_target(CMD_CTX);
        RISCV_INFO(r);
        r->reset_delays_wait = wait;
+       riscv_dtm_reset_delays(r->dtm, wait);
        return ERROR_OK;
 }
 
-COMMAND_HANDLER(riscv_set_ir)
+int riscv_dmi_jtag_set_ir(const char *ir_name, uint32_t value)
 {
-       if (CMD_ARGC != 2)
-               return ERROR_COMMAND_SYNTAX_ERROR;
-
-       uint32_t value;
-       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], value);
-
-       if (!strcmp(CMD_ARGV[0], "idcode"))
+       if (!strcmp(ir_name, "idcode"))
                buf_set_u32(ir_idcode, 0, 32, value);
-       else if (!strcmp(CMD_ARGV[0], "dtmcs"))
+       else if (!strcmp(ir_name, "dtmcs"))
                buf_set_u32(ir_dtmcontrol, 0, 32, value);
-       else if (!strcmp(CMD_ARGV[0], "dmi"))
+       else if (!strcmp(ir_name, "dmi"))
                buf_set_u32(ir_dbus, 0, 32, value);
        else
                return ERROR_FAIL;
@@ -4820,6 +4880,17 @@ COMMAND_HANDLER(riscv_set_ir)
        return ERROR_OK;
 }
 
+COMMAND_HANDLER(riscv_set_ir)
+{
+       if (CMD_ARGC != 2)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       uint32_t value;
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], value);
+
+       return riscv_dtm_set_ir(CMD_ARGV[0], value);
+}
+
 COMMAND_HANDLER(riscv_resume_order)
 {
        if (CMD_ARGC != 1)
@@ -4837,6 +4908,20 @@ COMMAND_HANDLER(riscv_resume_order)
        return ERROR_OK;
 }
 
+int riscv_dmi_jtag_use_bscan_tunnel(uint8_t irwidth, int tunnel_type)
+{
+       if (tunnel_type == BSCAN_TUNNEL_NESTED_TAP)
+               LOG_INFO("Nested Tap based Bscan Tunnel Selected");
+       else if (tunnel_type == BSCAN_TUNNEL_DATA_REGISTER)
+               LOG_INFO("Simple Register based Bscan Tunnel Selected");
+       else
+               LOG_INFO("Invalid Tunnel type selected ! : selecting default 
Nested Tap Type");
+
+       bscan_tunnel_type = tunnel_type;
+       bscan_tunnel_ir_width = irwidth;
+       return ERROR_OK;
+}
+
 COMMAND_HANDLER(riscv_use_bscan_tunnel)
 {
        uint8_t irwidth = 0;
@@ -4845,26 +4930,23 @@ COMMAND_HANDLER(riscv_use_bscan_tunnel)
        if (CMD_ARGC < 1 || CMD_ARGC > 2)
                return ERROR_COMMAND_SYNTAX_ERROR;
 
-       if (CMD_ARGC >= 1) {
-               COMMAND_PARSE_NUMBER(u8, CMD_ARGV[0], irwidth);
-               assert(BSCAN_TUNNEL_IR_WIDTH_NBITS < 8);
-               if (irwidth >= (uint8_t)1 << BSCAN_TUNNEL_IR_WIDTH_NBITS) {
-                       command_print(CMD, "'value' does not fit into %d bits.",
-                                       BSCAN_TUNNEL_IR_WIDTH_NBITS);
-                       return ERROR_COMMAND_ARGUMENT_OVERFLOW;
-               }
+       COMMAND_PARSE_NUMBER(u8, CMD_ARGV[0], irwidth);
+       assert(BSCAN_TUNNEL_IR_WIDTH_NBITS < 8);
+       if (irwidth >= (uint8_t)1 << BSCAN_TUNNEL_IR_WIDTH_NBITS) {
+               command_print(CMD, "'value' does not fit into %d bits.",
+                               BSCAN_TUNNEL_IR_WIDTH_NBITS);
+               return ERROR_COMMAND_ARGUMENT_OVERFLOW;
        }
        if (CMD_ARGC == 2)
                COMMAND_PARSE_NUMBER(int, CMD_ARGV[1], tunnel_type);
-       if (tunnel_type == BSCAN_TUNNEL_NESTED_TAP)
-               LOG_INFO("Nested Tap based Bscan Tunnel Selected");
-       else if (tunnel_type == BSCAN_TUNNEL_DATA_REGISTER)
-               LOG_INFO("Simple Register based Bscan Tunnel Selected");
-       else
-               LOG_INFO("Invalid Tunnel type selected ! : selecting default 
Nested Tap Type");
 
-       bscan_tunnel_type = tunnel_type;
-       bscan_tunnel_ir_width = irwidth;
+       return riscv_dtm_use_bscan_tunnel(irwidth, tunnel_type);
+}
+
+int riscv_dmi_jtag_set_bscan_tunnel_ir(int ir_id)
+{
+       LOG_INFO("Bscan tunnel IR 0x%x selected", ir_id);
+       bscan_tunnel_ir_id = ir_id;
        return ERROR_OK;
 }
 
@@ -4878,10 +4960,7 @@ COMMAND_HANDLER(riscv_set_bscan_tunnel_ir)
        if (CMD_ARGC == 1)
                COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], ir_id);
 
-       LOG_INFO("Bscan tunnel IR 0x%x selected", ir_id);
-
-       bscan_tunnel_ir_id = ir_id;
-       return ERROR_OK;
+       return riscv_dtm_set_bscan_tunnel_ir(ir_id);
 }
 
 COMMAND_HANDLER(riscv_set_maskisr)
diff --git a/src/target/riscv/riscv.h b/src/target/riscv/riscv.h
index 2a0a9b95f0..fdc353e026 100644
--- a/src/target/riscv/riscv.h
+++ b/src/target/riscv/riscv.h
@@ -4,6 +4,9 @@
 #define OPENOCD_TARGET_RISCV_RISCV_H
 
 struct riscv_program;
+struct riscv_dmi_backend_ops;
+struct riscv_dtm;
+struct adiv5_private_config;
 
 #include <stdint.h>
 #include "opcodes.h"
@@ -169,6 +172,7 @@ struct riscv_info {
        unsigned int common_magic;
 
        unsigned int dtm_version;
+       struct riscv_dtm *dtm;
 
        struct command_context *cmd_ctx;
        void *version_specific;
@@ -378,6 +382,8 @@ enum riscv_priv_mode {
 
 struct riscv_private_config {
        bool dcsr_ebreak_fields[N_RISCV_MODE];
+       struct riscv_dtm *dtm;
+       struct adiv5_private_config *adiv5_config;
 };
 
 static inline struct riscv_private_config
diff --git a/src/target/target.c b/src/target/target.c
index 1920de55ca..f4ea3dceca 100644
--- a/src/target/target.c
+++ b/src/target/target.c
@@ -709,7 +709,7 @@ static int jtag_enable_callback(enum jtag_event event, void 
*priv)
 {
        struct target *target = priv;
 
-       if (event != JTAG_TAP_EVENT_ENABLE || !target->tap->enabled)
+       if (event != JTAG_TAP_EVENT_ENABLE || !target->tap || 
!target->tap->enabled)
                return ERROR_OK;
 
        jtag_unregister_event_callback(jtag_enable_callback, target);
@@ -729,7 +729,7 @@ int target_examine(void)
 
        for (target = all_targets; target; target = target->next) {
                /* defer examination, but don't skip it */
-               if (!target->tap->enabled) {
+               if (target->tap && !target->tap->enabled) {
                        jtag_register_event_callback(jtag_enable_callback,
                                        target);
                        continue;
@@ -2788,7 +2788,7 @@ static int find_target(struct command_invocation *cmd, 
const char *name)
                command_print(cmd, "Target: %s is unknown, try one of:\n", 
name);
                return ERROR_FAIL;
        }
-       if (!target->tap->enabled) {
+       if (target->tap && !target->tap->enabled) {
                command_print(cmd, "Target: TAP %s is disabled, "
                         "can't be the current target\n",
                         target->tap->dotted_name);
@@ -2821,7 +2821,9 @@ COMMAND_HANDLER(handle_targets_command)
                const char *state;
                char marker = ' ';
 
-               if (target->tap->enabled)
+               if (!target->tap)
+                       state = target_state_name(target);
+               else if (target->tap->enabled)
                        state = target_state_name(target);
                else
                        state = "tap-disabled";
@@ -2837,7 +2839,7 @@ COMMAND_HANDLER(handle_targets_command)
                                target_name(target),
                                target_type_name(target),
                                nvp_value2name(nvp_target_endian, 
target->endianness)->name,
-                               target->tap->dotted_name,
+                               target->tap ? target->tap->dotted_name : 
"<none>",
                                state);
        }
 
@@ -2908,7 +2910,7 @@ static int sense_handler(void)
 
 static int handle_one_target(struct target *target)
 {
-       if (!target_active_polled(target) || !target->tap->enabled)
+       if (!target_active_polled(target) || (target->tap && 
!target->tap->enabled))
                return ERROR_OK;
 
        int res = target_poll(target);
@@ -3159,10 +3161,14 @@ COMMAND_HANDLER(handle_poll_command)
        if (CMD_ARGC == 0) {
                command_print(CMD, "background polling: %s",
                                jtag_poll_get_enabled() ? "on" : "off");
-               command_print(CMD, "TAP: %s (%s)",
-                               target->tap->dotted_name,
-                               target->tap->enabled ? "enabled" : "disabled");
-               if (!target->tap->enabled)
+               if (target->tap) {
+                       command_print(CMD, "TAP: %s (%s)",
+                                       target->tap->dotted_name,
+                                       target->tap->enabled ? "enabled" : 
"disabled");
+               } else {
+                       command_print(CMD, "TAP: <none>");
+               }
+               if (target->tap && !target->tap->enabled)
                        return ERROR_OK;
                retval = target_poll(target);
                if (retval != ERROR_OK)
@@ -5331,7 +5337,7 @@ COMMAND_HANDLER(handle_target_examine)
        }
 
        struct target *target = get_current_target(CMD_CTX);
-       if (!target->tap->enabled) {
+       if (target->tap && !target->tap->enabled) {
                command_print(CMD, "[TAP is disabled]");
                return ERROR_FAIL;
        }
@@ -5393,7 +5399,7 @@ COMMAND_HANDLER(handle_target_poll)
                return ERROR_COMMAND_SYNTAX_ERROR;
 
        struct target *target = get_current_target(CMD_CTX);
-       if (!target->tap->enabled) {
+       if (target->tap && !target->tap->enabled) {
                command_print(CMD, "[TAP is disabled]");
                return ERROR_FAIL;
        }
@@ -5420,7 +5426,7 @@ COMMAND_HANDLER(handle_target_reset)
        COMMAND_PARSE_NUMBER(int, CMD_ARGV[1], a);
 
        struct target *target = get_current_target(CMD_CTX);
-       if (!target->tap->enabled) {
+       if (target->tap && !target->tap->enabled) {
                command_print(CMD, "[TAP is disabled]");
                return ERROR_FAIL;
        }
@@ -5454,7 +5460,7 @@ COMMAND_HANDLER(handle_target_halt)
                return ERROR_COMMAND_SYNTAX_ERROR;
 
        struct target *target = get_current_target(CMD_CTX);
-       if (!target->tap->enabled) {
+       if (target->tap && !target->tap->enabled) {
                command_print(CMD, "[TAP is disabled]");
                return ERROR_FAIL;
        }
@@ -5477,7 +5483,7 @@ COMMAND_HANDLER(handle_target_wait_state)
        COMMAND_PARSE_NUMBER(uint, CMD_ARGV[1], a);
 
        struct target *target = get_current_target(CMD_CTX);
-       if (!target->tap->enabled) {
+       if (target->tap && !target->tap->enabled) {
                command_print(CMD, "[TAP is disabled]");
                return ERROR_FAIL;
        }
@@ -5920,14 +5926,20 @@ COMMAND_HANDLER(handle_target_create)
                                command_print(CMD, "-dap ?name? required when 
creating target");
                                retval = ERROR_COMMAND_ARGUMENT_INVALID;
                        }
+               } else if (target->has_dtm) {
+                       if (!target->dtm_configured) {
+                               command_print(CMD, "-dtm ?name? required when 
creating target");
+                               retval = ERROR_COMMAND_ARGUMENT_INVALID;
+                       }
                } else {
                        if (!target->tap_configured) {
                                command_print(CMD, "-chain-position ?name? 
required when creating target");
                                retval = ERROR_COMMAND_ARGUMENT_INVALID;
                        }
                }
-               /* tap must be set after target was configured */
-               if (!target->tap)
+               /* tap must be set after target was configured, unless the 
target
+                * uses a non-JTAG DTM. */
+               if (!target->tap && !target->has_dtm)
                        retval = ERROR_COMMAND_ARGUMENT_INVALID;
        }
 
diff --git a/src/target/target.h b/src/target/target.h
index ce1fc728be..48d8f24c5a 100644
--- a/src/target/target.h
+++ b/src/target/target.h
@@ -187,6 +187,8 @@ struct target {
                                                                                
 * currently. */
        bool has_dap;                                           /* set to true 
if target has ADIv5 support */
        bool dap_configured;                            /* set to true if ADIv5 
DAP is configured */
+       bool has_dtm;                                           /* set to true 
if target has RISC-V DTM support */
+       bool dtm_configured;                            /* set to true if 
RISC-V DTM is configured */
        bool tap_configured;                            /* set to true if JTAG 
tap has been configured
                                                                                
 * through -chain-position */
 
diff --git a/tcl/target/rp2350-riscv.cfg b/tcl/target/rp2350-riscv.cfg
new file mode 100644
index 0000000000..7f852a279c
--- /dev/null
+++ b/tcl/target/rp2350-riscv.cfg
@@ -0,0 +1,122 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# RP2350 Hazard3 RISC-V debug over SWD/ADIv6.
+# Rescue mode and mixed Cortex-M33/RISC-V switching are intentionally not
+# configured here.
+
+transport select swd
+
+source [find target/swj-dp.tcl]
+
+if { [info exists CHIPNAME] } {
+       set _CHIPNAME $CHIPNAME
+} else {
+       set _CHIPNAME rp2350
+}
+
+if { [info exists CPUTAPID] } {
+       set _CPUTAPID $CPUTAPID
+} else {
+       set _CPUTAPID 0x00040927
+}
+
+if { [info exists USE_CORE] } {
+       set _USE_CORE $USE_CORE
+} else {
+       set _USE_CORE { rv0 rv1 }
+}
+
+if { [info exists WORKAREASIZE] } {
+       set _WORKAREASIZE $WORKAREASIZE
+} else {
+       set _WORKAREASIZE 0x10000
+}
+
+if { [info exists FLASHSIZE] } {
+       set _FLASHSIZE $FLASHSIZE
+} else {
+       set _FLASHSIZE 0
+}
+
+proc _rp2350_riscv_reset_init { } {
+       set chip_id [format 0x%08x [read_memory 0x40000000 32 1]]
+
+       # RP2350 A0 needs the QSPI IO isolation removed before ROM flash calls.
+       switch $chip_id {
+               0x00004927 {
+                       mww 0x40030014 0
+                       mww 0x4003001c 0
+                       mww 0x40030024 0
+                       mww 0x4003002c 0
+                       mww 0x40030034 0
+                       mww 0x4003003c 0
+               }
+       }
+
+       rp2xxx rom_api_call FC
+}
+
+swj_newdap $_CHIPNAME cpu -expected-id $_CPUTAPID
+
+if { [info exists SWD_MULTIDROP] } {
+       dap create $_CHIPNAME.dap -adiv6 -chain-position $_CHIPNAME.cpu \
+               -dp-id 0x0040927 -instance-id 0
+} else {
+       dap create $_CHIPNAME.dap -adiv6 -chain-position $_CHIPNAME.cpu
+}
+
+if { [lsearch $_USE_CORE rv0] >= 0 } {
+       set _TARGETNAME_RV0 $_CHIPNAME.rv0
+       target create $_TARGETNAME_RV0 riscv -dap $_CHIPNAME.dap -ap-num 0xa000 
-coreid 0
+       $_TARGETNAME_RV0 riscv virt2phys_mode off
+       $_TARGETNAME_RV0 configure -work-area-phys 0x20010000 \
+               -work-area-size $_WORKAREASIZE -work-area-backup 0
+}
+
+if { [lsearch $_USE_CORE rv1] >= 0 } {
+       set _TARGETNAME_RV1 $_CHIPNAME.rv1
+       target create $_TARGETNAME_RV1 riscv -dap $_CHIPNAME.dap -ap-num 0xa000 
-coreid 1
+       $_TARGETNAME_RV1 riscv virt2phys_mode off
+       $_TARGETNAME_RV1 configure -work-area-phys 0x20010000 \
+               -work-area-size $_WORKAREASIZE -work-area-backup 0
+}
+
+if { [info exists USE_SMP] } {
+       set _USE_SMP $USE_SMP
+} else {
+       set _USE_SMP 0
+}
+
+if { $_USE_SMP } {
+       $_TARGETNAME_RV0 configure -rtos hwthread
+       $_TARGETNAME_RV1 configure -rtos hwthread
+       target smp $_TARGETNAME_RV0 $_TARGETNAME_RV1
+}
+
+if { [info exists _TARGETNAME_RV0] } {
+       set _FLASH_TARGET $_TARGETNAME_RV0
+} elseif { [info exists _TARGETNAME_RV1] } {
+       set _FLASH_TARGET $_TARGETNAME_RV1
+}
+
+if { [info exists _FLASH_TARGET] } {
+       # QSPI flash size detection during GDB connect requires backing up RAM.
+       set _WKA_BACKUP [expr { $_FLASHSIZE == 0 }]
+       $_FLASH_TARGET configure -event reset-init "_rp2350_riscv_reset_init"
+       $_FLASH_TARGET configure -work-area-phys 0x20010000 \
+               -work-area-size $_WORKAREASIZE -work-area-backup $_WKA_BACKUP
+
+       set _FLASHNAME $_CHIPNAME.flash
+       flash bank $_FLASHNAME rp2xxx 0x10000000 $_FLASHSIZE 0 0 $_FLASH_TARGET
+}
+
+if { [info exists _TARGETNAME_RV1] && [info exists _FLASHNAME] } {
+       # Alias to ensure GDB connecting to core 1 gets the correct memory map.
+       flash bank $_CHIPNAME.alias virtual 0x10000000 0 0 0 $_TARGETNAME_RV1 
$_FLASHNAME
+}
+
+if { [info exists _TARGETNAME_RV0] } {
+       targets $_TARGETNAME_RV0
+} elseif { [info exists _TARGETNAME_RV1] } {
+       targets $_TARGETNAME_RV1
+}

-- 

Reply via email to