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/+/9586
-- gerrit commit e37057465e6fe31036447986ec47cbf28db35684 Author: William Markezana <[email protected]> Date: Fri Apr 10 12:45:43 2026 -0400 jtag/drivers: add T-Head CK-Link JTAG adapter driver Add support for the T-Head CK-Link Lite V2 probe family, including the Bouffalo Lab BL616/BL702 CK-Link clones used as built-in debuggers on BL618/BL702 evaluation boards. The wire protocol is a framed command format over USB bulk endpoints, reverse-engineered from T-Head's DebugServer binary. The driver implements: - Full DebugServer init sequence: selfreg setup, operational clock, 5-wire -> 2-wire -> 5-wire mode toggle (effective TAP reset). - JTAG_SCAN via the probe's single-entry batch opcode with IR caching across execute_queue boundaries; end-of-queue flush populates the IR scan's in_value with hardware-captured Capture-IR data. - Opcode-aware response-frame checksum validation. Selfreg reads use sum-mod-256; JTAG batch responses exclude the last dr_tdo byte. - Clock-divider control (freq_kHz = 48000 / (N+1)) with once-warning clamp when the requested speed exceeds the DebugServer-validated ceiling (2526 kHz). - libusb serial-number filter via adapter_get_required_serial() and kernel-driver auto-detach on Linux. - TCL command 'cklink vid_pid (vid pid)+' to override defaults. Known protocol limitations, surfaced as one-time warnings rather than silent data loss: - Scans exceed 255 bits: the batch opcode encodes bit-counts in single bytes. Clamp + TDI pass-through for the un-shifted portion, which gives OpenOCD's bypass chain-length probe the expected end-of-chain marker. - Back-to-back IR scans: the batch opcode requires both IR and DR phases; mid-queue flushing a deferred IR with a dummy DR would trigger spurious DMI transactions. Single-TAP chains only. - JTAG_TMS, JTAG_PATHMOVE: no TMS-sequence opcode exposed. Return ERROR_JTAG_NOT_IMPLEMENTED. - RTCK: not supported, return ERROR_JTAG_NOT_IMPLEMENTED. Tested against BL618 (VID 0x42bf, PID 0xb210) and BL702 eval boards: IDCODE read, RISC-V DM examine, halt, register read, memory read, single-step, hardware breakpoints, GDB RSP server all functional. Change-Id: If6adaec0445748f3e97078bf980c502eff4d5001 Signed-off-by: William Markezana <[email protected]> diff --git a/NEWS b/NEWS index 9db6c5feee..4b77a446d1 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ This file includes highlights of the changes made in the OpenOCD source archive release. JTAG Layer: + * Added support for T-Head CK-Link Lite JTAG adapters, including the + Bouffalo Lab BL616/BL702 clone probes (BL618 eval board debugger). Boundary Scan: diff --git a/configure.ac b/configure.ac index 0d5f0eb354..515f4af119 100644 --- a/configure.ac +++ b/configure.ac @@ -128,6 +128,7 @@ m4_define([USB1_ADAPTERS], [[[ftdi], [MPSSE mode of FTDI based devices], [FTDI]], [[ftdi_cjtag], [cJTAG (OScan1, JScan3) tunneled thru MPSSE], [FTDI_CJTAG]], [[ch347], [CH347 based devices], [CH347]], + [[cklink], [T-Head CK-Link JTAG Probe], [CKLINK]], [[stlink], [ST-Link Programmer], [HLADAPTER_STLINK]], [[ti_icdi], [TI ICDI JTAG Programmer], [HLADAPTER_ICDI]], [[ulink], [Keil ULINK JTAG Programmer], [ULINK]], diff --git a/contrib/60-openocd.rules b/contrib/60-openocd.rules index 151b4e2c06..0f49e1a2c0 100644 --- a/contrib/60-openocd.rules +++ b/contrib/60-openocd.rules @@ -269,4 +269,8 @@ ATTRS{idVendor}=="c251", ATTRS{idProduct}=="2750", MODE="660", GROUP="plugdev", # CMSIS-DAP compatible adapters ATTRS{product}=="*CMSIS-DAP*", MODE="660", GROUP="plugdev", TAG+="uaccess" +# T-Head CK-Link Lite and Bouffalo Lab BL616/BL702 clones +ATTRS{idVendor}=="32bf", ATTRS{idProduct}=="b210", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="42bf", ATTRS{idProduct}=="b210", MODE="660", GROUP="plugdev", TAG+="uaccess" + LABEL="openocd_rules_end" diff --git a/doc/openocd.texi b/doc/openocd.texi index a21a3e4bda..d016cc5d3c 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -2615,6 +2615,24 @@ ch347 device_desc "EasyDevKit" @end deffn @end deffn +@deffn {Interface Driver} {cklink} +Driver for the T-Head CK-Link Lite V2 JTAG probe and Bouffalo Lab +BL616/BL702 clones, including the built-in debugger on Bouffalo BL618 +evaluation boards. Communication is over USB bulk endpoints using a +T-Head DebugServer-compatible framed command protocol. + +The driver only supports JTAG transport and a single-TAP chain. The +probe runs at a fixed clock; @command{adapter speed} requests are +accepted but ignored (with a one-time warning). @command{JTAG_TMS} and +@command{JTAG_PATHMOVE} are not supported by this probe's protocol. + +@deffn {Config Command} {cklink vid_pid} [vid pid]+ +Override the built-in VID/PID table. By default the driver matches +0x42bf/0xb210 (Bouffalo) and 0x32bf/0xb210 (T-Head). Up to eight +@var{vid}/@var{pid} pairs may be specified. +@end deffn +@end deffn + @deffn {Interface Driver} {cmsis-dap} ARM CMSIS-DAP compliant based adapter v1 (USB HID based) or v2 (USB bulk or TCP/IP). diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am index 5216e5d774..ad08b0e0c8 100644 --- a/src/jtag/drivers/Makefile.am +++ b/src/jtag/drivers/Makefile.am @@ -213,6 +213,9 @@ endif if CH347 DRIVERFILES += %D%/ch347.c endif +if CKLINK +DRIVERFILES += %D%/cklink.c +endif DRIVERHEADERS = \ %D%/bitbang.h \ diff --git a/src/jtag/drivers/cklink.c b/src/jtag/drivers/cklink.c new file mode 100644 index 0000000000..efbbd85a44 --- /dev/null +++ b/src/jtag/drivers/cklink.c @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/*************************************************************************** + * T-Head CK-Link JTAG adapter driver. * + * * + * Supports the CK-Link Lite V2 wire protocol used by T-Head probes and * + * Bouffalo Lab BL616/BL702 CK-Link clones. USB bulk with a framed * + * command protocol reverse-engineered from T-Head's DebugServer. * + * * + * Copyright (C) 2026 by William Markezana * + * <[email protected]> * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> + +#include <jtag/adapter.h> +#include <jtag/interface.h> +#include <jtag/commands.h> +#include <helper/binarybuffer.h> +#include <helper/bits.h> +#include <helper/time_support.h> +#include "libusb_helper.h" + +/* USB identification. VID/PID table is extendable via `cklink vid_pid`. */ +#define CKLINK_VID_BOUFFALO 0x42bf +#define CKLINK_VID_THEAD 0x32bf +#define CKLINK_PID_LITE_V2 0xb210 +#define CKLINK_MAX_VID_PIDS 8 + +#define CKLINK_USB_INTERFACE 0 +#define CKLINK_EP_OUT 0x02 +#define CKLINK_EP_IN 0x81 +#define CKLINK_USB_TIMEOUT_MS 1000 +#define CKLINK_USB_BUF_SIZE 2048 + +/* Wire-protocol framing bytes */ +#define CKLINK_FRAME_START 0x68 +#define CKLINK_FRAME_END 0x16 + +/* Response frame layout: start + status + payload + checksum + end */ +#define CKLINK_FRAME_OVERHEAD 4 +#define CKLINK_RESP_PAYLOAD_OFFSET 2 + +/* Opcodes */ +#define CKLINK_OP_SELFREG_WRITE 0x06 +#define CKLINK_OP_SELFREG_READ 0x87 +#define CKLINK_OP_JTAG_BATCH 0x88 + +/* Self-register indices */ +#define CKLINK_SR_CSR 0 /* clock divider + CDI mode */ +#define CKLINK_SR_MTCR_WAIT 1 /* target-comm wait cycles */ +#define CKLINK_SR_JTAG_CONFIG 8 /* JTAG config word */ +#define CKLINK_SELFREG_BYTES 4 + +/* + * Init values replayed from a captured DebugServer session. + * sr0 (CSR): byte 0 = clock divider, byte 3 = CDI mode (0x60=5-wire JTAG, + * 0x61=2-wire cJTAG). The 5->2->5 mode toggle at end of init acts as a + * TAP reset: the wire driver re-initializes on each switch. + * sr1 MTCR_WAIT = 1000 cycles. sr8 JTAG_CONFIG: opaque, known-good. + */ +#define CKLINK_SR0_SETUP_CLK_BYTE 0x17 /* ~2000 kHz */ +#define CKLINK_SR0_RUN_CLK_BYTE 0x12 /* ~2526 kHz, DebugServer default */ +#define CKLINK_SR0_MODE_JTAG 0x60 +#define CKLINK_SR0_MODE_CJTAG 0x61 +#define CKLINK_SR1_INIT_VALUE 0x000003e8 +#define CKLINK_SR8_INIT_VALUE 0x00622250 + +/* freq_kHz = 48000 / (N + 1); cap at DebugServer-validated 0x12. */ +#define CKLINK_CLK_SOURCE_KHZ 48000 +#define CKLINK_CLK_DIV_MIN CKLINK_SR0_RUN_CLK_BYTE +#define CKLINK_CLK_DIV_MAX 0xff + +#define CKLINK_CSR(clk, mode) (((uint32_t)(mode) << 24) | (clk)) + +/* Batch opcode encodes IR/DR bit-counts in single bytes. */ +#define CKLINK_MAX_SCAN_BITS 255 +#define CKLINK_MAX_SCAN_BYTES 32 /* DIV_ROUND_UP(255, 8) */ + +/* Synthesized IR when a DR scan arrives without a cached IR. */ +#define CKLINK_PLACEHOLDER_IR_BITS 5 +#define CKLINK_PLACEHOLDER_IR_VALUE 0x01 + +struct cklink_ctx { + struct libusb_device_handle *usb_dev; + uint8_t tx_buf[CKLINK_USB_BUF_SIZE]; + uint8_t rx_buf[CKLINK_USB_BUF_SIZE]; + uint8_t csr_clk; + uint8_t ir_value[CKLINK_MAX_SCAN_BYTES]; + unsigned int ir_bits; + struct jtag_command *pending_ir_cmd; + uint8_t *scan_tdi; + uint8_t *scan_tdo; + size_t scan_buf_size; + bool long_scan_warned; + bool dropped_ir_warned; +}; + +static struct cklink_ctx *cklink_handle; + +/* VID/PID table, NULL-terminated pairs; extendable via `cklink vid_pid`. */ +static uint16_t cklink_vids[CKLINK_MAX_VID_PIDS + 1] = { + CKLINK_VID_BOUFFALO, + CKLINK_VID_THEAD, +}; +static uint16_t cklink_pids[CKLINK_MAX_VID_PIDS + 1] = { + CKLINK_PID_LITE_V2, + CKLINK_PID_LITE_V2, +}; + +/* Sum-mod-256. */ +static uint8_t cklink_checksum(const uint8_t *buf, int len) +{ + unsigned int sum = 0; + for (int i = 0; i < len; i++) + sum += buf[i]; + return (uint8_t)(sum & 0xff); +} + +/* Send a framed command; NULL rxlen means fire-and-forget (selfreg writes + * are not acknowledged). @p opcode selects the response-checksum rule. */ +static int cklink_usb_xfer(struct cklink_ctx *ck, uint8_t opcode, + int txlen, int *rxlen) +{ + int actual = 0; + int ret = jtag_libusb_bulk_write(ck->usb_dev, CKLINK_EP_OUT, + (char *)ck->tx_buf, txlen, + CKLINK_USB_TIMEOUT_MS, &actual); + if (ret != ERROR_OK || actual != txlen) { + LOG_ERROR("CK-Link bulk write failed (ret=%d, actual=%d/%d)", + ret, actual, txlen); + return ERROR_FAIL; + } + + if (!rxlen) + return ERROR_OK; + + ret = jtag_libusb_bulk_read(ck->usb_dev, CKLINK_EP_IN, + (char *)ck->rx_buf, CKLINK_USB_BUF_SIZE, + CKLINK_USB_TIMEOUT_MS, &actual); + if (ret != ERROR_OK || actual < CKLINK_FRAME_OVERHEAD) { + LOG_ERROR("CK-Link bulk read failed (ret=%d, actual=%d)", + ret, actual); + return ERROR_FAIL; + } + if (ck->rx_buf[0] != CKLINK_FRAME_START) { + LOG_ERROR("CK-Link bad response start byte 0x%02x", + ck->rx_buf[0]); + return ERROR_FAIL; + } + if (ck->rx_buf[actual - 1] != CKLINK_FRAME_END) { + LOG_ERROR("CK-Link bad response end byte 0x%02x", + ck->rx_buf[actual - 1]); + return ERROR_FAIL; + } + /* + * RX checksum is opcode-specific: selfreg reads sum all payload; + * single-entry JTAG batch sums all except the last byte of dr_tdo. + */ + int csum_end = (opcode == CKLINK_OP_JTAG_BATCH) ? actual - 3 + : actual - 2; + uint8_t computed = cklink_checksum(ck->rx_buf, csum_end); + if (computed != ck->rx_buf[actual - 2]) { + LOG_ERROR("CK-Link response checksum mismatch: op=0x%02x stated=0x%02x computed=0x%02x", + opcode, ck->rx_buf[actual - 2], computed); + return ERROR_FAIL; + } + /* Status byte semantics undocumented; log non-zero for traces. */ + if (ck->rx_buf[1] != 0) { + LOG_DEBUG("CK-Link op=0x%02x returned status=0x%02x", + opcode, ck->rx_buf[1]); + } + *rxlen = actual; + return ERROR_OK; +} + +static int cklink_frame_start(struct cklink_ctx *ck, uint8_t opcode) +{ + ck->tx_buf[0] = CKLINK_FRAME_START; + ck->tx_buf[1] = opcode; + return 2; +} + +static int cklink_frame_end(struct cklink_ctx *ck, int pos) +{ + assert(pos + 2 <= CKLINK_USB_BUF_SIZE); + ck->tx_buf[pos] = cklink_checksum(ck->tx_buf, pos); + ck->tx_buf[pos + 1] = CKLINK_FRAME_END; + return pos + 2; +} + +static int cklink_selfreg_write(struct cklink_ctx *ck, uint8_t reg, + const uint8_t *value) +{ + int pos = cklink_frame_start(ck, CKLINK_OP_SELFREG_WRITE); + ck->tx_buf[pos++] = reg; + memcpy(&ck->tx_buf[pos], value, CKLINK_SELFREG_BYTES); + pos += CKLINK_SELFREG_BYTES; + pos = cklink_frame_end(ck, pos); + return cklink_usb_xfer(ck, CKLINK_OP_SELFREG_WRITE, pos, NULL); +} + +static int cklink_selfreg_read(struct cklink_ctx *ck, uint8_t reg, uint8_t *value) +{ + int pos = cklink_frame_start(ck, CKLINK_OP_SELFREG_READ); + ck->tx_buf[pos++] = reg; + pos = cklink_frame_end(ck, pos); + + int rxlen = 0; + int ret = cklink_usb_xfer(ck, CKLINK_OP_SELFREG_READ, pos, &rxlen); + if (ret != ERROR_OK) + return ret; + + const int expected = CKLINK_FRAME_OVERHEAD + CKLINK_SELFREG_BYTES; + if (rxlen < expected) { + LOG_ERROR("CK-Link selfreg read response too short (%d < %d)", + rxlen, expected); + return ERROR_FAIL; + } + memcpy(value, &ck->rx_buf[CKLINK_RESP_PAYLOAD_OFFSET], + CKLINK_SELFREG_BYTES); + return ERROR_OK; +} + +/* Single-entry batch scan. Scans > 255 bits are clamped with a one-time + * warning: chunking across entries breaks Shift-DR continuity, and + * erroring out here breaks OpenOCD's chain probe's -expected-id recovery. */ +static int cklink_jtag_scan(struct cklink_ctx *ck, + unsigned int ir_bits, const uint8_t *ir_tdi, + uint8_t *ir_tdo, + unsigned int dr_bits, const uint8_t *dr_tdi, + uint8_t *dr_tdo) +{ + if ((ir_bits > CKLINK_MAX_SCAN_BITS || dr_bits > CKLINK_MAX_SCAN_BITS) + && !ck->long_scan_warned) { + LOG_WARNING("CK-Link: scan exceeds %u-bit per-entry limit (ir=%u dr=%u); clamping", + CKLINK_MAX_SCAN_BITS, ir_bits, dr_bits); + ck->long_scan_warned = true; + } + if (ir_bits > CKLINK_MAX_SCAN_BITS) + ir_bits = CKLINK_MAX_SCAN_BITS; + if (dr_bits > CKLINK_MAX_SCAN_BITS) + dr_bits = CKLINK_MAX_SCAN_BITS; + + const unsigned int ir_bytes = DIV_ROUND_UP(ir_bits, 8); + const unsigned int dr_bytes = DIV_ROUND_UP(dr_bits, 8); + + int pos = cklink_frame_start(ck, CKLINK_OP_JTAG_BATCH); + ck->tx_buf[pos++] = 0; /* entry_count - 1 */ + ck->tx_buf[pos++] = (uint8_t)ir_bits; + if (ir_tdi) + memcpy(&ck->tx_buf[pos], ir_tdi, ir_bytes); + else + memset(&ck->tx_buf[pos], 0, ir_bytes); + pos += ir_bytes; + ck->tx_buf[pos++] = (uint8_t)dr_bits; + if (dr_tdi) + memcpy(&ck->tx_buf[pos], dr_tdi, dr_bytes); + else + memset(&ck->tx_buf[pos], 0, dr_bytes); + pos += dr_bytes; + pos = cklink_frame_end(ck, pos); + + int rxlen = 0; + int ret = cklink_usb_xfer(ck, CKLINK_OP_JTAG_BATCH, pos, &rxlen); + if (ret != ERROR_OK) + return ret; + + /* Response: start + status + ir_tdo + dr_echo + dr_tdo + csum + end. */ + const unsigned int expected = + CKLINK_FRAME_OVERHEAD + ir_bytes + 1 + dr_bytes; + if ((unsigned int)rxlen < expected) { + LOG_ERROR("CK-Link scan response truncated: got %d, need %u (ir=%u dr=%u)", + rxlen, expected, ir_bits, dr_bits); + return ERROR_FAIL; + } + + if (ir_tdo && ir_bytes) + memcpy(ir_tdo, &ck->rx_buf[CKLINK_RESP_PAYLOAD_OFFSET], ir_bytes); + + const unsigned int dr_tdo_offset = + CKLINK_RESP_PAYLOAD_OFFSET + ir_bytes + 1; + if (dr_tdo) + memcpy(dr_tdo, &ck->rx_buf[dr_tdo_offset], dr_bytes); + + return ERROR_OK; +} + +/* Shift num_bits of idle cycles, chunked to the 255-bit protocol limit. */ +static int cklink_idle_cycles(struct cklink_ctx *ck, unsigned int num_bits) +{ + static const uint8_t zeros[CKLINK_MAX_SCAN_BYTES] = { 0 }; + + while (num_bits > 0) { + unsigned int chunk = num_bits > CKLINK_MAX_SCAN_BITS + ? CKLINK_MAX_SCAN_BITS : num_bits; + int ret = cklink_jtag_scan(ck, 0, NULL, NULL, + chunk, zeros, NULL); + if (ret != ERROR_OK) + return ret; + num_bits -= chunk; + } + return ERROR_OK; +} + +/* Flush a pending IR with a 1-bit dummy DR so its in_value gets populated + * with real captured IR TDO. Called at end of execute_queue so Capture-IR + * validation sees real hardware data. */ +static int cklink_flush_pending_ir(struct cklink_ctx *ck) +{ + if (!ck || !ck->pending_ir_cmd) + return ERROR_OK; + + uint8_t ir_tdo[CKLINK_MAX_SCAN_BYTES] = { 0 }; + uint8_t dummy_dr = 0; + int ret = cklink_jtag_scan(ck, ck->ir_bits, ck->ir_value, ir_tdo, + 1, &dummy_dr, NULL); + if (ret != ERROR_OK) + return ret; + + struct scan_command *scan = ck->pending_ir_cmd->cmd.scan; + unsigned int off = 0; + for (unsigned int i = 0; i < scan->num_fields; i++) { + struct scan_field *f = &scan->fields[i]; + if (f->in_value) { + buf_set_buf(ir_tdo, off, f->in_value, 0, + f->num_bits); + } + off += f->num_bits; + } + ck->pending_ir_cmd = NULL; + /* ir_bits/ir_value stay valid: hardware IR is still what we cached. */ + return ERROR_OK; +} + +static int cklink_execute_scan(struct cklink_ctx *ck, struct jtag_command *cmd) +{ + struct scan_command *scan = cmd->cmd.scan; + + unsigned int total_bits = 0; + for (unsigned int i = 0; i < scan->num_fields; i++) + total_bits += scan->fields[i].num_bits; + + if (total_bits == 0) + return ERROR_OK; + + const unsigned int total_bytes = DIV_ROUND_UP(total_bits, 8); + if (total_bytes > ck->scan_buf_size) { + uint8_t *ntdi = realloc(ck->scan_tdi, total_bytes); + uint8_t *ntdo = realloc(ck->scan_tdo, total_bytes); + if (!ntdi || !ntdo) { + LOG_ERROR("CK-Link: out of memory for %u-bit scan", + total_bits); + /* Partial success is fine; freed in cklink_quit. */ + if (ntdi) + ck->scan_tdi = ntdi; + if (ntdo) + ck->scan_tdo = ntdo; + return ERROR_FAIL; + } + ck->scan_tdi = ntdi; + ck->scan_tdo = ntdo; + ck->scan_buf_size = total_bytes; + } + memset(ck->scan_tdi, 0, total_bytes); + memset(ck->scan_tdo, 0, total_bytes); + + unsigned int bit_offset = 0; + for (unsigned int i = 0; i < scan->num_fields; i++) { + struct scan_field *field = &scan->fields[i]; + if (field->out_value) { + buf_set_buf(field->out_value, 0, ck->scan_tdi, + bit_offset, field->num_bits); + } + bit_offset += field->num_bits; + } + + if (scan->ir_scan) { + /* + * Defer IR until paired with a DR (batch needs both). + * Back-to-back IR drops the first: mid-queue flush would + * cycle Capture-DR/Update-DR on the previous IR's register + * (e.g. DMI), triggering spurious transactions. + */ + if (total_bytes > sizeof(ck->ir_value)) { + LOG_ERROR("CK-Link: IR scan of %u bits exceeds cache (%zu bytes)", + total_bits, sizeof(ck->ir_value)); + return ERROR_JTAG_QUEUE_FAILED; + } + if (ck->pending_ir_cmd) { + if (!ck->dropped_ir_warned) { + LOG_WARNING("CK-Link: back-to-back IR scan dropped pending IR; multi-TAP chains are not supported"); + ck->dropped_ir_warned = true; + } else { + LOG_DEBUG("CK-Link: dropping pending IR (%u bits) before new IR scan", + ck->ir_bits); + } + } + memcpy(ck->ir_value, ck->scan_tdi, total_bytes); + ck->ir_bits = total_bits; + ck->pending_ir_cmd = cmd; + return ERROR_OK; + } + + /* No cached IR yet: inject IDCODE placeholder so chain detection works. */ + const uint8_t placeholder_ir = CKLINK_PLACEHOLDER_IR_VALUE; + const unsigned int eff_ir_bits = ck->ir_bits ? ck->ir_bits + : CKLINK_PLACEHOLDER_IR_BITS; + const uint8_t *eff_ir_value = ck->ir_bits ? ck->ir_value : &placeholder_ir; + if (!ck->ir_bits) + LOG_DEBUG("CK-Link: DR scan with no cached IR; injecting IDCODE placeholder"); + + uint8_t ir_tdo_buf[CKLINK_MAX_SCAN_BYTES] = { 0 }; + int ret = cklink_jtag_scan(ck, eff_ir_bits, eff_ir_value, ir_tdo_buf, + total_bits, ck->scan_tdi, ck->scan_tdo); + if (ret != ERROR_OK) + return ret; + + /* Scatter captured IR TDO back into the deferred IR command's fields. */ + if (ck->pending_ir_cmd) { + struct scan_command *ir_scan = ck->pending_ir_cmd->cmd.scan; + unsigned int ir_off = 0; + for (unsigned int i = 0; i < ir_scan->num_fields; i++) { + struct scan_field *f = &ir_scan->fields[i]; + if (f->in_value) { + buf_set_buf(ir_tdo_buf, ir_off, + f->in_value, 0, f->num_bits); + } + ir_off += f->num_bits; + } + ck->pending_ir_cmd = NULL; + } + + /* + * For clamped scans, fill un-shifted TDO with caller's TDI (models + * "no more TAPs past the limit" - a bare wire). Gives OpenOCD's + * bypass chain-length probe the expected end-of-chain marker. + */ + if (total_bits > CKLINK_MAX_SCAN_BITS) { + buf_set_buf(ck->scan_tdi, CKLINK_MAX_SCAN_BITS, + ck->scan_tdo, CKLINK_MAX_SCAN_BITS, + total_bits - CKLINK_MAX_SCAN_BITS); + } + + bit_offset = 0; + for (unsigned int i = 0; i < scan->num_fields; i++) { + struct scan_field *field = &scan->fields[i]; + if (field->in_value) { + buf_set_buf(ck->scan_tdo, bit_offset, + field->in_value, 0, field->num_bits); + } + bit_offset += field->num_bits; + } + + return ERROR_OK; +} + +static int cklink_write_csr(struct cklink_ctx *ck, uint8_t clk, uint8_t mode) +{ + uint8_t buf[CKLINK_SELFREG_BYTES]; + h_u32_to_le(buf, CKLINK_CSR(clk, mode)); + return cklink_selfreg_write(ck, CKLINK_SR_CSR, buf); +} + +/* khz -> sr0 byte 0 divider, rounded up so we never exceed the request. */ +static uint8_t cklink_khz_to_div(int khz) +{ + if (khz <= 0) + return CKLINK_CLK_DIV_MAX; + int n = ((CKLINK_CLK_SOURCE_KHZ + khz - 1) / khz) - 1; + if (n < CKLINK_CLK_DIV_MIN) + n = CKLINK_CLK_DIV_MIN; + if (n > CKLINK_CLK_DIV_MAX) + n = CKLINK_CLK_DIV_MAX; + return (uint8_t)n; +} + +static int cklink_execute_tlr_reset(struct cklink_ctx *ck) +{ + /* Probe-side reset via 5->2->5 mode toggle; target TAP reset not guaranteed. */ + int ret; + + ret = cklink_write_csr(ck, ck->csr_clk, CKLINK_SR0_MODE_CJTAG); + if (ret != ERROR_OK) + return ret; + ret = cklink_write_csr(ck, ck->csr_clk, CKLINK_SR0_MODE_CJTAG); + if (ret != ERROR_OK) + return ret; + ret = cklink_write_csr(ck, ck->csr_clk, CKLINK_SR0_MODE_JTAG); + if (ret != ERROR_OK) + return ret; + + ck->ir_bits = 0; + memset(ck->ir_value, 0, sizeof(ck->ir_value)); + ck->pending_ir_cmd = NULL; + tap_set_state(TAP_RESET); + return ERROR_OK; +} + +static int cklink_execute_runtest(struct cklink_ctx *ck, struct jtag_command *cmd) +{ + return cklink_idle_cycles(ck, cmd->cmd.runtest->num_cycles); +} + +static int cklink_execute_stableclocks(struct cklink_ctx *ck, struct jtag_command *cmd) +{ + /* TMS=0 shifts only hold state in RTI; other stable states would advance. */ + return cklink_idle_cycles(ck, cmd->cmd.stableclocks->num_cycles); +} + +static int cklink_execute_tms(struct jtag_command *cmd) +{ + /* No TMS-sequence opcode: honoring JTAG_TMS is impossible. */ + LOG_ERROR("CK-Link: JTAG_TMS (%u bits) is not supported by this probe", + cmd->cmd.tms->num_bits); + return ERROR_JTAG_NOT_IMPLEMENTED; +} + +static int cklink_execute_reset(struct cklink_ctx *ck, struct jtag_command *cmd) +{ + /* Only TRST is meaningful here: we have no separate SRST control. */ + if (cmd->cmd.reset->trst) + return cklink_execute_tlr_reset(ck); + return ERROR_OK; +} + +static int cklink_execute_queue(struct jtag_command *cmd_queue) +{ + struct cklink_ctx *ck = cklink_handle; + if (!ck) + return ERROR_JTAG_INIT_FAILED; + + for (struct jtag_command *cmd = cmd_queue; cmd; cmd = cmd->next) { + int ret; + + switch (cmd->type) { + case JTAG_SCAN: + ret = cklink_execute_scan(ck, cmd); + break; + case JTAG_TLR_RESET: + ret = cklink_execute_tlr_reset(ck); + break; + case JTAG_RUNTEST: + ret = cklink_execute_runtest(ck, cmd); + break; + case JTAG_RESET: + ret = cklink_execute_reset(ck, cmd); + break; + case JTAG_PATHMOVE: + /* No TMS-sequence opcode: honoring PATHMOVE is impossible. */ + LOG_ERROR("CK-Link: JTAG_PATHMOVE is not supported by this probe"); + ret = ERROR_JTAG_NOT_IMPLEMENTED; + break; + case JTAG_STABLECLOCKS: + ret = cklink_execute_stableclocks(ck, cmd); + break; + case JTAG_TMS: + ret = cklink_execute_tms(cmd); + break; + case JTAG_SLEEP: + jtag_sleep(cmd->cmd.sleep->us); + ret = ERROR_OK; + break; + default: + LOG_ERROR("CK-Link: unsupported JTAG command %d", + cmd->type); + return ERROR_JTAG_QUEUE_FAILED; + } + if (ret != ERROR_OK) + return ret; + } + /* Flush so any un-paired IR's in_value is populated before OpenOCD reads. */ + return cklink_flush_pending_ir(ck); +} + +static int cklink_probe_init(struct cklink_ctx *ck) +{ + uint8_t sr1[CKLINK_SELFREG_BYTES]; + uint8_t sr8[CKLINK_SELFREG_BYTES]; + uint8_t readback[CKLINK_SELFREG_BYTES]; + int ret; + + h_u32_to_le(sr1, CKLINK_SR1_INIT_VALUE); + h_u32_to_le(sr8, CKLINK_SR8_INIT_VALUE); + + /* DebugServer init sequence, replayed verbatim. */ + ret = cklink_write_csr(ck, CKLINK_SR0_SETUP_CLK_BYTE, + CKLINK_SR0_MODE_JTAG); + if (ret != ERROR_OK) + return ret; + ret = cklink_selfreg_write(ck, CKLINK_SR_MTCR_WAIT, sr1); + if (ret != ERROR_OK) + return ret; + ret = cklink_selfreg_write(ck, CKLINK_SR_JTAG_CONFIG, sr8); + if (ret != ERROR_OK) + return ret; + + ret = cklink_write_csr(ck, CKLINK_SR0_RUN_CLK_BYTE, + CKLINK_SR0_MODE_JTAG); + if (ret != ERROR_OK) + return ret; + + /* 5->2->5 mode toggle: effective TAP reset. */ + ret = cklink_write_csr(ck, CKLINK_SR0_RUN_CLK_BYTE, + CKLINK_SR0_MODE_CJTAG); + if (ret != ERROR_OK) + return ret; + ret = cklink_write_csr(ck, CKLINK_SR0_RUN_CLK_BYTE, + CKLINK_SR0_MODE_CJTAG); + if (ret != ERROR_OK) + return ret; + ret = cklink_write_csr(ck, CKLINK_SR0_RUN_CLK_BYTE, + CKLINK_SR0_MODE_JTAG); + if (ret != ERROR_OK) + return ret; + + ret = cklink_selfreg_read(ck, CKLINK_SR_CSR, readback); + if (ret != ERROR_OK) + return ret; + if (readback[3] != CKLINK_SR0_MODE_JTAG) { + LOG_WARNING("CK-Link: sr0 mode byte is 0x%02x, expected 0x%02x (5-wire JTAG)", + readback[3], CKLINK_SR0_MODE_JTAG); + } + ck->csr_clk = CKLINK_SR0_RUN_CLK_BYTE; + + /* Pre-load IR = IDCODE; 1-bit DR is the minimum the batch opcode accepts. */ + const uint8_t idcode_ir = CKLINK_PLACEHOLDER_IR_VALUE; + const uint8_t dummy_dr = 0; + return cklink_jtag_scan(ck, CKLINK_PLACEHOLDER_IR_BITS, &idcode_ir, NULL, + 1, &dummy_dr, NULL); +} + +static int cklink_init(void) +{ + struct cklink_ctx *ck = calloc(1, sizeof(*ck)); + if (!ck) { + LOG_ERROR("CK-Link: out of memory"); + return ERROR_JTAG_INIT_FAILED; + } + + const char *serial = adapter_get_required_serial(); + if (jtag_libusb_open(cklink_vids, cklink_pids, serial, + &ck->usb_dev, NULL) != ERROR_OK) { + LOG_ERROR("CK-Link probe not found"); + free(ck); + return ERROR_JTAG_INIT_FAILED; + } + + libusb_set_auto_detach_kernel_driver(ck->usb_dev, 1); + + int claim = libusb_claim_interface(ck->usb_dev, CKLINK_USB_INTERFACE); + if (claim != 0) { + LOG_ERROR("CK-Link: failed to claim interface %d: %s", + CKLINK_USB_INTERFACE, libusb_error_name(claim)); + jtag_libusb_close(ck->usb_dev); + free(ck); + return ERROR_JTAG_INIT_FAILED; + } + + if (cklink_probe_init(ck) != ERROR_OK) { + LOG_ERROR("CK-Link: probe initialization failed"); + libusb_release_interface(ck->usb_dev, CKLINK_USB_INTERFACE); + jtag_libusb_close(ck->usb_dev); + free(ck); + return ERROR_JTAG_INIT_FAILED; + } + + cklink_handle = ck; + tap_set_state(TAP_RESET); + LOG_INFO("CK-Link Lite V2 initialized (5-wire JTAG)"); + return ERROR_OK; +} + +static int cklink_quit(void) +{ + if (!cklink_handle) + return ERROR_OK; + + libusb_release_interface(cklink_handle->usb_dev, + CKLINK_USB_INTERFACE); + jtag_libusb_close(cklink_handle->usb_dev); + free(cklink_handle->scan_tdi); + free(cklink_handle->scan_tdo); + free(cklink_handle); + cklink_handle = NULL; + return ERROR_OK; +} + +static int cklink_speed(int speed) +{ + struct cklink_ctx *ck = cklink_handle; + if (!ck) + return ERROR_OK; /* called before init */ + + uint8_t div = cklink_khz_to_div(speed); + if (div == ck->csr_clk) + return ERROR_OK; + int ret = cklink_write_csr(ck, div, CKLINK_SR0_MODE_JTAG); + if (ret != ERROR_OK) + return ret; + ck->csr_clk = div; + int actual = CKLINK_CLK_SOURCE_KHZ / (div + 1); + LOG_DEBUG("CK-Link: adapter speed: requested %d kHz, programmed %d kHz (div=0x%02x)", + speed, actual, div); + return ERROR_OK; +} + +static int cklink_khz(int khz, int *jtag_speed) +{ + if (khz == 0) { + LOG_ERROR("CK-Link: adaptive clocking (RTCK) is not supported"); + return ERROR_JTAG_NOT_IMPLEMENTED; + } + uint8_t div = cklink_khz_to_div(khz); + int achievable = CKLINK_CLK_SOURCE_KHZ / (div + 1); + if (achievable != khz) { + static int last_clamp_khz; + if (khz != last_clamp_khz) { + LOG_WARNING("CK-Link: requested %d kHz clamped to %d kHz (div=0x%02x)", + khz, achievable, div); + last_clamp_khz = khz; + } + } + *jtag_speed = achievable; + return ERROR_OK; +} + +static int cklink_speed_div(int speed, int *khz) +{ + *khz = speed; + return ERROR_OK; +} + +COMMAND_HANDLER(cklink_handle_vid_pid_command) +{ + if (CMD_ARGC < 2 || CMD_ARGC % 2) + return ERROR_COMMAND_SYNTAX_ERROR; + + unsigned int pairs = CMD_ARGC / 2; + if (pairs > CKLINK_MAX_VID_PIDS) { + LOG_ERROR("CK-Link: too many vid/pid pairs (max %d)", + CKLINK_MAX_VID_PIDS); + return ERROR_COMMAND_SYNTAX_ERROR; + } + for (unsigned int i = 0; i < pairs; i++) { + COMMAND_PARSE_NUMBER(u16, CMD_ARGV[2 * i], cklink_vids[i]); + COMMAND_PARSE_NUMBER(u16, CMD_ARGV[2 * i + 1], cklink_pids[i]); + if (cklink_vids[i] == 0 || cklink_pids[i] == 0) { + LOG_ERROR("CK-Link: vid/pid must be non-zero (pair %u)", i); + return ERROR_COMMAND_SYNTAX_ERROR; + } + } + cklink_vids[pairs] = 0; + cklink_pids[pairs] = 0; + return ERROR_OK; +} + +static const struct command_registration cklink_subcommand_handlers[] = { + { + .name = "vid_pid", + .handler = &cklink_handle_vid_pid_command, + .mode = COMMAND_CONFIG, + .help = "VID/PID pairs of CK-Link probes to match (replaces defaults)", + .usage = "(vid pid)+", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration cklink_command_handlers[] = { + { + .name = "cklink", + .mode = COMMAND_ANY, + .help = "perform cklink management", + .chain = cklink_subcommand_handlers, + .usage = "<subcommand> [args...]", + }, + COMMAND_REGISTRATION_DONE +}; + +static struct jtag_interface cklink_jtag_interface = { + .execute_queue = cklink_execute_queue, +}; + +struct adapter_driver cklink_adapter_driver = { + .name = "cklink", + .transport_ids = TRANSPORT_JTAG, + .transport_preferred_id = TRANSPORT_JTAG, + .commands = cklink_command_handlers, + + .init = cklink_init, + .quit = cklink_quit, + .speed = cklink_speed, + .khz = cklink_khz, + .speed_div = cklink_speed_div, + + .jtag_ops = &cklink_jtag_interface, +}; diff --git a/src/jtag/interface.h b/src/jtag/interface.h index 51cbf9ac0d..5c6c8eea93 100644 --- a/src/jtag/interface.h +++ b/src/jtag/interface.h @@ -380,6 +380,7 @@ extern struct adapter_driver at91rm9200_adapter_driver; extern struct adapter_driver bcm2835gpio_adapter_driver; extern struct adapter_driver buspirate_adapter_driver; extern struct adapter_driver ch347_adapter_driver; +extern struct adapter_driver cklink_adapter_driver; extern struct adapter_driver cmsis_dap_adapter_driver; extern struct adapter_driver dmem_dap_adapter_driver; extern struct adapter_driver dummy_adapter_driver; diff --git a/src/jtag/interfaces.c b/src/jtag/interfaces.c index caf5ace999..60c7364c64 100644 --- a/src/jtag/interfaces.c +++ b/src/jtag/interfaces.c @@ -62,6 +62,9 @@ struct adapter_driver *adapter_drivers[] = { #if BUILD_CH347 == 1 &ch347_adapter_driver, #endif +#if BUILD_CKLINK == 1 + &cklink_adapter_driver, +#endif #if BUILD_CMSIS_DAP_USB == 1 || BUILD_CMSIS_DAP_HID == 1 || BUILD_CMSIS_DAP_TCP == 1 &cmsis_dap_adapter_driver, #endif diff --git a/tcl/interface/cklink.cfg b/tcl/interface/cklink.cfg new file mode 100644 index 0000000000..fb017aa056 --- /dev/null +++ b/tcl/interface/cklink.cfg @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# T-Head CK-Link JTAG adapter (including Bouffalo BL616/BL702 clones) +# + +adapter driver cklink --
