Skip to content

Instantly share code, notes, and snippets.

@Vbitz
Created April 11, 2026 13:14
Show Gist options
  • Select an option

  • Save Vbitz/2f7ec133e87224488e2ecab1fdb12f4d to your computer and use it in GitHub Desktop.

Select an option

Save Vbitz/2f7ec133e87224488e2ecab1fdb12f4d to your computer and use it in GitHub Desktop.
Flasher for WCH-LinkE implemented in Pure Go for Linux/amd64
package main
import (
"encoding/binary"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"unsafe"
)
const (
commandTimeoutMS = 200
writeTimeoutMS = 5000
asyncChunkSize = 64
asyncWindow = 16
chipCH32V003 = 0x09
sectorSize = 64
flashAddr = 0x08000000
dmdata0 = 0x04
dmdata1 = 0x05
dmcontrol = 0x10
dmstatus = 0x11
dmhartinfo = 0x12
dmabstractcs = 0x16
dmcommand = 0x17
dmabstractauto = 0x18
dmprogbuf0 = 0x20
dmprogbuf1 = 0x21
dmprogbuf2 = 0x22
dmprogbuf3 = 0x23
dmprogbuf4 = 0x24
dmprogbuf5 = 0x25
dmchipid = 0x7f
flashSTATR = 0x4002200c
flashCTLR = 0x40022010
flashADDR = 0x40022014
flashKEYR = 0x40022004
flashOBKEYR = 0x40022008
flashMODEKEYR = 0x40022024
flashKey1 = 0x45670123
flashKey2 = 0xCDEF89AB
crStartSet = 0x00000040
crPagePG = 0x00010000
crPageER = 0x00020000
crBufLoad = 0x00040000
crBufRST = 0x00080000
flashStatrWrErr = 0x00000010
stateVoid = iota
stateWriteSeq
stateReadSeq
wchVendorID = "1a86"
wchProductID = "8010"
usbInterface = 0
iocNRBits = 8
iocTypeBits = 8
iocSizeBits = 14
iocDirBits = 2
iocNRShift = 0
iocTypeShift = iocNRShift + iocNRBits
iocSizeShift = iocTypeShift + iocTypeBits
iocDirShift = iocSizeShift + iocSizeBits
iocWrite = 1
iocRead = 2
usbdevfsUrbTypeBulk = 3
usbdevfsUrbBulkContinuation = 0x04
)
type linkE struct {
file *os.File
path string
stateTag int
currentAddr uint32
lastWriteFlag bool
autoIncrement bool
flashUnlocked bool
}
type usbdevfsBulktransfer struct {
Ep uint32
Len uint32
Timeout uint32
Data uintptr
}
type usbdevfsDisconnectClaim struct {
Interface uint32
Flags uint32
Driver [256]byte
}
type usbdevfsUrb struct {
Type uint8
Endpoint uint8
Status int32
Flags uint32
Buffer uintptr
BufferLength int32
ActualLength int32
StartFrame int32
NumberOfPackets int32
ErrorCount int32
Signr uint32
UserContext uintptr
}
type inflightURB struct {
urb *usbdevfsUrb
data []byte
}
func main() {
imagePath := flag.String("file", "examples/blink/blink.bin", "firmware binary to flash")
address := flag.Uint("addr", flashAddr, "flash address")
flag.Parse()
if err := run(*imagePath, uint32(*address)); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(imagePath string, address uint32) error {
image, err := os.ReadFile(imagePath)
if err != nil {
return fmt.Errorf("read image: %w", err)
}
if len(image) == 0 {
return errors.New("image is empty")
}
link, err := openLink()
if err != nil {
return err
}
defer link.close()
if err := link.setupProgrammer(); err != nil {
return err
}
chipID, err := link.identifyChip()
if err != nil {
return err
}
fmt.Printf("Detected CH32V003\n")
fmt.Printf("DMCHIPID: %08x\n", chipID)
fmt.Printf("Flashing %s (%d bytes)\n", imagePath, len(image))
if err := link.flashImage(address, image); err != nil {
return err
}
if err := link.rebootTarget(); err != nil {
return err
}
fmt.Println("Image written.")
return nil
}
func openLink() (*linkE, error) {
devicePath, err := findWCHLinkDevice()
if err != nil {
return nil, err
}
f, err := os.OpenFile(devicePath, os.O_RDWR, 0)
if err != nil {
return nil, fmt.Errorf("open %s: %w", devicePath, err)
}
l := &linkE{file: f, path: devicePath, stateTag: stateVoid}
if err := l.claimInterface(usbInterface); err != nil {
f.Close()
return nil, err
}
_ = l.drain()
return l, nil
}
func findWCHLinkDevice() (string, error) {
matches, err := filepath.Glob("/sys/bus/usb/devices/*")
if err != nil {
return "", fmt.Errorf("scan usb sysfs: %w", err)
}
for _, dev := range matches {
vendor, err := os.ReadFile(filepath.Join(dev, "idVendor"))
if err != nil {
continue
}
product, err := os.ReadFile(filepath.Join(dev, "idProduct"))
if err != nil {
continue
}
if strings.TrimSpace(string(vendor)) != wchVendorID || strings.TrimSpace(string(product)) != wchProductID {
continue
}
busnum, err := readIntFile(filepath.Join(dev, "busnum"))
if err != nil {
continue
}
devnum, err := readIntFile(filepath.Join(dev, "devnum"))
if err != nil {
continue
}
return fmt.Sprintf("/dev/bus/usb/%03d/%03d", busnum, devnum), nil
}
return "", errors.New("could not find WCH-LinkE in RISC-V mode (VID:PID 1a86:8010)")
}
func readIntFile(path string) (int, error) {
raw, err := os.ReadFile(path)
if err != nil {
return 0, err
}
return strconv.Atoi(strings.TrimSpace(string(raw)))
}
func (l *linkE) claimInterface(iface uint32) error {
if err := ioctlValuePtr(l.file.Fd(), usbdevfsClaimInterface(), iface); err == nil {
return nil
} else if !errors.Is(err, syscall.EBUSY) {
return fmt.Errorf("claim interface %d on %s: %w", iface, l.path, err)
}
claim := usbdevfsDisconnectClaim{Interface: iface}
if err := ioctlPtr(l.file.Fd(), usbdevfsDisconnectClaimReq(), unsafe.Pointer(&claim)); err != nil {
return fmt.Errorf("disconnect+claim interface %d on %s: %w", iface, l.path, err)
}
return nil
}
func (l *linkE) close() {
if l.file != nil {
_ = l.file.Close()
}
}
func (l *linkE) drain() error {
buf := make([]byte, 1024)
err := l.bulkIn(0x81, buf, 1)
if errors.Is(err, syscall.ETIMEDOUT) {
return nil
}
return err
}
func ioctlRequest(dir, typ, nr, size uintptr) uintptr {
return (dir << iocDirShift) | (typ << iocTypeShift) | (nr << iocNRShift) | (size << iocSizeShift)
}
func usbdevfsBulkReq() uintptr {
return ioctlRequest(iocRead|iocWrite, 'U', 2, unsafe.Sizeof(usbdevfsBulktransfer{}))
}
func usbdevfsClaimInterface() uintptr {
return ioctlRequest(iocRead, 'U', 15, unsafe.Sizeof(uint32(0)))
}
func usbdevfsDisconnectClaimReq() uintptr {
return ioctlRequest(iocRead, 'U', 27, unsafe.Sizeof(usbdevfsDisconnectClaim{}))
}
func usbdevfsSubmitURB() uintptr {
return ioctlRequest(iocRead, 'U', 10, unsafe.Sizeof(usbdevfsUrb{}))
}
func usbdevfsReapURB() uintptr {
return ioctlRequest(iocWrite, 'U', 12, unsafe.Sizeof(uintptr(0)))
}
func usbdevfsReapURBNDelay() uintptr {
return ioctlRequest(iocWrite, 'U', 13, unsafe.Sizeof(uintptr(0)))
}
func ioctlNoArg(fd uintptr, req uintptr, value uintptr) error {
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, req, value)
if errno != 0 {
return errno
}
return nil
}
func ioctlValuePtr(fd uintptr, req uintptr, value uint32) error {
v := value
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, req, uintptr(unsafe.Pointer(&v)))
if errno != 0 {
return errno
}
return nil
}
func ioctlPtr(fd uintptr, req uintptr, ptr unsafe.Pointer) error {
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, req, uintptr(ptr))
if errno != 0 {
return errno
}
return nil
}
func bulkTransfer(fd uintptr, ep uint32, data []byte, timeoutMS int) (int, error) {
var ptr uintptr
if len(data) > 0 {
ptr = uintptr(unsafe.Pointer(&data[0]))
}
req := usbdevfsBulktransfer{
Ep: ep,
Len: uint32(len(data)),
Timeout: uint32(timeoutMS),
Data: ptr,
}
r1, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, usbdevfsBulkReq(), uintptr(unsafe.Pointer(&req)))
if errno != 0 {
return 0, errno
}
n := int(r1)
if ep&0x80 == 0 && n == 0 && len(data) > 0 {
n = len(data)
}
return n, nil
}
func submitBulkURB(fd uintptr, ep uint8, data []byte, flags uint32) (*usbdevfsUrb, error) {
if len(data) == 0 {
return nil, nil
}
urb := &usbdevfsUrb{
Type: usbdevfsUrbTypeBulk,
Endpoint: ep,
Flags: flags,
Buffer: uintptr(unsafe.Pointer(&data[0])),
BufferLength: int32(len(data)),
}
if err := ioctlPtr(fd, usbdevfsSubmitURB(), unsafe.Pointer(urb)); err != nil {
return nil, err
}
return urb, nil
}
func reapURB(fd uintptr, req uintptr) (*usbdevfsUrb, error) {
var reap uintptr
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, req, uintptr(unsafe.Pointer(&reap)))
if errno != 0 {
return nil, errno
}
return (*usbdevfsUrb)(unsafe.Pointer(reap)), nil
}
func removeInflight(inflight []inflightURB, urb *usbdevfsUrb) (inflightURB, []inflightURB, error) {
for i, item := range inflight {
if item.urb == urb {
last := inflight[len(inflight)-1]
inflight[i] = last
inflight = inflight[:len(inflight)-1]
return item, inflight, nil
}
}
return inflightURB{}, inflight, fmt.Errorf("reaped unexpected urb %p", urb)
}
func checkCompleted(item inflightURB) error {
if item.urb.Status != 0 {
return syscall.Errno(-item.urb.Status)
}
if int(item.urb.ActualLength) != len(item.data) {
return fmt.Errorf("short async write: %d/%d", item.urb.ActualLength, len(item.data))
}
return nil
}
func submitBulkPipeline(fd uintptr, ep uint8, data []byte) error {
if len(data) == 0 {
return nil
}
inflight := make([]inflightURB, 0, asyncWindow)
for off := 0; off < len(data); off += asyncChunkSize {
end := off + asyncChunkSize
if end > len(data) {
end = len(data)
}
chunk := data[off:end]
flags := uint32(0)
if off > 0 {
flags |= usbdevfsUrbBulkContinuation
}
urb, err := submitBulkURB(fd, ep, chunk, flags)
if err != nil {
return err
}
inflight = append(inflight, inflightURB{urb: urb, data: chunk})
for {
reaped, err := reapURB(fd, usbdevfsReapURBNDelay())
if err != nil {
if errors.Is(err, syscall.EAGAIN) {
break
}
return err
}
item, next, err := removeInflight(inflight, reaped)
inflight = next
if err != nil {
return err
}
if err := checkCompleted(item); err != nil {
return err
}
}
for len(inflight) >= asyncWindow {
progress := false
for {
reaped, err := reapURB(fd, usbdevfsReapURBNDelay())
if err != nil {
if errors.Is(err, syscall.EAGAIN) {
break
}
return err
}
progress = true
item, next, err := removeInflight(inflight, reaped)
inflight = next
if err != nil {
return err
}
if err := checkCompleted(item); err != nil {
return err
}
}
if !progress {
time.Sleep(100 * time.Microsecond)
}
}
}
for len(inflight) > 0 {
reaped, err := reapURB(fd, usbdevfsReapURBNDelay())
if err != nil {
if errors.Is(err, syscall.EAGAIN) {
time.Sleep(100 * time.Microsecond)
continue
}
return err
}
item, next, err := removeInflight(inflight, reaped)
inflight = next
if err != nil {
return err
}
if err := checkCompleted(item); err != nil {
return err
}
}
return nil
}
func (l *linkE) bulkOut(ep uint8, data []byte) error {
if len(data) == 0 {
return nil
}
if len(data) > 64 {
if err := submitBulkPipeline(l.file.Fd(), ep, data); err != nil {
return fmt.Errorf("usb async bulk out on %s: %w", l.path, err)
}
return nil
}
n, err := bulkTransfer(l.file.Fd(), uint32(ep), data, writeTimeoutMS)
if err != nil {
return fmt.Errorf("usb bulk out on %s: %w", l.path, err)
}
if n != len(data) {
return fmt.Errorf("short write: %d/%d", n, len(data))
}
return nil
}
func (l *linkE) bulkIn(ep uint8, buf []byte, timeoutMS int) error {
_, err := l.bulkInCount(ep, buf, timeoutMS)
return err
}
func (l *linkE) bulkInCount(ep uint8, buf []byte, timeoutMS int) (int, error) {
if len(buf) == 0 {
return 0, nil
}
n, err := bulkTransfer(l.file.Fd(), uint32(ep), buf, timeoutMS)
if err != nil {
return 0, fmt.Errorf("usb bulk in on %s: %w", l.path, err)
}
return n, nil
}
func (l *linkE) command(cmd []byte, replyLen int) ([]byte, error) {
return l.commandWithTimeout(cmd, replyLen, commandTimeoutMS)
}
func (l *linkE) commandWithTimeout(cmd []byte, replyLen int, timeoutMS int) ([]byte, error) {
if err := l.bulkOut(0x01, cmd); err != nil {
return nil, fmt.Errorf("send %s: %w", hexBytes(cmd), err)
}
if replyLen <= 0 {
replyLen = 4
}
buf := make([]byte, 1024)
n, err := l.bulkInCount(0x81, buf, timeoutMS)
if err != nil {
return nil, fmt.Errorf("recv for %s: %w", hexBytes(cmd), err)
}
return buf[:n], nil
}
func (l *linkE) setupProgrammer() error {
_, _ = l.command([]byte{0x81, 0x0d, 0x01, 0xff}, 4)
reply, err := l.command([]byte{0x81, 0x0d, 0x01, 0x01}, 7)
if err != nil {
return fmt.Errorf("read programmer version: %w", err)
}
if len(reply) < 6 {
return fmt.Errorf("short programmer version reply: %s", hexBytes(reply))
}
fmt.Printf("WCH Programmer is %s version %d.%d\n", programmerName(reply[5]), reply[3], reply[4])
if _, err := l.command([]byte{0x81, 0x0c, 0x02, 0x01, 0x02}, 4); err != nil {
return fmt.Errorf("set default interface speed: %w", err)
}
var last []byte
alreadyConnected := false
for tries := 0; tries < 6; tries++ {
reply, err = l.command([]byte{0x81, 0x0d, 0x01, 0x02}, 9)
if err != nil {
return fmt.Errorf("connect target: %w", err)
}
last = reply
if len(reply) == 9 && (reply[4] == 0x00 || (reply[8] != 0x02 && reply[8] != 0x03 && reply[8] != 0x00)) {
if alreadyConnected {
break
}
alreadyConnected = true
}
if len(reply) != 4 && !(len(reply) >= 3 && reply[0] == 0x81 && reply[1] == 0x55 && reply[2] == 0x01) {
break
}
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0x13}, 4); err != nil {
return fmt.Errorf("force reset low: %w", err)
}
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0xff}, 4); err != nil {
return fmt.Errorf("exit programming while retrying: %w", err)
}
time.Sleep(5 * time.Millisecond)
if tries > 3 {
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0x03}, 4); err != nil {
return fmt.Errorf("retry reset sequence: %w", err)
}
}
if _, err := l.command([]byte{0x81, 0x0b, 0x01, 0x01}, 4); err != nil {
return fmt.Errorf("release reset line: %w", err)
}
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0x02}, 9); err != nil {
return fmt.Errorf("retry target connect: %w", err)
}
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0xff}, 4); err != nil {
return fmt.Errorf("retry exit programming: %w", err)
}
}
if len(last) == 4 || (len(last) >= 3 && last[0] == 0x81 && last[1] == 0x55 && last[2] == 0x01) {
return fmt.Errorf("link error, nothing connected to linker: %s", hexBytes(last))
}
return nil
}
func (l *linkE) identifyChip() (uint32, error) {
if _, err := l.readReg32(dmhartinfo); err != nil {
return 0, fmt.Errorf("read DMHARTINFO: %w", err)
}
if err := l.writeReg32(dmcontrol, 0x80000001); err != nil {
return 0, fmt.Errorf("init DMCONTROL: %w", err)
}
if err := l.writeReg32(dmcontrol, 0x80000001); err != nil {
return 0, fmt.Errorf("re-halt target: %w", err)
}
chipID, err := l.readReg32(dmchipid)
if err != nil {
return 0, fmt.Errorf("read DMCHIPID: %w", err)
}
if chipID&0xfff00f00 != 0x00300500 {
return 0, fmt.Errorf("unsupported chip id %08x, this tool currently only supports CH32V003", chipID)
}
if err := l.writeReg32(dmcontrol, 0x80000003); err != nil {
return 0, fmt.Errorf("reset target: %w", err)
}
if err := l.writeReg32(dmcontrol, 0x80000001); err != nil {
return 0, fmt.Errorf("halt after reset: %w", err)
}
if err := l.writeReg32(dmcontrol, 0x80000001); err != nil {
return 0, fmt.Errorf("re-halt after reset: %w", err)
}
time.Sleep(10 * time.Millisecond)
if _, err := l.command([]byte{0x81, 0x0c, 0x02, chipCH32V003, 0x01}, 4); err != nil {
return 0, fmt.Errorf("set CH32V003 interface speed: %w", err)
}
return chipID, nil
}
func (l *linkE) flashImage(address uint32, image []byte) error {
if err := l.voidHighLevelState(); err != nil {
return err
}
if err := l.unlockFlash(); err != nil {
return err
}
paddedLen := ((len(image) - 1) & ^(sectorSize - 1)) + sectorSize
padded := make([]byte, paddedLen)
for i := range padded {
padded[i] = 0xff
}
copy(padded, image)
for off := 0; off < len(padded); off += sectorSize {
pageAddr := address + uint32(off)
page := padded[off : off+sectorSize]
if err := l.eraseSector(pageAddr); err != nil {
return fmt.Errorf("erase sector at %08x: %w", pageAddr, err)
}
if err := l.writeFlashPage(pageAddr, page); err != nil {
return fmt.Errorf("write page at %08x: %w", pageAddr, err)
}
}
return nil
}
func (l *linkE) rebootTarget() error {
if _, err := l.command([]byte{0x81, 0x0b, 0x01, 0x01}, 4); err != nil {
return fmt.Errorf("release reset line: %w", err)
}
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0x02}, 9); err != nil {
return fmt.Errorf("reconnect after flash: %w", err)
}
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0xff}, 4); err != nil {
return fmt.Errorf("exit programming after flash: %w", err)
}
return nil
}
func (l *linkE) holdReset() error {
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0x02}, 9); err != nil {
return fmt.Errorf("enter reset-hold mode: %w", err)
}
if _, err := l.command([]byte{0x81, 0x0d, 0x01, 0x01}, 4); err != nil {
return fmt.Errorf("hold target in reset: %w", err)
}
return nil
}
func (l *linkE) voidHighLevelState() error {
l.stateTag = stateVoid
l.currentAddr = 0
l.lastWriteFlag = false
l.autoIncrement = false
return nil
}
func (l *linkE) waitForDoneOp(ignore bool) error {
timeout := 100
for {
rrv, err := l.readReg32(dmabstractcs)
if err != nil {
return err
}
if rrv&(1<<12) == 0 || timeout <= 0 {
if ((rrv>>8)&7) != 0 || (rrv&(1<<12)) != 0 {
if !ignore {
ds, _ := l.readReg32(dmstatus)
return fmt.Errorf("abstract command fault dmabstractcs=%08x dmstatus=%08x", rrv, ds)
}
if err := l.writeReg32(dmabstractcs, 0x00000700); err != nil {
return err
}
return fmt.Errorf("abstract command fault dmabstractcs=%08x", rrv)
}
return nil
}
timeout--
}
}
func (l *linkE) readWordRaw(address uint32) (uint32, error) {
steps := []struct {
reg uint8
value uint32
}{
{dmabstractauto, 0},
{dmprogbuf0, 0x0004a403},
{dmprogbuf1, 0x00100073},
{dmdata0, address},
{dmcommand, 0x00231009},
{dmcommand, 0x00241000},
{dmcommand, 0x00221008},
}
for _, step := range steps {
if err := l.writeReg32(step.reg, step.value); err != nil {
return 0, err
}
}
return l.readReg32(dmdata0)
}
func (l *linkE) writeWordRaw(address, data uint32) error {
steps := []struct {
reg uint8
value uint32
}{
{dmabstractauto, 0},
{dmprogbuf0, 0x0084a023},
{dmprogbuf1, 0x00100073},
{dmdata0, address},
{dmcommand, 0x00231009},
{dmdata0, data},
{dmcommand, 0x00271008},
}
for _, step := range steps {
if err := l.writeReg32(step.reg, step.value); err != nil {
return err
}
}
return l.waitForDoneOp(false)
}
func (l *linkE) staticUpdatePROGBUFRegs() error {
rr, err := l.readReg32(dmhartinfo)
if err != nil {
return fmt.Errorf("read hart info: %w", err)
}
data0Offset := 0xe0000000 | (rr & 0x7ff)
steps := []struct {
reg uint8
value uint32
}{
{dmabstractauto, 0},
{dmdata0, data0Offset},
{dmcommand, 0x0023100a},
{dmdata0, data0Offset + 4},
{dmcommand, 0x0023100b},
{dmdata0, flashSTATR},
{dmcommand, 0x0023100c},
{dmdata0, crPagePG | crBufLoad},
{dmcommand, 0x0023100d},
}
for _, step := range steps {
if err := l.writeReg32(step.reg, step.value); err != nil {
return err
}
}
return nil
}
func isFlashAddress(addr uint32) bool {
return addr&0xe0000000 == 0
}
func (l *linkE) writeWord(address, data uint32) error {
isFlash := isFlashAddress(address)
if l.stateTag != stateWriteSeq || l.lastWriteFlag != isFlash {
didDisableReq := false
if l.stateTag != stateWriteSeq {
if err := l.writeReg32(dmabstractauto, 0); err != nil {
return err
}
didDisableReq = true
if l.stateTag != stateReadSeq {
if err := l.staticUpdatePROGBUFRegs(); err != nil {
return err
}
}
if err := l.writeReg32(dmprogbuf0, 0x41844100); err != nil {
return err
}
if err := l.writeReg32(dmprogbuf1, 0x0491c080); err != nil {
return err
}
}
if isFlash {
if err := l.writeReg32(dmprogbuf2, 0x0001c184); err != nil {
return err
}
if err := l.writeReg32(dmprogbuf3, 0x4200c254); err != nil {
return err
}
if err := l.writeReg32(dmprogbuf4, 0xfc758805); err != nil {
return err
}
if err := l.writeReg32(dmprogbuf5, 0x90029002); err != nil {
return err
}
} else {
if err := l.writeReg32(dmprogbuf2, 0x9002c184); err != nil {
return err
}
}
if err := l.writeReg32(dmdata1, address); err != nil {
return err
}
if err := l.writeReg32(dmdata0, data); err != nil {
return err
}
if err := l.writeReg32(dmcommand, 0x00240000); err != nil {
return err
}
if didDisableReq {
if err := l.writeReg32(dmabstractauto, 1); err != nil {
return err
}
}
l.lastWriteFlag = isFlash
l.stateTag = stateWriteSeq
l.currentAddr = address
} else {
if address != l.currentAddr {
if err := l.writeReg32(dmdata1, address); err != nil {
return err
}
}
if err := l.writeReg32(dmdata0, data); err != nil {
return err
}
}
if isFlash {
if err := l.waitForDoneOp(false); err != nil {
return err
}
}
l.currentAddr = address + 4
return nil
}
func (l *linkE) readWord(address uint32) (uint32, error) {
autoincrement := true
if address == flashCTLR || address == flashSTATR {
autoincrement = false
}
if l.stateTag != stateReadSeq || address != l.currentAddr || autoincrement != l.autoIncrement {
if l.stateTag != stateReadSeq || !autoincrement {
if l.stateTag != stateWriteSeq {
if err := l.staticUpdatePROGBUFRegs(); err != nil {
return 0, err
}
}
if err := l.writeReg32(dmabstractauto, 0); err != nil {
return 0, err
}
if err := l.writeReg32(dmprogbuf0, 0x40044180); err != nil {
return 0, err
}
if autoincrement {
if err := l.writeReg32(dmprogbuf1, 0xc1040411); err != nil {
return 0, err
}
} else {
if err := l.writeReg32(dmprogbuf1, 0xc1040001); err != nil {
return 0, err
}
}
if err := l.writeReg32(dmprogbuf2, 0x9002c180); err != nil {
return 0, err
}
if autoincrement {
if err := l.writeReg32(dmabstractauto, 1); err != nil {
return 0, err
}
}
l.autoIncrement = autoincrement
}
if err := l.writeReg32(dmdata1, address); err != nil {
return 0, err
}
if err := l.writeReg32(dmcommand, 0x00240000); err != nil {
return 0, err
}
l.stateTag = stateReadSeq
l.currentAddr = address
if err := l.waitForDoneOp(true); err != nil {
_ = l.voidHighLevelState()
}
}
if l.autoIncrement {
l.currentAddr += 4
}
value, err := l.readReg32(dmdata0)
if err != nil {
return 0, err
}
return value, nil
}
func (l *linkE) waitForFlash() error {
deadline := time.Now().Add(3 * time.Second)
for time.Now().Before(deadline) {
rw, err := l.readWordRaw(flashSTATR)
if err != nil {
return err
}
if rw&3 == 0 {
if rw&flashStatrWrErr != 0 {
return fmt.Errorf("memory protection error statr=%08x", rw)
}
return nil
}
time.Sleep(100 * time.Microsecond)
}
rw, _ := l.readWordRaw(flashSTATR)
return fmt.Errorf("flash timed out statr=%08x", rw)
}
func (l *linkE) unlockFlash() error {
if l.flashUnlocked {
return nil
}
rw, err := l.readWordRaw(flashCTLR)
if err != nil {
return fmt.Errorf("read flash ctlr: %w", err)
}
if rw&0x8080 != 0 {
for _, step := range []struct {
addr uint32
value uint32
}{
{flashKEYR, flashKey1},
{flashKEYR, flashKey2},
{flashOBKEYR, flashKey1},
{flashOBKEYR, flashKey2},
{flashMODEKEYR, flashKey1},
{flashMODEKEYR, flashKey2},
} {
if err := l.writeWordRaw(step.addr, step.value); err != nil {
return fmt.Errorf("unlock flash write %08x: %w", step.addr, err)
}
}
rw, err = l.readWordRaw(flashCTLR)
if err != nil {
return fmt.Errorf("re-read flash ctlr: %w", err)
}
if rw&0x8080 != 0 {
return fmt.Errorf("flash is not unlocked ctlr=%08x", rw)
}
}
l.flashUnlocked = true
return nil
}
func (l *linkE) eraseSector(address uint32) error {
if err := l.waitForFlash(); err != nil {
return err
}
if err := l.writeWordRaw(flashCTLR, crPageER); err != nil {
return err
}
if err := l.writeWordRaw(flashADDR, address); err != nil {
return err
}
if err := l.writeWordRaw(flashCTLR, crStartSet|crPageER); err != nil {
return err
}
if err := l.voidHighLevelState(); err != nil {
return err
}
return l.waitForFlash()
}
func (l *linkE) writeFlashPage(address uint32, page []byte) error {
if len(page) != sectorSize {
return fmt.Errorf("page size must be %d, got %d", sectorSize, len(page))
}
if err := l.writeWordRaw(flashCTLR, crPagePG); err != nil {
return err
}
if err := l.writeWordRaw(flashCTLR, crBufRST|crPagePG); err != nil {
return err
}
if err := l.voidHighLevelState(); err != nil {
return err
}
if err := l.waitForFlash(); err != nil {
return err
}
for i := 0; i < len(page); i += 4 {
word := binary.LittleEndian.Uint32(page[i : i+4])
if err := l.writeWord(address+uint32(i), word); err != nil {
return err
}
}
if err := l.writeWordRaw(flashADDR, address); err != nil {
return err
}
if err := l.writeWordRaw(flashCTLR, crPagePG|crStartSet); err != nil {
return err
}
if err := l.voidHighLevelState(); err != nil {
return err
}
return l.waitForFlash()
}
func (l *linkE) writeReg32(reg uint8, value uint32) error {
req := []byte{
0x81, 0x08, 0x06, reg,
byte(value >> 24), byte(value >> 16), byte(value >> 8), byte(value),
0x02,
}
if err := l.bulkOut(0x01, req); err != nil {
return fmt.Errorf("send register write %02x: %w", reg, err)
}
for attempt := 0; attempt < 4; attempt++ {
reply := make([]byte, 64)
n, err := l.bulkInCount(0x81, reply, commandTimeoutMS)
if err != nil {
return fmt.Errorf("recv register write %02x: %w", reg, err)
}
reply = reply[:n]
if len(reply) == 9 && reply[0] == 0x82 && reply[1] == 0x08 && reply[2] == 0x06 && reply[3] == reg && reply[8] != 0x02 && reply[8] != 0x03 {
return nil
}
}
return fmt.Errorf("bad register write response for reg %02x", reg)
}
func (l *linkE) readReg32(reg uint8) (uint32, error) {
req := []byte{0x81, 0x08, 0x06, reg, 0, 0, 0, 0, 0x01}
if err := l.bulkOut(0x01, req); err != nil {
return 0, fmt.Errorf("send register read %02x: %w", reg, err)
}
for attempt := 0; attempt < 4; attempt++ {
reply := make([]byte, 64)
n, err := l.bulkInCount(0x81, reply, commandTimeoutMS)
if err != nil {
return 0, fmt.Errorf("recv register read %02x: %w", reg, err)
}
reply = reply[:n]
if len(reply) == 9 && reply[0] == 0x82 && reply[1] == 0x08 && reply[2] == 0x06 && reply[3] == reg && reply[8] != 0x02 && reply[8] != 0x03 {
return binary.BigEndian.Uint32(reply[4:8]), nil
}
}
return 0, fmt.Errorf("bad register read response for reg %02x", reg)
}
func bytesRange(data []byte, start, end int) []byte {
if start >= len(data) {
return nil
}
if end > len(data) {
end = len(data)
}
if start < 0 {
start = 0
}
return data[start:end]
}
func programmerName(id byte) string {
switch id {
case 1:
return "CH549"
case 2:
return "CH32V307"
case 3:
return "CH32V203"
case 4:
return "LinkB"
case 5:
return "LinkW"
case 18:
return "LinkE"
default:
return fmt.Sprintf("unknown-%02x", id)
}
}
func hexBytes(data []byte) string {
if len(data) == 0 {
return "<empty>"
}
parts := make([]string, len(data))
for i, b := range data {
parts[i] = fmt.Sprintf("%02x", b)
}
return strings.Join(parts, " ")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment