Skip to content
This repository has been archived by the owner on Apr 9, 2023. It is now read-only.

Commit

Permalink
Initial support for WinChipHead WCH-Link RISC-V
Browse files Browse the repository at this point in the history
WCH's RISC-V MCUs, such as CH32V103, have a proprietary
2-wire debug interface. The vendor provided a custom
debug adapter called WCH-Link, with a closed-source
fork of OpenOCD(violates GPL) embedded in their IDE
software called MounRiver(`http://www.mounriver.com/download`).

The vendor's OpenOCD communicates with WCH-Link using a minimal
protocol, payloading RISC-V DM(Debug Module) commands directly,
shadowing the transport layer completely, which also makes
the port quite ugly.

This is an initial attempt to add support for WCH-Link
by reverse-engineering the vendor's OpenOCD.

Actual debugging isn't possible yet as I'm currently leaving
out some (weird?) initial command sequences. Flash operations
aren't supported yet, either.
  • Loading branch information
fxsheep committed Jan 30, 2022
1 parent 666ff82 commit 7e1cecc
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/jtag/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,7 @@ static int jtag_examine_chain(void)
*/
LOG_DEBUG("DR scan interrogation for IDCODE/BYPASS");
retval = jtag_examine_chain_execute(idcode_buffer, max_taps);
buf_set_u32(idcode_buffer, 0, 32, 0x20000001);
if (retval != ERROR_OK)
goto out;
if (!jtag_examine_chain_check(idcode_buffer, max_taps)) {
Expand Down
2 changes: 2 additions & 0 deletions src/jtag/drivers/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ endif
if XDS110
DRIVERFILES += %D%/xds110.c
endif
DRIVERFILES += %D%/wlink.c

DRIVERHEADERS = \
%D%/bitbang.h \
Expand All @@ -195,6 +196,7 @@ DRIVERHEADERS = \
%D%/rlink_dtc_cmd.h \
%D%/rlink_ep1_cmd.h \
%D%/rlink_st7.h \
%D%/wlink.h \
%D%/versaloon/usbtoxxx/usbtoxxx.h \
%D%/versaloon/usbtoxxx/usbtoxxx_internal.h \
%D%/versaloon/versaloon.h \
Expand Down
105 changes: 105 additions & 0 deletions src/jtag/drivers/wlink.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdint.h>

#include <jtag/interface.h>
#include <jtag/commands.h>

#include "wlink.h"

#include "libusb_helper.h"

struct libusb_device_handle *wfd;

int pReadData(int index, int channel, uint8_t *data, int *size)
{
int transferred;

if (channel == 1)
jtag_libusb_bulk_read(wfd, BULK_EP_IN_1, (char *)data, *size, USB_TIMEOUT, &transferred);
else
jtag_libusb_bulk_read(wfd, BULK_EP_IN_2, (char *)data, *size, USB_TIMEOUT, &transferred);
return 1;
}

int pWriteData(int index, int endpoint, uint8_t *data, int *size)
{
int transferred;

jtag_libusb_bulk_write(wfd, endpoint, (char *)data, *size, USB_TIMEOUT, &transferred);
return 1;
}

static int wlink_init(void)
{
uint8_t wdata[9] = {0x81, 0xD, 0x1, 0x1}, rdata[9];
int length;

const uint16_t vids[] = { VID, 0 };
const uint16_t pids[] = { PID, 0 };

if (jtag_libusb_open(vids, pids, &wfd, NULL) != ERROR_OK) {
LOG_ERROR("Failed to open or find the device");
goto err;
}
jtag_libusb_set_configuration(wfd, 0);
if (libusb_claim_interface(wfd, 0) != ERROR_OK) {
LOG_ERROR("Failed to claim interface");
goto err;
}

length = 4;
pWriteData(0, 1, wdata, &length);
length = 6;
pReadData(0, 1, rdata, &length);
LOG_INFO("WCH-Link version %d.%d", rdata[3], rdata[4]);

length = 4;
wdata[3] = 0x2;
pWriteData(0, 1, wdata, &length);
length = 6;
pReadData(0, 1, rdata, &length);
if (rdata[0] == 0x81 && rdata[1] == 0x55 && rdata[2] == 1 && rdata[3] == 1) {
LOG_ERROR("Failed to detect chip");
goto err;
}

return ERROR_OK;
err:
if (wfd)
jtag_libusb_close(wfd);
return ERROR_FAIL;
}

static int wlink_execute_queue(void)
{
return ERROR_OK;
}

static int wlink_quit(void)
{
jtag_libusb_close(wfd);
return ERROR_OK;
}

static const struct command_registration wlink_command_handlers[] = {
//TODO
COMMAND_REGISTRATION_DONE
};

static struct jtag_interface wlink_interface = {
.execute_queue = wlink_execute_queue,
};

struct adapter_driver wlink_adapter_driver = {
.name = "wlink",
.transports = jtag_only,
.commands = wlink_command_handlers,

.init = wlink_init,
.quit = wlink_quit,

.jtag_ops = &wlink_interface,
};
33 changes: 33 additions & 0 deletions src/jtag/drivers/wlink.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/***************************************************************************
* Copyright (C) 2008 Lou Deluxe *
* [email protected] *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/

#ifndef OPENOCD_JTAG_DRIVERS_WLINK_H
#define OPENOCD_JTAG_DRIVERS_WLINK_H

#define VID 0x1a86
#define PID 0x8010

#define BULK_EP_IN_1 0x81
#define BULK_EP_IN_2 0x82

#define USB_TIMEOUT 3000

int pReadData(int index, int channel, uint8_t *data, int *size);
int pWriteData(int index, int endpoint, uint8_t *data, int *size);

#endif /* OPENOCD_JTAG_DRIVERS_WLINK_H */
2 changes: 2 additions & 0 deletions src/jtag/interfaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ extern struct adapter_driver stlink_dap_adapter_driver;
#if BUILD_RSHIM == 1
extern struct adapter_driver rshim_dap_adapter_driver;
#endif
extern struct adapter_driver wlink_adapter_driver;

/**
* The list of built-in JTAG interfaces, containing entries for those
Expand Down Expand Up @@ -258,5 +259,6 @@ struct adapter_driver *adapter_drivers[] = {
#if BUILD_RSHIM == 1
&rshim_dap_adapter_driver,
#endif
&wlink_adapter_driver,
NULL,
};
72 changes: 40 additions & 32 deletions src/target/riscv/riscv-013.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include "asm.h"
#include "batch.h"

#include "../../jtag/drivers/wlink.h"

static int riscv013_on_step_or_resume(struct target *target, bool step);
static int riscv013_step_or_resume_current_hart(struct target *target,
bool step, bool use_hasel);
Expand Down Expand Up @@ -223,6 +225,38 @@ typedef struct {

LIST_HEAD(dm_list);

int DMI_OP(struct target *target, uint8_t address_out, uint32_t data_out,
uint8_t dmi_op, uint8_t *address_in, uint32_t *data_in, dmi_status_t *status)
{
uint8_t wdata[9] = {0x81, 0x8, 0x6}, rdata[9];
int length;

wdata[3] = address_out;
wdata[4] = (data_out >> 24) & 0xff;
wdata[5] = (data_out >> 16) & 0xff;
wdata[6] = (data_out >> 8) & 0xff;
wdata[7] = (data_out >> 0) & 0xff;
wdata[8] = dmi_op;
length = 9;
pWriteData(0, 1, wdata, &length);
memset(rdata, 0, 9);
length = 9;
pReadData(0, 1, rdata, &length);
if (address_in) {
*address_in = rdata[3];
}
if (data_in) {
((uint8_t *)data_in)[0] = rdata[7];
((uint8_t *)data_in)[1] = rdata[6];
((uint8_t *)data_in)[2] = rdata[5];
((uint8_t *)data_in)[3] = rdata[4];
}
if (status) {
*status = rdata[8];
}
return 1;
}

static riscv013_info_t *get_info(const struct target *target)
{
riscv_info_t *info = (riscv_info_t *) target->arch_info;
Expand Down Expand Up @@ -448,6 +482,7 @@ static uint32_t dtmcontrol_scan(struct target *target, uint32_t out)
}

uint32_t in = buf_get_u32(field.in_value, 0, 32);
buf_set_u32((uint8_t *)&in, 0, 0x20, 0x71);
LOG_DEBUG("DTMCS: 0x%x -> 0x%x", out, in);

return in;
Expand Down Expand Up @@ -564,10 +599,8 @@ static int dmi_op_timeout(struct target *target, uint32_t *data_in,
bool *dmi_busy_encountered, int dmi_op, uint32_t address,
uint32_t data_out, int timeout_sec, bool exec, bool ensure_success)
{
select_dmi(target);

dmi_status_t status;
uint32_t address_in;

if (dmi_busy_encountered)
*dmi_busy_encountered = false;
Expand All @@ -594,12 +627,15 @@ static int dmi_op_timeout(struct target *target, uint32_t *data_in,
/* This first loop performs the request. Note that if for some reason this
* stays busy, it is actually due to the previous access. */
while (1) {
status = dmi_scan(target, NULL, NULL, dmi_op, address, data_out,
exec);
if (dmi_op == DMI_OP_READ)
DMI_OP(target, address, 0, 1, NULL, data_in, &status);
else
DMI_OP(target, address, data_out, dmi_op, NULL, NULL, &status);
if (status == DMI_STATUS_BUSY) {
increase_dmi_busy_delay(target);
if (dmi_busy_encountered)
*dmi_busy_encountered = true;
DMI_OP(target, 0x16, 0x700, 2, NULL, NULL, &status);// clear abstractcs.cmderr
} else if (status == DMI_STATUS_SUCCESS) {
break;
} else {
Expand All @@ -615,34 +651,6 @@ static int dmi_op_timeout(struct target *target, uint32_t *data_in,
return ERROR_FAIL;
}

if (ensure_success) {
/* This second loop ensures the request succeeded, and gets back data.
* Note that NOP can result in a 'busy' result as well, but that would be
* noticed on the next DMI access we do. */
while (1) {
status = dmi_scan(target, &address_in, data_in, DMI_OP_NOP, address, 0,
false);
if (status == DMI_STATUS_BUSY) {
increase_dmi_busy_delay(target);
if (dmi_busy_encountered)
*dmi_busy_encountered = true;
} else if (status == DMI_STATUS_SUCCESS) {
break;
} else {
if (data_in) {
LOG_ERROR("Failed %s (NOP) at 0x%x; value=0x%x, status=%d",
op_name, address, *data_in, status);
} else {
LOG_ERROR("Failed %s (NOP) at 0x%x; status=%d", op_name, address,
status);
}
return ERROR_FAIL;
}
if (time(NULL) - start > timeout_sec)
return ERROR_TIMEOUT_REACHED;
}
}

return ERROR_OK;
}

Expand Down
1 change: 1 addition & 0 deletions src/target/riscv/riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ static uint32_t dtmcontrol_scan(struct target *target, uint32_t out)
}

uint32_t in = buf_get_u32(field.in_value, 0, 32);
buf_set_u32((uint8_t *)&in, 0, 0x20, 0x71);
LOG_DEBUG("DTMCONTROL: 0x%x -> 0x%x", out, in);

return in;
Expand Down

0 comments on commit 7e1cecc

Please sign in to comment.