|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | #include "dmidpi.h" | 
|  | #include "tcp_server.h" | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <pthread.h> | 
|  | #include <stdbool.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  |  | 
|  | // IDCODE register | 
|  | // [31:28] 0x0,    - Version | 
|  | // [27:12] 0x4F54, - Part Number: "OT" | 
|  | // [11:1]  0x426,  - Manufacturer Identity: Google | 
|  | // [0]     0x1     - fixed | 
|  | const int IDCODEVAL = 0x04F5484D; | 
|  |  | 
|  | // DTMCS register | 
|  | // [31:18]  0x0  - fixed | 
|  | // [17]     0x0  - dmihardreset (unused) | 
|  | // [16]     0x0  - dmireset (unused) | 
|  | // [15]     0x0  - fixed | 
|  | // [14:12]  0x0  - RunTestIdle cycles required (0) | 
|  | // [11:10]  0x0  - DMI status (unused) | 
|  | // [9:4]    0x7  - Number of address bits | 
|  | // [3:0]    0x1  - Protocol version (0.13) | 
|  | const int DTMCSRVAL = 0x00000071; | 
|  |  | 
|  | enum jtag_state_t : uint8_t { | 
|  | TestLogicReset, | 
|  | RunTestIdle, | 
|  | SelectDrScan, | 
|  | CaptureDr, | 
|  | ShiftDr, | 
|  | Exit1Dr, | 
|  | PauseDr, | 
|  | Exit2Dr, | 
|  | UpdateDr, | 
|  | SelectIrScan, | 
|  | CaptureIr, | 
|  | ShiftIr, | 
|  | Exit1Ir, | 
|  | PauseIr, | 
|  | Exit2Ir, | 
|  | UpdateIr | 
|  | }; | 
|  |  | 
|  | enum jtag_reg_t : uint8_t { | 
|  | Bypass0 = 0x0, | 
|  | IdCode = 0x1, | 
|  | DTMCSR = 0x10, | 
|  | DMIAccess = 0x11, | 
|  | Bypass1 = 0x1f | 
|  | }; | 
|  |  | 
|  | struct jtag_ctx { | 
|  | uint32_t ir_shift_reg; | 
|  | uint64_t dr_shift_reg; | 
|  | uint32_t ir_captured; | 
|  | uint64_t dr_captured; | 
|  | uint8_t dr_length; | 
|  | uint8_t jtag_tdo; | 
|  | jtag_state_t jtag_state; | 
|  | uint8_t dmi_outstanding; | 
|  | }; | 
|  |  | 
|  | struct dmi_sig_values { | 
|  | uint8_t dmi_req_valid; | 
|  | uint8_t dmi_req_ready; | 
|  | uint32_t dmi_req_addr; | 
|  | uint32_t dmi_req_op; | 
|  | uint32_t dmi_req_data; | 
|  | uint8_t dmi_rsp_valid; | 
|  | uint8_t dmi_rsp_ready; | 
|  | uint32_t dmi_rsp_data; | 
|  | uint32_t dmi_rsp_resp; | 
|  | uint8_t dmi_rst_n; | 
|  | }; | 
|  |  | 
|  | struct dmidpi_ctx { | 
|  | struct tcp_server_ctx *sock; | 
|  | struct jtag_ctx jtag; | 
|  | struct dmi_sig_values sig; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Setup the correct shift register data | 
|  | * | 
|  | * @param ctx dmidpi context object | 
|  | */ | 
|  | static void set_dr_data(struct dmidpi_ctx *ctx) { | 
|  | switch (ctx->jtag.ir_captured) { | 
|  | case Bypass0: | 
|  | case Bypass1: | 
|  | ctx->jtag.dr_shift_reg = 0; | 
|  | ctx->jtag.dr_length = 1; | 
|  | break; | 
|  | case IdCode: | 
|  | ctx->jtag.dr_shift_reg = IDCODEVAL; | 
|  | ctx->jtag.dr_length = 32; | 
|  | break; | 
|  | case DTMCSR: | 
|  | ctx->jtag.dr_shift_reg = DTMCSRVAL; | 
|  | ctx->jtag.dr_length = 32; | 
|  | break; | 
|  | case DMIAccess: | 
|  | ctx->jtag.dr_shift_reg = ctx->jtag.dr_captured; | 
|  | ctx->jtag.dr_length = 32 + 7 + 2; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Drive a new DMI transaction to the DPI interface | 
|  | * | 
|  | * @param ctx dmidpi context object | 
|  | */ | 
|  | static void issue_dmi_req(struct dmidpi_ctx *ctx) { | 
|  | ctx->jtag.dmi_outstanding = 1; | 
|  | ctx->sig.dmi_req_valid = 1; | 
|  | ctx->sig.dmi_req_addr = (ctx->jtag.dr_captured >> 34) & 0x7F; | 
|  | ctx->sig.dmi_req_op = ctx->jtag.dr_captured & 0x3; | 
|  | ctx->sig.dmi_req_data = (ctx->jtag.dr_captured >> 2) & 0xFFFFFFFF; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Advance internal JTAG state | 
|  | * | 
|  | * @param ctx dmidpi context object | 
|  | * @param tdi tms tck JTAG signal values | 
|  | * @return true when a command completes, false otherwise | 
|  | */ | 
|  | static bool process_jtag_cmd(struct dmidpi_ctx *ctx, bool tdi, bool tms, | 
|  | bool tck) { | 
|  | // Return tdo values during tck low phase | 
|  | if (!tck) { | 
|  | if (ctx->jtag.jtag_state == ShiftDr) { | 
|  | ctx->jtag.jtag_tdo = ctx->jtag.dr_shift_reg & 0x1; | 
|  | } else { | 
|  | ctx->jtag.jtag_tdo = ctx->jtag.ir_shift_reg & 0x1; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // standard JTAG state machine | 
|  | switch (ctx->jtag.jtag_state) { | 
|  | case TestLogicReset: | 
|  | // reset design | 
|  | ctx->sig.dmi_rst_n = 0; | 
|  | ctx->jtag.ir_captured = 1; | 
|  | if (!tms) { | 
|  | ctx->jtag.jtag_state = RunTestIdle; | 
|  | } | 
|  | return true; | 
|  | case RunTestIdle: | 
|  | // release reset | 
|  | ctx->sig.dmi_rst_n = 1; | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = SelectDrScan; | 
|  | } | 
|  | return false; | 
|  | case SelectDrScan: | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = SelectIrScan; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = CaptureDr; | 
|  | } | 
|  | return false; | 
|  | case CaptureDr: | 
|  | set_dr_data(ctx); | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = Exit1Dr; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = ShiftDr; | 
|  | } | 
|  | return false; | 
|  | case ShiftDr: | 
|  | ctx->jtag.dr_shift_reg |= ((uint64_t)tdi << ctx->jtag.dr_length); | 
|  | ctx->jtag.dr_shift_reg >>= 1; | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = Exit1Dr; | 
|  | } | 
|  | return false; | 
|  | case Exit1Dr: | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = UpdateDr; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = PauseDr; | 
|  | } | 
|  | return false; | 
|  | case PauseDr: | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = Exit2Dr; | 
|  | } | 
|  | return false; | 
|  | case Exit2Dr: | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = UpdateDr; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = ShiftDr; | 
|  | } | 
|  | return false; | 
|  | case UpdateDr: | 
|  | ctx->jtag.dr_captured = ctx->jtag.dr_shift_reg; | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = SelectDrScan; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = RunTestIdle; | 
|  | } | 
|  | // If a DMI read or write completes, write it out | 
|  | if ((ctx->jtag.ir_captured == DMIAccess) && | 
|  | ((ctx->jtag.dr_captured & 0x3) != 0)) { | 
|  | issue_dmi_req(ctx); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | case SelectIrScan: | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = TestLogicReset; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = CaptureIr; | 
|  | } | 
|  | return false; | 
|  | case CaptureIr: | 
|  | ctx->jtag.ir_shift_reg = 0x5; | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = Exit1Ir; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = ShiftIr; | 
|  | } | 
|  | return false; | 
|  | case ShiftIr: | 
|  | ctx->jtag.ir_shift_reg |= ((uint32_t)tdi << 5); | 
|  | ctx->jtag.ir_shift_reg >>= 1; | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = Exit1Ir; | 
|  | } | 
|  | return false; | 
|  | case Exit1Ir: | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = UpdateIr; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = PauseIr; | 
|  | } | 
|  | return false; | 
|  | case PauseIr: | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = Exit2Ir; | 
|  | } | 
|  | return false; | 
|  | case Exit2Ir: | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = UpdateIr; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = ShiftIr; | 
|  | } | 
|  | return false; | 
|  | case UpdateIr: | 
|  | ctx->jtag.ir_captured = ctx->jtag.ir_shift_reg; | 
|  | if (tms) { | 
|  | ctx->jtag.jtag_state = SelectDrScan; | 
|  | } else { | 
|  | ctx->jtag.jtag_state = RunTestIdle; | 
|  | } | 
|  | return false; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Process a new command byte | 
|  | * | 
|  | * @param ctx a dmi context object | 
|  | * @param cmd a packaged OpenOCD cmd | 
|  | * @return true when a command completes, false otherwise | 
|  | */ | 
|  | static bool process_cmd_byte(struct dmidpi_ctx *ctx, char cmd) { | 
|  | /* | 
|  | * Documentation pointer: | 
|  | * The remote_bitbang protocol implemented below is documented in the OpenOCD | 
|  | * source tree at doc/manual/jtag/drivers/remote_bitbang.txt, or online at | 
|  | * https://repo.or.cz/openocd.git/blob/HEAD:/doc/manual/jtag/drivers/remote_bitbang.txt | 
|  | */ | 
|  |  | 
|  | // parse received command byte | 
|  | if (cmd >= '0' && cmd <= '7') { | 
|  | // JTAG write | 
|  | char cmd_bit = cmd - '0'; | 
|  | char tdi = (cmd_bit >> 0) & 0x1; | 
|  | char tms = (cmd_bit >> 1) & 0x1; | 
|  | char tck = (cmd_bit >> 2) & 0x1; | 
|  | return (process_jtag_cmd(ctx, tdi, tms, tck)); | 
|  | } else if (cmd >= 'r' && cmd <= 'u') { | 
|  | // JTAG reset (active high from OpenOCD) | 
|  | char cmd_bit = cmd - 'r'; | 
|  | char trst = ((cmd_bit >> 1) & 0x1); | 
|  | if (trst) { | 
|  | ctx->sig.dmi_rst_n = 0; | 
|  | ctx->jtag.jtag_state = RunTestIdle; | 
|  | } | 
|  | return true; | 
|  | } else if (cmd == 'R') { | 
|  | // JTAG read, send tdo as response | 
|  | char tdo_ascii = ctx->jtag.jtag_tdo + '0'; | 
|  | tcp_server_write(ctx->sock, tdo_ascii); | 
|  | } else if (cmd == 'B') { | 
|  | // printf("DMI DPI: BLINK ON!\n"); | 
|  | } else if (cmd == 'b') { | 
|  | // printf("DMI DPI: BLINK OFF!\n"); | 
|  | } else if (cmd == 'Q') { | 
|  | // quit (client disconnect) | 
|  | printf("DMI DPI: Remote disconnected.\n"); | 
|  | tcp_server_client_close(ctx->sock); | 
|  | } else { | 
|  | fprintf(stderr, | 
|  | "DMI DPI: Protocol violation detected: unsupported command %c\n", | 
|  | cmd); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Process DPI inputs from the design | 
|  | * | 
|  | * @param ctx dmidpi context object | 
|  | */ | 
|  | static void process_dmi_inputs(struct dmidpi_ctx *ctx) { | 
|  | // Deassert dmi_req_valid when acked | 
|  | if (ctx->sig.dmi_req_ready) { | 
|  | ctx->sig.dmi_req_valid = 0; | 
|  | } | 
|  | // Always ready for a resp | 
|  | ctx->sig.dmi_rsp_ready = 1; | 
|  | if (ctx->sig.dmi_rsp_valid) { | 
|  | ctx->jtag.dr_captured = (uint64_t)ctx->sig.dmi_rsp_data << 2; | 
|  | ctx->jtag.dr_captured |= (uint64_t)ctx->sig.dmi_rsp_resp & 0x3; | 
|  | // Clear req outstanding flag | 
|  | ctx->jtag.dmi_outstanding = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Advance DMI internal state | 
|  | * | 
|  | * @param ctx dmidpi context object | 
|  | */ | 
|  | static void update_dmi_state(struct dmidpi_ctx *ctx) { | 
|  | assert(ctx); | 
|  |  | 
|  | // read input from design | 
|  | process_dmi_inputs(ctx); | 
|  |  | 
|  | // If we are waiting for a previous transaction to complete, do not attempt | 
|  | // a new one | 
|  | if (ctx->jtag.dmi_outstanding) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | char done = 0; | 
|  | while (!done) { | 
|  | // read a command byte | 
|  | char cmd; | 
|  | if (!tcp_server_read(ctx->sock, &cmd)) { | 
|  | return; | 
|  | } | 
|  | // Process command bytes until a command completes | 
|  | done = process_cmd_byte(ctx, cmd); | 
|  | } | 
|  | } | 
|  |  | 
|  | void *dmidpi_create(const char *display_name, int listen_port) { | 
|  | // Create context | 
|  | struct dmidpi_ctx *ctx = | 
|  | (struct dmidpi_ctx *)calloc(1, sizeof(struct dmidpi_ctx)); | 
|  | assert(ctx); | 
|  |  | 
|  | // Set up socket details | 
|  | ctx->sock = tcp_server_create(display_name, listen_port); | 
|  |  | 
|  | printf( | 
|  | "\n" | 
|  | "JTAG: Virtual JTAG interface %s is listening on port %d. Use\n" | 
|  | "OpenOCD and the following configuration to connect:\n" | 
|  | "  interface remote_bitbang\n" | 
|  | "  remote_bitbang_host localhost\n" | 
|  | "  remote_bitbang_port %d\n", | 
|  | display_name, listen_port, listen_port); | 
|  |  | 
|  | return (void *)ctx; | 
|  | } | 
|  |  | 
|  | void dmidpi_close(void *ctx_void) { | 
|  | struct dmidpi_ctx *ctx = (struct dmidpi_ctx *)ctx_void; | 
|  | if (!ctx) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Shut down the server | 
|  | tcp_server_close(ctx->sock); | 
|  |  | 
|  | free(ctx); | 
|  | } | 
|  |  | 
|  | void dmidpi_tick(void *ctx_void, svBit *dmi_req_valid, | 
|  | const svBit dmi_req_ready, svBitVecVal *dmi_req_addr, | 
|  | svBitVecVal *dmi_req_op, svBitVecVal *dmi_req_data, | 
|  | const svBit dmi_rsp_valid, svBit *dmi_rsp_ready, | 
|  | const svBitVecVal *dmi_rsp_data, | 
|  | const svBitVecVal *dmi_rsp_resp, svBit *dmi_rst_n) { | 
|  | struct dmidpi_ctx *ctx = (struct dmidpi_ctx *)ctx_void; | 
|  |  | 
|  | if (!ctx) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | ctx->sig.dmi_req_ready = dmi_req_ready; | 
|  | ctx->sig.dmi_rsp_valid = dmi_rsp_valid; | 
|  | ctx->sig.dmi_rsp_data = *dmi_rsp_data; | 
|  | ctx->sig.dmi_rsp_resp = *dmi_rsp_resp; | 
|  |  | 
|  | update_dmi_state(ctx); | 
|  |  | 
|  | *dmi_req_valid = ctx->sig.dmi_req_valid; | 
|  | *dmi_req_addr = ctx->sig.dmi_req_addr; | 
|  | *dmi_req_op = ctx->sig.dmi_req_op; | 
|  | *dmi_req_data = ctx->sig.dmi_req_data; | 
|  | *dmi_rsp_ready = ctx->sig.dmi_rsp_ready; | 
|  | *dmi_rst_n = ctx->sig.dmi_rst_n; | 
|  | } |