############################## 1. Intro (All directory and file references are relative /urjtag.) All "cable" drivers lives in: src/tap/cable Currently theese parport drivers are available: arcom.c byteblaster.c dlc5.c ea253.c ei012.c keithkoep.c lattice.c minimal.c mpcbdm.c triton.c wiggler.c wiggler2.c To write a new driver, you make a new file in that directory named by convention by the driver name. In this case I'll describe the steps I took to make the driver for the aspo adapter card (as of july 2019). ############################## 2. Build system, adding a driver. (This section is valid for all cable driver, parport, usb and others.) Before I describe the driver code, let's look into how to integrate the .c file into the build system. You have to update the following files: MAINTAINERS configure.ac doc/UrJTAG.txt po/POTFILES.in src/tap/Makefile.am src/tap/cable_list.h 2.1 MAINTAINERS To be kind to others, please fill in the MAINTAINERS file, and keep it sorted. I added the entry: ASPO CABLE DRIVER M: Karl Hammar F: src/tap/cable/aspo.c S: Tested between the "ARCOM CABLE DRIVER" and "AU1500 BUS DRIVER". 2.2 configure.ac In configure.ac there is a list of all possible "cable" drivers around line 655: # Enable cable drivers URJ_DRIVER_SET([cable], [ arcom byteblaster dirtyjtag dlc5 ea253 ... (omitted lines) wiggler xpc ],[ ep9307 jim ts7800 ],[ # automatically disable cable drivers when a required feature is not available ... (omitted lines) ]) ]) add your name there. This will make it possible to enable your driver when you running autogen.sh and configure (look in config.h to make sure). URJ_DRIVER_SET() have four parameters 1st is the driver set name 2nd is the list of drivers to enable by default 3rd is the list of drivers to disable by default 4th is some extra code to run before processing user list you will probably add your driver name to the 2nd argument. I added the line: aspo between the lines with arcom and byteblaster to keep the list sorted. 2.3 doc/UrJTAG.txt Make a note here about your driver so others has a chance to know that it exist. The best place is below line 202 under the headers: ==== Supported JTAG adapters/cables ==== See 'help cable' command for up-to-date info. Parallel-port cables: 2.4 po/POTFILES.in Add your driver source file in the list. TODO: Still don't know at this moment how to make gettext to update po/urjtag.pot so translators can do their job properly. 2.5 src/tap/Makefile.am Add, if-endif code to add your code if your driver is enabled. I added: if ENABLE_CABLE_ASPO libtap_la_SOURCES += \ cable/aspo.c endif between the similar lines for ARCOM and BYTEBLASTER to keep the list sorted. 2.6 src/tap/cable_list.h Add your _URJ_CABLE() section here so your driver entry point can be included. This file is then included in: src/tap/cable.h src/tap/cable.c and via cpp hackery makes the driver entry points available. As before, keep the list sorted I added this section (between the ARCOM and BYTEBLASTER sections): #ifdef ENABLE_CABLE_ASPO _URJ_CABLE(aspo) #endif ############################## 3. Driver code. 3.1 Test building Copy a similar driver as your initial source file and replace the old driver name with the new one. In my case it amounted to: sed -e 's/wiggler2/aspo/g' < src/tap/cable/wiggler2.c > src/tap/cable/aspo.c and then change the first two text entries of the const urj_cable_driver_t last in the file to say something else than the original, just for testing. Now, run ./autogen.sh # ./configure done by autogen.sh; run it here with special options if needed make make install Look at the make output and make sure you see your source compiling, something like: CC cable/aspo.lo Test with jtag and verify that your driver is there with "help cable" without any actual "cable" attached to the paralell port. For me it worked out as (on linux using ppdev as the lowlevel driver): $ jtag UrJTAG 2018.09 # Copyright (C) 2002, 2003 ETC s.r.o. Copyright (C) 2007, 2008, 2009 Kolja Waschk and the respective authors ... (omitted lines) jtag> help cable Usage: cable DRIVER [DRIVER_OPTS] Select JTAG cable type. DRIVER name of cable DRIVER_OPTS options for the selected cable Type "cable DRIVER help" for info about options for cable DRIVER. You can also use the driver "probe" to attempt autodetection. List of supported cables: ARCOM Arcom JTAG Cable ASPO Aspo JTAG Cable (experimental) ByteBlaster Altera ByteBlaster/ByteBlaster II/ByteBlasterMV Parallel Port Download Cable DLC5 Xilinx DLC5 JTAG Parallel Cable III ... (omitted lines) DirtyJTAG DirtyJTAG STM32-based cable jtag> cable aspo ppdev /dev/parport0 Initializing ppdev port /dev/parport0 jtag> cable ASPO ppdev /dev/parport0 Initializing ppdev port /dev/parport0 jtag> quit As you can see, the driver name is case insensitive, the function that finds the driver is urj_tap_cable_find() (in src/tap/cable.c) and it uses strcasecmp(). 3.2 The jtag command When you run the command jtag, you run the code found in src/apps/jtag/jtag.c, which, if run interactively, calls jtag_readline_loop() -> jtag_readline_multiple_commands_support() -> urj_parse_line() (which is found in src/global/parse.c) -> urj_cmd_run() (src/cmd/cmd_cmd.c). urj_cmd_run() has two arguments, chain and params. Chain is the found chain of tap controllers among other things. Params is the command line entered, but tokenized. The word chain is used in the combination JTAG chain in doc/UrJTAG.txt. urj_cmd_run() searches urj_cmds[] for the command to run. The urj_cmds[] list is generated by Makefile from the files in src/cmd that contaings a const urj_cmd_t urj_cmd_* variable. So, if we enter quit to the command line, urj_cmd_run() finds "quit" in urj_cmd_quit variable last in src/cmd/cmd_quit.c and runs cmd_quit_run() since that is the run element of the urj_cmd_t struct. The process is similar for other commands and you can find them all in the src/cmd directory. 3.2.1 The jtag cable command In 3.1 we entered the command line "cable aspo ppdev /dev/parport0". By doing so urj_cmd_run() finds the data in urj_cmd_cable last in src/cmd/cmd_cable.c and extracts the run member of that struct, i.e. the function pointer cmd_cable_run. As seen in src/cmd/cmd_cable.c, cmd_cable_run() does some checks and calls urj_tap_chain_connect(chain, params[1], ¶ms[2]) (src/tap/chain.c), params[1] is in this case is "aspo", and ¶ms[2] is a pointer to { "ppdev", "/dev/parport0" }. It seems that everything hardware related goes through urj_tap_*() functions, they reside in the usr/tap directory. urj_tap_chain_connect() finds our driver (aspo), sees that it is a parport driver, finds the parport devtype (ppdev) and calls urj_tap_cable_parport_connect() (src/tap/cable.c). For some reason, the urj_parport_driver_t (include/urjtag/parport.h) struct doesn't contain a searchable string (e.g. "ppdev") like the urj_cable_driver_t (struct URJ_CABLE_DRIVER, include/urjtag/cable.h), and the search is done through a switch statement in urj_cable_parport_devtype_string(). So first we search for an int and then what driver struct matches that int. urj_tap_cable_parport_connect() then allocs an urj_cable_t cable, setting its .driver to a pointer to the driver, and runs the driver's connect(), usually urj_tap_cable_generic_parport_connect() (src/tap/cable/generic_parport.c) which finds the entry point (i.e. a struct with function pointers) for devtype, and runs its connect() entry. In our example, it is ppdev_connect(), which checks that we arn't already using the device (/dev/parport0 in our example), and then sets up a port_node_t (src/tap/parport.h) structure. Back in urj_tap_cable_parport_connect(), it runs urj_tap_cable_start(), which runs urj_tap_cable_init() (src/tap/cable.c) and urj_tap_trst_reset() (src/tap/tap.c). urj_tap_cable_init() sets up the urj_cable_t variable and calls the cable drivers init (aspo_init() in our example), which is calling ppdev_open() (in our example) which actually does open the device file. 3.3 driver documentation 3.3.1 driver entry point Your driver is reached with the "const urj_cable_driver_t" struct, usually found near the end of the driver code. The name of the struct must match what comes out of the _URJ_CABLE() macro found in src/tap/cable.[ch] and src/tap/cable_list.h. This macro is differntly defined in the files src/tap/cable.[ch]: cable.c: #define _URJ_CABLE(cable) &urj_tap_cable_##cable##_driver, cable.h: #define _URJ_CABLE(cable) extern const urj_cable_driver_t urj_tap_cable_##cable##_driver; but the struct name is the same for booth theese to definitions urj_tap_cable_##cable##_driver, i.e. if you used _URJ_CABLE(foo) in the cable_list.h file, then you must define the entry point as: const urj_cable_driver_t urj_tap_cable_foo_driver = { ... (omitted lines) }; This type, urj_cable_driver_t, is defined in include/urjtag/cable.h, it is a typedef near the top of the file: typedef struct URJ_CABLE_DRIVER urj_cable_driver_t; and the actual struct is defined in middle of the file: struct URJ_CABLE_DRIVER { ... int (*parport) (urj_cable_t *cable, urj_cable_parport_devtype_t devtype, int (*usb) (urj_cable_t *cable, const urj_param_t *params[]); int (*other) (urj_cable_t *cable, const urj_param_t *params[]); ... void (*disconnect) (urj_cable_t *cable); void (*cable_free) (urj_cable_t *cable); int (*init) (urj_cable_t *); void (*done) (urj_cable_t *); void (*set_frequency) (urj_cable_t *, uint32_t freq); void (*clock) (urj_cable_t *, int, int, int); int (*get_tdo) (urj_cable_t *); int (*transfer) (urj_cable_t *, int, const char *, char *); int (*set_signal) (urj_cable_t *, int, int); int (*get_signal) (urj_cable_t *, urj_pod_sigsel_t); void (*flush) (urj_cable_t *, urj_cable_flush_amount_t); void (*help) (urj_log_level_t ll, const char *); }; Your job as a driver writer is to fill in your struct variable with the right set of functions for the operation of your hardware. Most of theese entries are generic as seen in my example: const urj_cable_driver_t urj_tap_cable_aspo_driver = { "ASPO", N_("Aspo JTAG Cable (experimental)"), URJ_CABLE_DEVICE_PARPORT, { .parport = urj_tap_cable_generic_parport_connect, }, urj_tap_cable_generic_disconnect, urj_tap_cable_generic_parport_free, aspo_init, urj_tap_cable_generic_parport_done, urj_tap_cable_generic_set_frequency, aspo_clock, aspo_get_tdo, urj_tap_cable_generic_transfer, aspo_set_signal, urj_tap_cable_generic_get_signal, urj_tap_cable_generic_flush_one_by_one, urj_tap_cable_generic_parport_help }; The only thing I had to add is the init, clock, get_tdo and set_signal entries and the name ("ASPO") and the description (N_("...")). The N_(...) thing is defined in sysdep.h as #define N_(s) gettext_noop(s) and definition of gettext_noop() is found in include/urjtag/gettext.h. 3.3.2 Our first parameter, the urj_cable_t *cable All function entries in the urj_cable_driver_t struct (except help) have a "urj_cable_t *cable" first parameter, as can be seen from the extract below. urj_cable_t is a struct URJ_CABLE (as seen in include/urjtag/types.h) and that struct is declared in include/urjtag/cable.h, some 50 lines below the struct URJ_CABLE_DRIVER declaration: struct URJ_CABLE { const urj_cable_driver_t *driver; union { urj_usbconn_t *usb; urj_parport_t *port; void *other; } link; void *params; urj_chain_t *chain; urj_cable_queue_info_t todo; urj_cable_queue_info_t done; uint32_t delay; uint32_t frequency; }; The variable cable is initialized as urj_tap_cable_create(): cable = calloc() cable->driver = driver (= your driver entry point) urj_tap_cable_generic_parport_connect() cable->link.port = port cable->params = cable_params cable->chain = NULL urj_tap_cable_start() chain->cable = cable cable->delay = 0; cable->frequency = 0; urj_tap_cable_init() cable->todo = {} cable->done = {} ppdev_parport_alloc() cable->link.port = {} your parport driver _open (ppdev_open() in this example) open() and ioctl(,PPCLAIM) your cable driver _init() (aspo_init() in this example) PARAM_SIGNALS (cable) = ... urj_tap_chain_connect() chain->cable->chain = chain Of all struct members, only cable->params is of main interest for the cable driver writer. urj_tap_cable_generic_params_t and PARAM_SIGNALS() are declared in src/tap/cable/generic.h, and they are only used within the src/tap/cable directory. Its value is leaked out by urj_tap_cable_generic_get_signal() which is the default driver->get_signal() function, so we must provide for that value since it is visible to the outside. That can be done by the default function and types, by a custom one. urj_tap_cable_get_signal() and urj_tap_cable_get_signal_late() via urj_tap_chain_get_trst() and urj_tap_chain_get_pod_signal() is used by the pod command (src/cmd/cmd_pod.c). 3.3.3 Talking to the hardware Access to hw is done with the functions in src/tap/parport.c which are shorthands for accessing the functions of the choosed parport handler (ppdev in this example). There are three registers on an ordinary paralell port: data, control and status. Some bits in the control and status registers have inverted signals to/from the connector pins, but theese functions handles them so zero in an output bit given to thoose routines gives you low voltage on the output pin and low on an input pin gives you a zero on return on the corresponding bit. 3.3.4 Values to/from caller The caller of the driver seems to treat tms/tdi/tck/trst/tdo values as: 1 => high voltage on jtag bus, 0 => low voltage on jtag bus. This is guessed from the discussion below. We have looked at driver->init() above. get_tdo() returns the TDO jtag signal, and clock() and set_signal() sets the jtag signals. So we must know if a clock(cable, 1, 1, 1) means that TMS and TDI should be high (near Vcc) on the jtag bus on the target board, or low and ditto for TRST and TCK. From urj_svf_force_reset_state() (src/svf/svf.c) we know that tms == 1 implies that the jtag signal TMS is high since five TMS high clocked in puts the TAP controller in the Test-Logic-Reset state. urj_tap_trst_reset() (src/tap/tap.c) gives us that trst == 1 implies that jtag nTRST signal is high. urj_tap_reset_bypass() (src/tap/tap.c) gives that tdi == 1 implies that jtag TDI signal is high, BYPASS instruction is all 1's. By looking in various cables implementation of clock(), I guess that tck == 1 implies that the TCK signal is high on jtag bus.