-
-
Notifications
You must be signed in to change notification settings - Fork 784
Adding target drivers
This is a placeholder of cut-and-pasted text until I have a chance to write up some real documentation for the target API.
https://github.com/blacksphere/blackmagic/blob/master/src/include/target.h
This is the public API, used by the GDB server. Nothing outside of this header should be accessed from outside of src/target/*
https://github.com/blacksphere/blackmagic/blob/master/src/target/target_internal.h
Structure definitions and convenience function for use in target implementations. Specific target implementations fill in the function pointers in these structures for their device specific implementations
Supported JTAG devices are defined here: https://github.com/blacksphere/blackmagic/blob/master/src/target/jtag_scan.c#L35
The .handler
function is called when a device IDCODE matches .idcode
with .idmask
applied.
It is the responsibility of the handler function to instantiate a new target with target_new
and fill in the access methods.
https://github.com/blacksphere/blackmagic/blob/master/src/target/adiv5.c#L156
This table is a list of known Coresight CIDR, PIDR values. The ROM table is read from the ADIv5 Mem-AP, and the appropriate probe function is called here https://github.com/blacksphere/blackmagic/blob/master/src/target/adiv5.c#L323
There is currently support for:
The generic Cortex-M driver calls probe functions for each supported vendor device: https://github.com/blacksphere/blackmagic/blob/master/src/target/cortexm.c#L257
These probe functions should probe the target device, add any memories they support with target_add_ram
and target_add_flash
, and return true
for any device they support, or return false
otherwise.
The ADIv5 interface does not directly provide a way to write to flash. It's implemented differently for different target devices. This is done by filling in the function pointers in a struct target_flash
structure and calling target_add_flash
on the target in the device specific probe function.
struct target_flash {
target_addr start; /* Base address for this block of flash memory */
size_t length; /* Length of this block of flash memory */
size_t blocksize; /* Erase sector size for this block of flash memory */
flash_erase_func erase; /* Function pointer to flash erase function.
Called with address and length aligned on .blocksize */
flash_write_func write; /* Function pointer to flash write function.
Called with address and length aligned according to .align,
and padded with value in .erased. */
flash_done_func done; /* Called at the end of flash operations,
after all calls to .write */
int align; /* Alignment of buffer for .write */
uint8_t erased; /* Buffer pad value for .write */
}
There are a few basic patterns for how this is done in practice:
- Direct - writes are passed directly to the target driver which programs the flash directly using MMIO. (eg. kinetis.c)
- Buffered - the target layer will buffer write packets from GDB until and pass to the driver as writes of whole sectors. The driver will program the target flash directly using MMIO. This works well when whole sectors can be programmed with sequencial writes. (eg. stm32l0.c)
- Stubbed - writes are passed directly to the target driver which writes a program stub and the data payload to the target. The stub is then executed to program the device. This works well when flash can't be programmed with sequential writes. (eg. stm32f1.c)
- LPC - NXP devices include a built in ROM for programming flash. There is no published MMIO mechanism to program the flash on these devices. (eg. lpc11xx.c)
For devices than can only efficiently program flash sectors of a fixed size there is some additional support
in the target_flash
structure. To make use of this mechanism, the .write
and .done
pointers must be
assigned to target_flash_write_buffered
and target_flash_done_buffered
respectively.
struct target_flash {
...
/* For buffered flash */
size_t buf_size; /* Size of buffer for buffered operations */
flash_write_func write_buf; /* Function pointer to buffered flash write function.
Called with address aligned according to .buf_size,
and padded with value in .erased. The buffer
will always be exactly .buf_size bytes */
};
static int skeleton_flash_erase(struct target_flash *f,
uint32_t addr, size_t len) {…}
static int skeleton_flash_write(struct target_flash *f,
uint32_t dest, const void *src, size_t len) {…}
static void skeleton_add_flash(target *t)
{
struct target_flash *f = calloc(1, sizeof(*f));
f->start = SKELETON_FLASH_BASE;
f->length = SKELETON_FLASH_SIZE;
f->blocksize = SKELETON_BLOCKSIZE;
f->erase = skeleton_flash_erase;
f->write = skeleton_flash_write;
target_add_flash(t, f);
}
bool skeleton_probe(target *t)
{
if (target_mem_read32(t, SKELETON_DEVID_ADDR) == SKELETON_DEVID) {
skeleton_add_flash(t);
return true;
} else {
return false;
}
}