| // 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 <assert.h> |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "tcp_server.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 { |
| TestLogicReset, |
| RunTestIdle, |
| SelectDrScan, |
| CaptureDr, |
| ShiftDr, |
| Exit1Dr, |
| PauseDr, |
| Exit2Dr, |
| UpdateDr, |
| SelectIrScan, |
| CaptureIr, |
| ShiftIr, |
| Exit1Ir, |
| PauseIr, |
| Exit2Ir, |
| UpdateIr |
| }; |
| |
| enum jtag_reg_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; |
| enum 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; |
| } |