From 680e4f77ab008a45b597c810aaf1e7b2e742ab26 Mon Sep 17 00:00:00 2001 From: Bertold Van den Bergh Date: Sun, 25 Jul 2021 23:09:00 +0200 Subject: [PATCH] Initial commit of code --- cli/.gitignore | 1 + cli/asm/asm.sh | 5 + cli/asm/dumprom.asm | 52 +++++ cli/asm/dumprom.bin | Bin 0 -> 43 bytes cli/cli.go | 1 + cli/dumprom.go | 174 +++++++++++++++ cli/gpio.go | 48 ++++ cli/hexdump.go | 68 ++++++ cli/hid.go | 41 ++++ cli/i2c.go | 62 ++++++ cli/listhid.go | 31 +++ cli/main.go | 98 +++++++++ cli/mappers.go | 26 +++ cli/memio.go | 173 +++++++++++++++ go.mod | 11 + go.sum | 34 +++ mshal/asm/asm.sh | 11 + mshal/asm/code.asm | 2 + mshal/asm/code.bin | 1 + mshal/asm/gpio.asm | 11 + mshal/asm/gpio.bin | 1 + mshal/asm/hook.asm | 38 ++++ mshal/asm/hook_2106.bin | Bin 0 -> 52 bytes mshal/asm/hook_2109.bin | Bin 0 -> 52 bytes mshal/asm/i2cRead2109.asm | 3 + mshal/asm/i2cRead2109.bin | 1 + mshal/errors.go | 14 ++ mshal/hal.go | 123 +++++++++++ mshal/hal_patch_call.go | 103 +++++++++ mshal/hal_patch_eeprom.go | 152 +++++++++++++ mshal/hal_patch_gpio.go | 38 ++++ mshal/hal_patch_i2c.go | 95 ++++++++ mshal/hal_patch_install.go | 434 +++++++++++++++++++++++++++++++++++++ mshal/hal_patch_movc.go | 51 +++++ mshal/hal_region.go | 98 +++++++++ mshal/hal_rom.go | 213 ++++++++++++++++++ mshal/region.go | 97 +++++++++ 37 files changed, 2311 insertions(+) create mode 100644 cli/.gitignore create mode 100755 cli/asm/asm.sh create mode 100644 cli/asm/dumprom.asm create mode 100644 cli/asm/dumprom.bin create mode 100644 cli/cli.go create mode 100644 cli/dumprom.go create mode 100644 cli/gpio.go create mode 100644 cli/hexdump.go create mode 100644 cli/hid.go create mode 100644 cli/i2c.go create mode 100644 cli/listhid.go create mode 100644 cli/main.go create mode 100644 cli/mappers.go create mode 100644 cli/memio.go create mode 100644 go.mod create mode 100644 go.sum create mode 100755 mshal/asm/asm.sh create mode 100644 mshal/asm/code.asm create mode 100644 mshal/asm/code.bin create mode 100644 mshal/asm/gpio.asm create mode 100644 mshal/asm/gpio.bin create mode 100644 mshal/asm/hook.asm create mode 100644 mshal/asm/hook_2106.bin create mode 100644 mshal/asm/hook_2109.bin create mode 100644 mshal/asm/i2cRead2109.asm create mode 100644 mshal/asm/i2cRead2109.bin create mode 100644 mshal/errors.go create mode 100644 mshal/hal.go create mode 100644 mshal/hal_patch_call.go create mode 100644 mshal/hal_patch_eeprom.go create mode 100644 mshal/hal_patch_gpio.go create mode 100644 mshal/hal_patch_i2c.go create mode 100644 mshal/hal_patch_install.go create mode 100644 mshal/hal_patch_movc.go create mode 100644 mshal/hal_region.go create mode 100644 mshal/hal_rom.go create mode 100644 mshal/region.go diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 0000000..573c0c4 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1 @@ +cli diff --git a/cli/asm/asm.sh b/cli/asm/asm.sh new file mode 100755 index 0000000..c10e6fd --- /dev/null +++ b/cli/asm/asm.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +as31 -Fbin dumprom.asm +#as31 -Fbin dumprom.asm + diff --git a/cli/asm/dumprom.asm b/cli/asm/dumprom.asm new file mode 100644 index 0000000..494bba6 --- /dev/null +++ b/cli/asm/dumprom.asm @@ -0,0 +1,52 @@ +.equ CommAddr, 0xDEAD + +; Wait for number of bytes to be written +MOV DPTR, #CommAddr +MOVX A, @DPTR +JNZ work +RET +work: +MOV R7, A + +; Read source address +INC DPTR +MOVX A, @DPTR +MOV R0, A +INC DPTR +MOVX A, @DPTR +MOV R1, A + +; Read dest address +INC DPTR +MOVX A, @DPTR +MOV R2, A +INC DPTR +MOVX A, @DPTR +MOV R3, A + +; Clear code index +MOV R4, #0 + +dump: +; Read from code +MOV DPH, R0 +MOV DPL, R1 + +MOV A, R4 +MOVC A, @A+DPTR +; Write to XDATA +MOV DPH, R2 +MOV DPL, R3 +MOVX @DPTR, A + +; Update indices +INC R3 +INC R4 +DJNZ R7, dump + +; Signal completion +MOV DPTR, #CommAddr +CLR A +MOVX @DPTR, A +RET + diff --git a/cli/asm/dumprom.bin b/cli/asm/dumprom.bin new file mode 100644 index 0000000000000000000000000000000000000000..b637a72da6b9350e209a57f274a10c71e543aa53 GIT binary patch literal 43 zcmV+`0M!4G-mTzp0V4mS;P|89`J>?aqu~2|0EmN$g6xxugNuUj3k=`!klwB2@FHq^ B8> 8), byte(p.addrMailbox)}, -1) + + tmpBufLen := 1 + int(0xFF-byte(p.addrTemp)) + if tmpBufLen > p.addrTempLen { + tmpBufLen = p.addrTempLen + } + if tmpBufLen > 255 { + tmpBufLen = 255 + } + + config := ms.MemoryRegionGet(mshal.MemoryRegionUserConfig) + configOld := make([]byte, config.GetLength()) + configNew := make([]byte, config.GetLength()) + + /* Read orig hooks */ + if _, err := config.Access(false, 0, configOld); err != nil { + return nil, err + } + + /* Disable all userhooks */ + if _, err := config.Access(true, 0, configNew); err != nil { + return nil, err + } + + /* Disable reading */ + xdata := ms.MemoryRegionGet(mshal.MemoryRegionRAM) + if err := mshal.WriteByte(xdata, p.addrMailbox, 0); err != nil { + return nil, err + } + + /* Read original code */ + orig := make([]byte, len(dumpBlob)) + _, err := xdata.Access(false, p.addrLoad, orig) + if err != nil { + return nil, nil + } + + /* Ensure CPU left the affected area */ + time.Sleep(time.Second) + + /* Write new code */ + if _, err := xdata.Access(true, p.addrLoad, dumpBlob); err != nil { + return nil, err + } + + /* Enable USB hook */ + if err := mshal.WriteByte(config, p.addrHook, p.valueHook); err != nil { + return nil, err + } + + buf := make([]byte, 65536) + + addr := 0 + lastAddr := addr + len(buf) + index := 0 + + for { + config := []byte{byte(addr >> 8), byte(addr), byte(p.addrTemp >> 8), byte(p.addrTemp)} + remaining := lastAddr - addr + if remaining == 0 { + break + } + if remaining > tmpBufLen { + remaining = tmpBufLen + } + + if _, err := xdata.Access(true, p.addrMailbox+1, config); err != nil { + return nil, err + } + + if err := mshal.WriteByte(xdata, p.addrMailbox, byte(remaining)); err != nil { + return nil, err + } + + ack, err := mshal.ReadByte(xdata, p.addrMailbox) + if err != nil { + return nil, err + } + + if ack != 0 { + return nil, mshal.ErrorPatchFailed + } + + _, err = xdata.Access(false, p.addrTemp, buf[index:(index+remaining)]) + if err != nil { + return nil, err + } + + addr += remaining + index += remaining + fmt.Printf("Dumping code: %d bytes read.\n", addr) + } + + /* Remove overwritten code from dump */ + buf = bytes.ReplaceAll(buf, dumpBlob, orig) + + /* Disable USB hook */ + if err := mshal.WriteByte(config, p.addrHook, 0); err != nil { + return nil, err + } + + /* Ensure CPU left code */ + time.Sleep(25 * time.Millisecond) + + /* Put original code back */ + if _, err := xdata.Access(true, p.addrLoad, orig); err != nil { + return nil, err + } + + /* Re-enable old hooks */ + _, err = config.Access(true, 0, configOld) + return buf, err +} diff --git a/cli/gpio.go b/cli/gpio.go new file mode 100644 index 0000000..70425c8 --- /dev/null +++ b/cli/gpio.go @@ -0,0 +1,48 @@ +package main + +import ( + "errors" + "fmt" + "strconv" +) + +type GPIOGet struct { +} + +func (g *GPIOGet) Run(c *Context) error { + value, isOutput, err := c.hal.GPIOUpdate(0, 0, 0, 0) + if err != nil { + return err + } + + fmt.Printf("Pin: 76543210\nValue: %08s\nOutput: %08s\n", strconv.FormatInt(int64(value), 2), strconv.FormatInt(int64(isOutput), 2)) + + return nil +} + +type GPIOSet struct { + Command string `arg name:"command" help:"Set value: gpio=value (eg: 4=1). Set as input: gpio? (eg: 4?)" type:"string"` +} + +func parseDigit(digit byte) int { + if digit < '0' || digit > '9' { + return 0 + } + + return int(digit - '0') +} + +func (g *GPIOSet) Run(c *Context) error { + if len(g.Command) == 3 && g.Command[1] == '=' { + return c.hal.GPIOWrite(parseDigit(g.Command[0]), g.Command[2] != '0') + } else if len(g.Command) == 2 && g.Command[1] == '?' { + value, err := c.hal.GPIORead(parseDigit(g.Command[0])) + if err != nil { + return err + } + fmt.Println(value) + } else { + return errors.New("Invalid syntax") + } + return nil +} diff --git a/cli/hexdump.go b/cli/hexdump.go new file mode 100644 index 0000000..0373aea --- /dev/null +++ b/cli/hexdump.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + + "github.com/fatih/color" +) + +func hexdump(offset int, data []byte, mark []bool) string { + var result string + red := color.New(color.FgRed) + + for len(data) > 0 { + l := len(data) + if l > 32 { + l = 32 + } + work := data[:l] + data = data[l:] + var workMark []bool + if mark != nil { + workMark = mark[:l] + mark = mark[l:] + } + + var workHex string + var workAscii string + for i := 0; i < 32; i++ { + m := byte(0) + valid := i < len(work) + delta := false + if valid { + m = work[i] + if workMark != nil && workMark[i] { + delta = true + } + } + + if valid { + if delta { + workHex += red.Sprintf("%02x ", m) + } else { + workHex += fmt.Sprintf("%02x ", m) + } + + if m < 32 || m > 126 { + m = '.' + } + if delta { + workAscii += red.Sprintf("%c", m) + } else { + workAscii += fmt.Sprintf("%c", m) + } + } else { + workHex += " " + workAscii += " " + } + if i%8 == 7 { + workHex += " " + } + } + + result += fmt.Sprintf("%08x %s|%s|\n", offset, workHex, workAscii) + offset += l + } + + return result +} diff --git a/cli/hid.go b/cli/hid.go new file mode 100644 index 0000000..100f413 --- /dev/null +++ b/cli/hid.go @@ -0,0 +1,41 @@ +package main + +import ( + "os" + + "github.com/sstallion/go-hid" + "errors" +) + +func SearchDevice(foundHandler func(info *hid.DeviceInfo) error) error { + return hid.Enumerate(uint16(CLI.VID), uint16(CLI.PID), func(info *hid.DeviceInfo) error { + if CLI.Serial != "" && info.SerialNbr != CLI.Serial { + return nil + } + if CLI.RawPath != "" && info.Path != CLI.RawPath { + return nil + } + + return foundHandler(info) + }) +} + +func OpenDevice() (*hid.Device, error) { + var dev *hid.Device + err := SearchDevice(func(info *hid.DeviceInfo) error { + d, err := hid.Open(info.VendorID, info.ProductID, info.SerialNbr) + if err == nil { + dev = d + return errors.New("Done") + } + return err + }) + if dev != nil { + return dev, nil + } + if err == nil { + err = os.ErrNotExist + } + + return nil, err +} diff --git a/cli/i2c.go b/cli/i2c.go new file mode 100644 index 0000000..2111a2f --- /dev/null +++ b/cli/i2c.go @@ -0,0 +1,62 @@ +package main + +import ( + "encoding/hex" + "fmt" + + "github.com/BertoldVdb/ms-tools/mshal" +) + +type I2CScan struct { +} + +func (l *I2CScan) Run(c *Context) error { + fmt.Printf("Detected I2C devices:\r\n ") + for i := 0; i < 16; i++ { + fmt.Printf("%02X ", i) + } + for i := byte(0); i < 0x80; i++ { + ok, err := c.hal.I2CTransfer(i, []byte{0}, nil) + if err != nil { + return err + } + + if i&15 == 0 { + fmt.Printf("\r\n%02x ", i) + } + + if ok { + fmt.Printf("%02X ", i) + } else { + fmt.Printf("-- ") + } + } + fmt.Println() + return nil +} + +type I2CTransfer struct { + Addr int `arg name:"addr" help:"I2C device address" type:"int"` + + Write string `optional help:"Hex string to write to device"` + Read int `optional help:"Number of bytes to read back"` +} + +func (l *I2CTransfer) Run(c *Context) error { + wrBuf, err := hex.DecodeString(l.Write) + if err != nil { + return err + } + + rdBuf := make([]byte, l.Read) + ok, err := c.hal.I2CTransfer(byte(l.Addr), wrBuf, rdBuf) + if err != nil { + return err + } + if !ok { + return mshal.ErrorNoAck + } + + fmt.Println(hexdump(0, rdBuf, nil)) + return nil +} diff --git a/cli/listhid.go b/cli/listhid.go new file mode 100644 index 0000000..d4e1ef8 --- /dev/null +++ b/cli/listhid.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + + "github.com/sstallion/go-hid" +) + +type ListHIDCmd struct { +} + +func (l *ListHIDCmd) Run(c *Context) error { + return SearchDevice(func(info *hid.DeviceInfo) error { + fmt.Printf("%s: ID %04x:%04x %s %s\n", + info.Path, info.VendorID, info.ProductID, info.MfrStr, info.ProductStr) + fmt.Println("Device Information:") + fmt.Printf("\tPath %s\n", info.Path) + fmt.Printf("\tVendorID %04x\n", info.VendorID) + fmt.Printf("\tProductID %04x\n", info.ProductID) + fmt.Printf("\tSerialNbr %s\n", info.SerialNbr) + fmt.Printf("\tReleaseNbr %x.%x\n", info.ReleaseNbr>>8, info.ReleaseNbr&0xff) + fmt.Printf("\tMfrStr %s\n", info.MfrStr) + fmt.Printf("\tProductStr %s\n", info.ProductStr) + fmt.Printf("\tUsagePage %#x\n", info.UsagePage) + fmt.Printf("\tUsage %#x\n", info.Usage) + fmt.Printf("\tInterfaceNbr %d\n", info.InterfaceNbr) + fmt.Println() + + return nil + }) +} diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 0000000..94b60a8 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "os" + + "github.com/BertoldVdb/ms-tools/mshal" + "github.com/alecthomas/kong" + "github.com/sstallion/go-hid" +) + +type Context struct { + dev *hid.Device + hal *mshal.HAL +} + +var CLI struct { + VID int `optional type:"hex" help:"The USB Vendor ID."` + PID int `optional type:"hex" help:"The USB Product ID."` + Serial string `optional help:"The USB Serial."` + RawPath string `optional help:"The USB Device Path."` + LogLevel int `optional help:"Higher values give more output."` + + NoPatch bool `optional help:"Do not attempt to patch running firmware."` + EEPROMSize int `optional help:"Specify EEPROM size to skip autodetection."` + NoFirmware bool `optional help:"Do not use firmware in EEPROM."` + + ListDev ListHIDCmd `cmd help:"List devices."` + + ListRegions MEMIOListRegions `cmd help:"List available memory regions."` + Read MEMIOReadCmd `cmd help:"Read and dump memory."` + Write MEMIOWriteCmd `cmd help:"Write value to memory."` + WriteFile MEMIOWriteFileCmd `cmd help:"Write file to memory."` + + DumpROM DumpROM `cmd help:"Dump ROM (code) to file by uploading custom code."` + + I2CScan I2CScan `cmd name:"i2c-scan" help:"Scan I2C bus and shown discovered devices."` + I2CTransfer I2CTransfer `cmd name:"i2c-txfr" help:"Perform I2C transfer."` + + GPIOSet GPIOSet `cmd name:"gpio-set" help:"Set GPIO pin value and direction."` + GPIOGet GPIOGet `cmd name:"gpio-get" help:"Get GPIO values."` +} + +func main() { + k, err := kong.New(&CLI, + kong.NamedMapper("int", intMapper{}), + kong.NamedMapper("hex", intMapper{base: 16})) + if err != nil { + fmt.Println(err) + return + } + + ctx, err := k.Parse(os.Args[1:]) + if err != nil { + fmt.Println(err) + return + } + + hid.Init() + defer hid.Exit() + + c := &Context{} + if ctx.Command() != "list-dev" { + dev, err := OpenDevice() + if err != nil { + fmt.Println("Failed to open device", err) + return + } + defer dev.Close() + + c.dev = dev + config := mshal.HALConfig{ + PatchTryInstall: !CLI.NoPatch, + + PatchProbeEEPROM: true, + EEPromSize: CLI.EEPROMSize, + + PatchIgnoreUserFirmware: CLI.NoFirmware, + + LogFunc: func(level int, format string, param ...interface{}) { + if level > CLI.LogLevel { + return + } + str := fmt.Sprintf(format, param...) + fmt.Printf("HAL(%d): %s\n", level, str) + }, + } + + c.hal, err = mshal.New(dev, config) + if err != nil { + fmt.Println("Failed to create HAL", err) + return + } + } + + err = ctx.Run(c) + ctx.FatalIfErrorf(err) +} diff --git a/cli/mappers.go b/cli/mappers.go new file mode 100644 index 0000000..1caa918 --- /dev/null +++ b/cli/mappers.go @@ -0,0 +1,26 @@ +package main + +import ( + "reflect" + "strconv" + + "github.com/alecthomas/kong" +) + +type intMapper struct { + base int +} + +func (h intMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error { + var value string + err := ctx.Scan.PopValueInto("hex", &value) + if err != nil { + return err + } + i, err := strconv.ParseInt(value, h.base, 64) + if err != nil { + return err + } + target.SetInt(i) + return nil +} diff --git a/cli/memio.go b/cli/memio.go new file mode 100644 index 0000000..41d88f6 --- /dev/null +++ b/cli/memio.go @@ -0,0 +1,173 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "time" + + "github.com/BertoldVdb/ms-tools/mshal" + "github.com/inancgumus/screen" +) + +type MEMIOListRegions struct { +} + +func (l *MEMIOListRegions) Run(c *Context) error { + var regions []mshal.MemoryRegion + for _, m := range c.hal.MemoryRegionList() { + regions = append(regions, c.hal.MemoryRegionGet(m)) + } + + fmt.Printf("Region | Length | Parent (%s)\n", c.hal.GetDeviceType()) + + for _, m := range regions { + parent, offset := mshal.RecursiveGetParentAddress(m, 0) + fmt.Printf("%-13s| %5d |", m.GetName(), m.GetLength()) + if parent != m { + fmt.Printf(" %s.%04X", parent.GetName(), offset) + } + fmt.Printf("\n") + } + return nil +} + +type Region struct { + Region string `arg name:"region" help:"Memory region to access."` + Addr int `arg name:"addr" help:"Addresses to access." type:"int"` +} + +type MEMIOReadCmd struct { + Loop int `optional help:"0=Perform once, 1=Mark changes since start, 2=Mark changes since previous iteration."` + Filename string `optional help:"File to write dump to."` + + Region Region `embed` + Amount int `arg name:"amount" help:"Number of bytes to read, omit for maximum." optional default:"0"` +} + +func (l *MEMIOReadCmd) Run(c *Context) error { + if l.Loop < 0 || l.Loop > 2 { + return errors.New("Loop flag out of range") + } + + region := c.hal.MemoryRegionGet(mshal.MemoryRegionNameType(l.Region.Region)) + if region == nil { + return errors.New("Invalid memory region") + } + + if l.Amount == 0 { + l.Amount = region.GetLength() + } + + var oldBuf []byte + var mark []bool + for { + startTime := time.Now() + if l.Loop == 2 || mark == nil { + mark = make([]bool, l.Amount) + } + + buf := make([]byte, l.Amount) + n, err := region.Access(false, l.Region.Addr, buf) + if err != nil { + return fmt.Errorf("Read error: %s", err.Error()) + } + buf = buf[:n] + + if l.Filename != "" { + err := ioutil.WriteFile(l.Filename, buf, 0644) + return err + } + + if l.Amount == 1 { + if len(buf) < 1 { + return errors.New("0 bytes returned") + } + fmt.Printf("0x%02x\n", buf[0]) + } else { + if l.Loop != 0 { + screen.Clear() + screen.MoveTopLeft() + if oldBuf != nil { + for i, m := range oldBuf { + if m != buf[i] { + mark[i] = true + } + } + } + } + fmt.Println(hexdump(l.Region.Addr, buf, mark)) + } + + oldBuf = buf + + if l.Loop == 0 { + break + } + d := time.Now().Sub(startTime) + td := 200 * time.Millisecond + if d < td { + time.Sleep(td - d) + } + } + + return nil +} + +type MEMIOWriteCmd struct { + Zone Region `embed` + Value int `arg name:"value" help:"Value to write." type:"int"` +} + +func (w MEMIOWriteCmd) Run(c *Context) error { + region := c.hal.MemoryRegionGet(mshal.MemoryRegionNameType(w.Zone.Region)) + if region == nil { + return errors.New("Invalid memory region") + } + + var value [1]byte + value[0] = byte(w.Value) + _, err := region.Access(true, w.Zone.Addr, value[:]) + return err +} + +type MEMIOWriteFileCmd struct { + Region Region `embed` + Filename string `arg name:"filename" help:"File to read data from."` + + Verify bool `optional name:"verify" help:"Read and verify written file."` +} + +func (w MEMIOWriteFileCmd) Run(c *Context) error { + data, err := ioutil.ReadFile(w.Filename) + if err != nil { + return err + } + + region := c.hal.MemoryRegionGet(mshal.MemoryRegionNameType(w.Region.Region)) + if region == nil { + return errors.New("Invalid memory region") + } + + n, err := region.Access(true, w.Region.Addr, data) + if n > 0 { + fmt.Printf("Wrote %d bytes to %s:%04x.\n", n, w.Region.Region, w.Region.Addr) + } + + if w.Verify { + readback := make([]byte, len(data)) + _, err := region.Access(false, w.Region.Addr, readback) + if err != nil { + return err + } + + if !bytes.Equal(readback, data) { + return errors.New("Failed to verify write") + } + + fmt.Println("Verification OK.") + } + + return err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c6e4950 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/BertoldVdb/ms-tools + +go 1.16 + +require ( + github.com/alecthomas/kong v0.2.17 + github.com/fatih/color v1.12.0 + github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 + github.com/sstallion/go-hid v0.0.0-20190621001400-1cf4630be9f4 + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..db8d299 --- /dev/null +++ b/go.sum @@ -0,0 +1,34 @@ +github.com/alecthomas/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0= +github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 h1:fO9A67/izFYFYky7l1pDP5Dr0BTCRkaQJUG6Jm5ehsk= +github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3/go.mod h1:Ey4uAp+LvIl+s5jRbOHLcZpUDnkjLBROl15fZLwPlTM= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sstallion/go-hid v0.0.0-20190621001400-1cf4630be9f4 h1:hczfXYN39SDj4FcN5J7sgHBtJm4U7ef2nvlonn6NvVU= +github.com/sstallion/go-hid v0.0.0-20190621001400-1cf4630be9f4/go.mod h1:JwBz6izP5UGcbYDU5VGeLkqpRIpSBDPtCB5/XnVXz9Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mshal/asm/asm.sh b/mshal/asm/asm.sh new file mode 100755 index 0000000..534c97c --- /dev/null +++ b/mshal/asm/asm.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +(echo ".equ HID, 0x13"; cat hook.asm) > hook_2109.asm +(echo ".equ HID, 0x14"; cat hook.asm) > hook_2106.asm +as31 -Fbin hook_2109.asm +as31 -Fbin hook_2106.asm +rm hook_2109.asm +rm hook_2106.asm +as31 -Fbin gpio.asm +as31 -Fbin code.asm +as31 -Fbin i2cRead2109.asm diff --git a/mshal/asm/code.asm b/mshal/asm/code.asm new file mode 100644 index 0000000..073b09b --- /dev/null +++ b/mshal/asm/code.asm @@ -0,0 +1,2 @@ +MOVC A, @A+DPTR +RET diff --git a/mshal/asm/code.bin b/mshal/asm/code.bin new file mode 100644 index 0000000..1379193 --- /dev/null +++ b/mshal/asm/code.bin @@ -0,0 +1 @@ +“" \ No newline at end of file diff --git a/mshal/asm/gpio.asm b/mshal/asm/gpio.asm new file mode 100644 index 0000000..372ba3c --- /dev/null +++ b/mshal/asm/gpio.asm @@ -0,0 +1,11 @@ +MOV A, P3 ;Direction (1=input, 0=output) +ORL A, R6 +ANL A, R7 +MOV P3, A +MOV A, P2 ;State (1=high, 0=low) +ORL A, R4 +ANL A, R5 +MOV P2, A +MOV R2, P2 +MOV R3, P3 +RET diff --git a/mshal/asm/gpio.bin b/mshal/asm/gpio.bin new file mode 100644 index 0000000..465b3d2 --- /dev/null +++ b/mshal/asm/gpio.bin @@ -0,0 +1 @@ +å°N_õ°å L]õ ª «°" \ No newline at end of file diff --git a/mshal/asm/hook.asm b/mshal/asm/hook.asm new file mode 100644 index 0000000..9373877 --- /dev/null +++ b/mshal/asm/hook.asm @@ -0,0 +1,38 @@ +hookRun: + MOV A, HID + XRL A, R0 + JNZ hookRet + + LCALL hookWork + + MOV HID+1, A + MOV HID+2, R2 + MOV HID+3, R3 + MOV HID+4, R4 + MOV HID+5, R5 + MOV HID+6, R6 + MOV HID+7, R7 + MOV A, #0xFF + RLC A + MOV HID, A + +hookRet: + RET + +hookWork: + MOV DPH, HID+3 + MOV DPL, HID+4 + MOV R3, HID+3 + MOV R4, HID+4 + MOV R5, HID+5 + MOV R6, HID+6 + MOV R7, HID+7 + MOV A, R7 + RRC A + MOV A, R7 + + PUSH HID+2 + PUSH HID+1 + RET ;Call address in HID+1 + + diff --git a/mshal/asm/hook_2106.bin b/mshal/asm/hook_2106.bin new file mode 100644 index 0000000000000000000000000000000000000000..75724b1b2e2797ca0666a3c2e8c0b61a91d791f7 GIT binary patch literal 52 zcmaDVl2ITg#31ukv`eg8yhox}vQMgCy5zs{R}rOF@#a>Erq$wWB-To+z?0PXP;sQ>@~ literal 0 HcmV?d00001 diff --git a/mshal/asm/hook_2109.bin b/mshal/asm/hook_2109.bin new file mode 100644 index 0000000000000000000000000000000000000000..9fe52d6165e87a6d8cd41902ab8a70c3e7c57f55 GIT binary patch literal 52 zcmaDVoKYYq#31ukq)W70tVg_8qEE74s^q`%S7D`AvF29srqyC=#Mer!lUy(LUike1 I(E}n%0PHUlmjD0& literal 0 HcmV?d00001 diff --git a/mshal/asm/i2cRead2109.asm b/mshal/asm/i2cRead2109.asm new file mode 100644 index 0000000..1fd43b3 --- /dev/null +++ b/mshal/asm/i2cRead2109.asm @@ -0,0 +1,3 @@ +MOV 0x21.0, C +LCALL 0x4cf3 +RET diff --git a/mshal/asm/i2cRead2109.bin b/mshal/asm/i2cRead2109.bin new file mode 100644 index 0000000..32aa4b6 --- /dev/null +++ b/mshal/asm/i2cRead2109.bin @@ -0,0 +1 @@ +’Ló" \ No newline at end of file diff --git a/mshal/errors.go b/mshal/errors.go new file mode 100644 index 0000000..78844cc --- /dev/null +++ b/mshal/errors.go @@ -0,0 +1,14 @@ +package mshal + +import "errors" + +var ( + ErrorUnknownDevice = errors.New("Unsupported device found") + ErrorInvalidResponse = errors.New("Received invalid response") + ErrorReadNotAllowed = errors.New("Memory can't be read") + ErrorWriteNotAllowed = errors.New("Memory can't be written") + ErrorTimeout = errors.New("The operation did not complete in time") + ErrorPatchFailed = errors.New("Could not patch code") + ErrorMissingFunction = errors.New("This function is not supported in this mode") + ErrorNoAck = errors.New("No ACK received") +) diff --git a/mshal/hal.go b/mshal/hal.go new file mode 100644 index 0000000..9b84d7b --- /dev/null +++ b/mshal/hal.go @@ -0,0 +1,123 @@ +package mshal + +import ( + "github.com/sstallion/go-hid" +) + +type HAL struct { + dev *hid.Device + + deviceType int + deviceTypeExtra int + eepromSize int + + patchAllocAddr int + patchCallAddrs []int + + patchInstalled bool + + config HALConfig +} + +type LogFunc func(level int, format string, param ...interface{}) + +type HALConfig struct { + EEPromSize int + + PatchTryInstall bool + PatchIgnoreUserFirmware bool + PatchProbeEEPROM bool + + LogFunc LogFunc +} + +func New(dev *hid.Device, config HALConfig) (*HAL, error) { + h := &HAL{ + dev: dev, + config: config, + } + + xdata := h.MemoryRegionGet(MemoryRegionRAM) + /* This is a value that is set by the ROM, so we can ID the chip from it */ + chipType, err := ReadByte(xdata, 0xF800) + if err != nil { + return nil, err + } + + switch chipType { + case 0x6a: + h.deviceType = 2106 + + chipType, err = ReadByte(xdata, 0x35) + h.deviceTypeExtra = int(chipType) + + case 0xa7: + h.deviceType = 2109 + default: + return nil, ErrorUnknownDevice + } + + if h.config.LogFunc != nil { + h.config.LogFunc(1, "Detected %s", h.GetDeviceType()) + } + + if config.PatchTryInstall { + isNew, err := h.patchInstall() + if err != nil { + return nil, err + } + + if h.config.LogFunc != nil { + if isNew { + h.config.LogFunc(1, "Patch installed") + } else { + h.config.LogFunc(1, "Patch already installed") + } + } + + h.patchInstalled = true + } + + if h.eepromSize == 0 && config.PatchProbeEEPROM { + h.eepromSize, err = h.patchEepromDetectSize() + if err != nil { + h.eepromSize = 2048 + h.config.LogFunc(1, "Failed to detect EEPROM: %v", err) + } + } + + if h.deviceType == 2106 && h.eepromSize > 2048 { + h.eepromSize = 2048 + } + + h.config.LogFunc(1, "Assumed EEPROM Size: %d", h.eepromSize) + + return h, nil +} + +type MemoryRegionNameType string + +const ( + MemoryRegionCODE MemoryRegionNameType = "CODE" + MemoryRegionRAM MemoryRegionNameType = "RAM" + MemoryRegionIRAM MemoryRegionNameType = "IRAM" + MemoryRegionEEPROM MemoryRegionNameType = "EEPROM" + MemoryRegionUserConfig MemoryRegionNameType = "USERCONFIG" + + MemoryRegionUserRAM MemoryRegionNameType = "USERRAM" + + MemoryRegionRegisters2106TVD MemoryRegionNameType = "TVDREGS" +) + +type HookNameType string + +func (h *HAL) GetDeviceType() string { + if h.deviceType == 2106 { + if h.deviceTypeExtra != 0 { + return "MS2106s" + } + return "MS2106" + } + + return "MS2109" +} diff --git a/mshal/hal_patch_call.go b/mshal/hal_patch_call.go new file mode 100644 index 0000000..51d0f80 --- /dev/null +++ b/mshal/hal_patch_call.go @@ -0,0 +1,103 @@ +package mshal + +import ( + "encoding/hex" + "errors" + "time" +) + +func (h *HAL) patchExchangeReport(out [9]byte) ([9]byte, error) { + var in [9]byte + + if h.config.LogFunc != nil { + h.config.LogFunc(3, "PatchOut: %s", hex.EncodeToString(out[:])) + } + + if _, err := h.dev.SendFeatureReport(out[:]); err != nil { + return in, err + } + + timeout := time.Now().Add(time.Second) + + for time.Now().Before(timeout) { + _, err := h.dev.GetFeatureReport(in[:]) + if err != nil { + return in, err + } + + if in[1]&0xFE == 0xFE { + if h.config.LogFunc != nil { + h.config.LogFunc(3, "PatchIn: %s", hex.EncodeToString(in[:])) + } + + return in, nil + } + } + + return in, ErrorTimeout +} + +type patchExecFuncResponse struct { + A byte + R2 byte + R3 byte + R4 byte + R5 byte + R6 byte + R7 byte + C bool +} + +type patchExecFuncRequest struct { + DPTR uint16 + R3 byte + R4 byte + R5 byte + R6 byte + R7_A byte +} + +func (h *HAL) patchExecFunc(inIRQ bool, addr int, req patchExecFuncRequest) (patchExecFuncResponse, error) { + var response patchExecFuncResponse + + if !h.patchInstalled { + return response, ErrorMissingFunction + } + + if req.DPTR != 0 && (req.R4 != 0 || req.R3 != 0) { + return response, errors.New("Can't set both DPTR and R3/R4") + } + + var out [9]byte + out[1] = 0xef + if inIRQ { + out[1] = 0xee + } + out[2] = byte(addr >> 8) + out[3] = byte(addr) + if req.DPTR != 0 { + out[4] = byte(req.DPTR >> 8) + out[5] = byte(req.DPTR) + } else { + out[4] = req.R3 + out[5] = req.R4 + } + out[6] = req.R5 + out[7] = req.R6 + out[8] = req.R7_A + + in, err := h.patchExchangeReport(out) + if err != nil { + return response, err + } + + response.A = in[2] + response.R2 = in[3] + response.R3 = in[4] + response.R4 = in[5] + response.R5 = in[6] + response.R6 = in[7] + response.R7 = in[8] + response.C = in[1]&1 > 0 + return response, nil +} diff --git a/mshal/hal_patch_eeprom.go b/mshal/hal_patch_eeprom.go new file mode 100644 index 0000000..36d24a0 --- /dev/null +++ b/mshal/hal_patch_eeprom.go @@ -0,0 +1,152 @@ +package mshal + +func (h *HAL) patchEepromDetectSize() (int, error) { + eepromFound := 0 + for i := byte(0x50); i <= 0x57; i++ { + ok, err := h.I2CTransfer(i, []byte{0}, nil) + if err != nil { + return 0, err + } + if !ok { + break + } + eepromFound++ + } + + if eepromFound == 0 { + return 0, nil + } + + if eepromFound > 1 { + return eepromFound * 256, nil + } + + if h.deviceType == 2109 { + /* If we find only one EEPROM we assume it is 16-bit addressable since <256 byte EEPROMs + make no sense for this application. To actually test it you need to write to the chip :( */ + return 4096, nil + } + + return 256, nil +} + +func (h *HAL) patchEEPROMUnlock(unlock bool) error { + if h.deviceType == 2109 { + return h.GPIOWrite(5, !unlock) + } + + return nil +} + +type halPatchEEPROMMemoryRegion struct { + hal *HAL +} + +func (h halPatchEEPROMMemoryRegion) GetName() MemoryRegionNameType { + return MemoryRegionEEPROM +} + +func (h halPatchEEPROMMemoryRegion) GetLength() int { + return h.hal.eepromSize +} + +func (h halPatchEEPROMMemoryRegion) GetParent() (MemoryRegion, int) { + return nil, 0 +} + +func (h halPatchEEPROMMemoryRegion) read(addr int, buf []byte) (int, error) { + var ok bool + var err error + + if h.GetLength() <= 2048 { + ok, err = h.hal.I2CTransfer(0x50+byte(addr>>8), []byte{byte(addr)}, buf) + } else { + ok, err = h.hal.I2CTransfer(0x50, []byte{byte(addr >> 8), byte(addr)}, buf) + } + + if err != nil { + return 0, err + } + if !ok { + return 0, ErrorNoAck + } + + return len(buf), nil +} + +func (h halPatchEEPROMMemoryRegion) write(addr int, buf []byte) (int, error) { + if err := h.hal.patchEEPROMUnlock(true); err != nil { + return 0, err + } + defer h.hal.patchEEPROMUnlock(false) + + /* We can write 16-byte pages */ + endOfPage := (addr + 16) / 16 * 16 + bytesRemaining := endOfPage - addr + + if len(buf) > bytesRemaining { + buf = buf[:bytesRemaining] + } + + var workBuf [16 + 2]byte + copy(workBuf[2:], buf) + workBuf[0] = byte(addr >> 8) + workBuf[1] = byte(addr) + wrBuf := workBuf[:len(buf)+2] + + var ok bool + var err error + + if h.GetLength() <= 2048 { + ok, err = h.hal.I2CTransfer(0x50+byte(addr>>8), wrBuf[1:], nil) + } else { + ok, err = h.hal.I2CTransfer(0x50, wrBuf, nil) + } + + if err != nil { + return 0, err + } + if !ok { + return 0, ErrorNoAck + } + + /* The EEPROM is working now, poll it to know when ACK received */ + for { + _, err := h.read(0, []byte{0}) + if err == nil { + break + } + if err != ErrorNoAck { + return 0, err + } + } + + return len(buf), nil +} + +func (h halPatchEEPROMMemoryRegion) Access(write bool, addr int, buf []byte) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + if addr > h.GetLength() { + return 0, nil + } + if addr+len(buf) > h.GetLength() { + buf = buf[:h.GetLength()-addr] + } + + if write { + return h.write(addr, buf) + } + + return h.read(addr, buf) +} + +func (h *HAL) patchMakeEEPROMRegion() MemoryRegion { + if !h.patchInstalled { + return nil + } + + return regionWrapCompleteIO(halPatchEEPROMMemoryRegion{hal: h}) +} diff --git a/mshal/hal_patch_gpio.go b/mshal/hal_patch_gpio.go new file mode 100644 index 0000000..5dd7e93 --- /dev/null +++ b/mshal/hal_patch_gpio.go @@ -0,0 +1,38 @@ +package mshal + +func (h *HAL) GPIOUpdate(stateSet byte, stateClear byte, outputSet byte, outputClear byte) (byte, byte, error) { + if !h.patchInstalled { + return 0, 0, ErrorMissingFunction + } + + var req patchExecFuncRequest + req.R4 = stateSet + req.R5 = ^stateClear + req.R6 = outputClear + req.R7_A = ^outputSet + resp, err := h.patchExecFunc(true, h.patchCallAddrs[1], req) + + return resp.R2, ^resp.R3, err +} + +func (h *HAL) GPIOWrite(index int, value bool) error { + if value { + return h.GPIOSet(index) + } + return h.GPIOClear(index) +} + +func (h *HAL) GPIOSet(index int) error { + _, _, err := h.GPIOUpdate(1< 0, err +} diff --git a/mshal/hal_patch_i2c.go b/mshal/hal_patch_i2c.go new file mode 100644 index 0000000..12763df --- /dev/null +++ b/mshal/hal_patch_i2c.go @@ -0,0 +1,95 @@ +package mshal + +func (h *HAL) patchI2CStart() error { + addr := 0x3639 + if h.deviceType == 2109 { + addr = 0x6a8c + } + + _, err := h.patchExecFunc(true, addr, patchExecFuncRequest{}) + return err +} + +func (h *HAL) patchI2CStop() error { + addr := 0x3730 + if h.deviceType == 2109 { + addr = 0x6aba + } + _, err := h.patchExecFunc(true, addr, patchExecFuncRequest{}) + return err +} + +func (h *HAL) patchI2CRead(ack bool) (uint8, error) { + addr := 0x26cb + if h.deviceType == 2109 { + addr = h.patchCallAddrs[3] + } + r7 := byte(1) + if ack { + r7 = 0 + } + resp, err := h.patchExecFunc(true, addr, patchExecFuncRequest{R7_A: r7}) + return resp.R7, err +} + +func (h *HAL) patchI2CWrite(value uint8) (bool, error) { + addr := 0x2126 + if h.deviceType == 2109 { + addr = 0x4648 + } + resp, err := h.patchExecFunc(true, addr, patchExecFuncRequest{R7_A: value}) + if h.deviceType == 2109 { + return resp.C, err + } + return resp.R7 > 0, err +} + +func (h *HAL) I2CTransfer(addr uint8, wrBuf []byte, rdBuf []byte) (bool, error) { + if !h.patchInstalled { + return false, ErrorMissingFunction + } + + if len(rdBuf) == 0 && len(wrBuf) == 0 { + return true, nil + + } + if len(wrBuf) > 0 { + if err := h.patchI2CStart(); err != nil { + return false, err + } + + if ack, err := h.patchI2CWrite(addr << 1); !ack || err != nil { + return false, err + } + + for _, m := range wrBuf { + if ack, err := h.patchI2CWrite(m); !ack || err != nil { + return false, err + } + } + } + + if len(rdBuf) > 0 { + if err := h.patchI2CStart(); err != nil { + return false, err + } + + if ack, err := h.patchI2CWrite(addr<<1 | 1); !ack || err != nil { + return false, err + } + + for i := range rdBuf { + value, err := h.patchI2CRead(i < len(rdBuf)-1) + if err != nil { + return false, err + } + rdBuf[i] = value + } + } + + if err := h.patchI2CStop(); err != nil { + return false, err + } + + return true, nil +} diff --git a/mshal/hal_patch_install.go b/mshal/hal_patch_install.go new file mode 100644 index 0000000..73bcdff --- /dev/null +++ b/mshal/hal_patch_install.go @@ -0,0 +1,434 @@ +package mshal + +import ( + "bytes" + _ "embed" + "encoding/binary" + "encoding/hex" + "hash/crc32" +) + +func (h *HAL) patchAlloc(len int) int { + addr := h.patchAllocAddr + h.patchAllocAddr += len + if h.config.LogFunc != nil { + h.config.LogFunc(2, "Allocated %d bytes for patch at %04x", len, addr) + } + return addr +} + +func (h *HAL) patchWriteWithTempFirstByte(region MemoryRegion, addr int, data []byte, firstByte byte) error { + if len(data) == 0 { + return nil + } + + if h.config.LogFunc != nil { + h.config.LogFunc(2, "Safe writing blob at %04x: %s", addr, hex.EncodeToString(data)) + } + + if err := WriteByte(region, addr, firstByte); err != nil { + return err + } + + if _, err := region.Access(true, addr+1, data[1:]); err != nil { + return err + } + + return WriteByte(region, addr, data[0]) +} + +func (h *HAL) patchWriteWithRET(region MemoryRegion, addr int, data []byte) error { + return h.patchWriteWithTempFirstByte(region, addr, data, 0x22) +} + +func patchTrampolineEncode(orig []byte, origAddr int, R0Value byte, hookAddr int) []byte { + // ...orig... + // LCALL origAddr + // MOV R0, #R0Value + // LJMP hookAddr + + trampolineOrig := []byte{ + 0x12, byte(origAddr >> 8), byte(origAddr), + } + + trampolineHook := []byte{ + 0x78, R0Value, + 0x02, byte(hookAddr >> 8), byte(hookAddr), + } + + result := orig + if origAddr != 0 { + result = append(result, trampolineOrig...) + } + result = append(result, trampolineHook...) + + return result +} + +func (h *HAL) patchTrampolineInstall(ram MemoryRegion, replaceCode bool, addr int, R0value byte, hookAddr int) error { + var trampoline []byte + if replaceCode { + replaceLen := 0 + var in [14]byte + + _, err := ram.Access(false, addr, in[:]) + if err != nil { + return err + } + + /* Can we patch this code? */ + if in[0] == 0x2 || in[0] == 0x12 || in[0] == 0x90 { + replaceLen = 3 + } else if in[0] == 0xe5 && in[1] == 0x33 && in[2] == 0x30 { + replaceLen = 14 + } else { + return ErrorPatchFailed + } + + trampoline = patchTrampolineEncode(in[:replaceLen], addr+replaceLen, R0value, hookAddr) + } else { + trampoline = patchTrampolineEncode(nil, 0, R0value, hookAddr) + } + + trampolineAddr := h.patchAlloc(len(trampoline)) + + if h.config.LogFunc != nil { + h.config.LogFunc(2, "Writing trampoline at %04x: %s", trampolineAddr, hex.EncodeToString(trampoline)) + } + + if _, err := ram.Access(true, trampolineAddr, trampoline); err != nil { + return err + } + + return h.patchWriteWithRET(ram, addr, []byte{0x02, byte(trampolineAddr >> 8), byte(trampolineAddr)}) +} + +type blob struct { + data []byte + reloc func(dataCopy []byte, addr int) (int, []byte) +} + +//go:embed asm/hook_2106.bin +var codeCallgate2106 []byte + +//go:embed asm/hook_2109.bin +var codeCallgate2109 []byte + +func relocateCallgate(result []byte, addr int) (int, []byte) { + if result[5] != 0x12 { + panic("Offset 5 is not LCALL") + } + + callAddr := binary.BigEndian.Uint16(result[6:]) + callAddr += uint16(addr) + binary.BigEndian.PutUint16(result[6:], callAddr) + + return addr, result +} + +//go:embed asm/gpio.bin +var codeGpio []byte + +//go:embed asm/code.bin +var codeMOVC []byte + +//go:embed asm/i2cRead2109.bin +var codei2cRead []byte + +var installBlobs2106 = []blob{ + { + data: codeCallgate2106, + reloc: relocateCallgate, + }, { + data: codeGpio, + }, { + data: codeMOVC, + }} + +var installBlobs2109 = []blob{ + { + data: codeCallgate2109, + reloc: relocateCallgate, + }, { + data: codeGpio, + }, { + data: codeMOVC, + }, { + data: codei2cRead, + }, +} + +func (h *HAL) EEPROMReloadUser() error { + if h.config.LogFunc != nil { + h.config.LogFunc(1, "Reloading EEPROM code") + } + + ram := h.MemoryRegionGet(MemoryRegionRAM) + userConfig := h.MemoryRegionGet(MemoryRegionUserConfig) + + addr, _, err := h.patchHookGet(userConfig, true) + if err != nil { + return err + } + + /* Write RET and enable callback */ + if err := h.patchHookConfigure(userConfig, true, false); err != nil { + return err + } + + loadEEPROM := []byte{0x02, 0x12, 0x82} + if h.deviceType == 2109 { + loadEEPROM = []byte{0x02, 0x5f, 0x19} + } + + /* Reload EEPROM from IRQ context */ + if err := h.patchWriteWithRET(ram, addr, loadEEPROM); err != nil { + return err + } + + return h.patchHookConfigure(userConfig, true, true) +} + +func (h *HAL) EEPROMIgnoreUser() error { + if h.config.LogFunc != nil { + h.config.LogFunc(1, "Unloading EEPROM code") + } + + userConfig := h.MemoryRegionGet(MemoryRegionUserConfig) + + ff := bytes.Repeat([]byte{0xFF}, userConfig.GetLength()) + ff[4] = 0 + ff[5] = 0 + + _, err := userConfig.Access(true, 0, ff) + return err +} + +func (h *HAL) EEPROMIsLoaded() (bool, int, error) { + userconfig := h.MemoryRegionGet(MemoryRegionUserConfig) + + var hdr [4]byte + if _, err := userconfig.Access(false, 0, hdr[:]); err != nil { + return false, 0, err + } + + eepromLen := int(binary.BigEndian.Uint16(hdr[2:])) + + if h.deviceType == 2106 { + return hdr[0] == 0x5a && hdr[1] == 0xa5, eepromLen, nil + } + + if hdr[0] == 0xa5 && hdr[1] == 0x5a { + return true, eepromLen, nil + } + + /* 2109 can also use 16bit eeproms and they have a different header */ + return hdr[0] == 0x96 && hdr[1] == 0x69, eepromLen, nil +} + +func (h *HAL) patchHookGet(loc MemoryRegion, inIRQ bool) (int, bool, error) { + if h.deviceType == 2106 { + if inIRQ { + value, err := ReadByte(loc, 0x9) + return 0xc4a0, value == 0x96, err + } else { + value, err := ReadByte(loc, 0x5) + return 0xc420, value == 0x5a, err + } + } + + value, err := ReadByte(loc, 0x4) + if err != nil { + return 0, false, err + } + + if inIRQ { + return 0xcc20, value&4 > 0, nil + } + + return 0xcc00, value&1 > 0, nil +} +func (h *HAL) patchHookConfigure(loc MemoryRegion, inIRQ bool, enable bool) error { + if h.config.LogFunc != nil { + h.config.LogFunc(2, "Configuring userhook: inIRQ=%v, enable=%v", inIRQ, enable) + } + + if h.deviceType == 2106 { + value := byte(0) + if inIRQ { + if enable { + value = 0x96 + } + return WriteByte(loc, 0x9, value) + } else { + if enable { + value = 0x5a + } + return WriteByte(loc, 0x5, value) + } + } + + value, err := ReadByte(loc, 0x4) + if err != nil { + return err + } + + if inIRQ { + value &= ^byte(4) + if enable { + value |= 4 + } + } else { + value &= ^byte(1) + if enable { + value |= 1 + } + } + + return WriteByte(loc, 0x4, value) +} + +func (h *HAL) patchInitAlloc(userConfig MemoryRegion) (bool, error) { + userCodePresent, userCodeLen, err := h.EEPROMIsLoaded() + if err != nil { + return userCodePresent, err + } + if !userCodePresent { + userCodeLen = 256 + } + _, userOffset := RecursiveGetParentAddress(userConfig, userConfig.GetLength()) + + h.patchAllocAddr = userOffset + userCodeLen + return userCodePresent, nil +} + +func (h *HAL) patchInstall() (bool, error) { + installBlobs := installBlobs2106 + if h.deviceType == 2109 { + installBlobs = installBlobs2109 + } + + ram := h.MemoryRegionGet(MemoryRegionRAM) + userConfig := h.MemoryRegionGet(MemoryRegionUserConfig) + + h.patchCallAddrs = make([]int, len(installBlobs)) + + /* Calculate checksum of blobs */ + crc := crc32.New(crc32.IEEETable) + for _, m := range installBlobs { + crc.Write(m.data) + } + sum := crc.Sum(nil) + + if h.config.PatchIgnoreUserFirmware { + sum[0] = ^sum[0] + } + + if _, err := h.patchInitAlloc(userConfig); err != nil { + return false, err + } + + /* Read current stored patch info */ + sumBlock := make([]byte, len(sum)+2*len(installBlobs)) + sumBlockAddr := h.patchAlloc(len(sumBlock)) + + if _, err := ram.Access(false, sumBlockAddr, sumBlock); err != nil { + return false, err + } + + /* Is this chip already patched? */ + if bytes.Equal(sumBlock[:4], sum) { + sumBlock = sumBlock[4:] + for i := range installBlobs { + h.patchCallAddrs[i] = int(binary.BigEndian.Uint16(sumBlock)) + sumBlock = sumBlock[2:] + } + return false, nil + } + + if !h.config.PatchIgnoreUserFirmware { + /* Reload eeprom to unpatch */ + if err := h.EEPROMReloadUser(); err != nil { + return true, err + } + } else { + /* Remove EEPROM data */ + if err := h.EEPROMIgnoreUser(); err != nil { + return false, err + } + } + + userCodePresent, err := h.patchInitAlloc(userConfig) + if err != nil { + return false, err + } + + sumBlock = make([]byte, len(sum)+2*len(installBlobs)) + sumBlockAddr = h.patchAlloc(len(sumBlock)) + copy(sumBlock, sum) + + /* Install all blobs */ + for i, m := range installBlobs { + data := m.data + + loadAddr := h.patchAlloc(len(data)) + callAddr := loadAddr + + if m.reloc != nil { + dataCopy := make([]byte, len(data)) + copy(dataCopy, data) + callAddr, data = m.reloc(dataCopy, loadAddr) + } + + if h.config.LogFunc != nil { + h.config.LogFunc(2, "Writing blob at %04x: %s", loadAddr, hex.EncodeToString(data)) + } + + _, err := ram.Access(true, loadAddr, data) + if err != nil { + return true, err + } + + h.patchCallAddrs[i] = callAddr + } + + /* Check current state */ + addrIrq, enableIrq, err := h.patchHookGet(userConfig, true) + if err != nil { + return true, err + } + addrNorm, enableNorm, err := h.patchHookGet(userConfig, false) + if err != nil { + return true, err + } + + if userCodePresent && (!enableIrq || !enableNorm) { + return true, ErrorPatchFailed + } + + /* Install trampolines to callgate */ + if err := h.patchTrampolineInstall(ram, userCodePresent, addrIrq, 0xee, h.patchCallAddrs[0]); err != nil { + return true, err + } + + if err := h.patchTrampolineInstall(ram, userCodePresent, addrNorm, 0xef, h.patchCallAddrs[0]); err != nil { + return true, err + } + + /* Enable callbacks */ + if !userCodePresent { + if err := h.patchHookConfigure(userConfig, true, true); err != nil { + return true, err + } + if err := h.patchHookConfigure(userConfig, false, true); err != nil { + return true, err + } + } + + /* Write patch sumblock */ + for i := range installBlobs { + binary.BigEndian.PutUint16(sumBlock[4+(2*i):], uint16(h.patchCallAddrs[i])) + } + + return true, h.patchWriteWithTempFirstByte(ram, sumBlockAddr, sumBlock, sumBlock[0]-1) +} diff --git a/mshal/hal_patch_movc.go b/mshal/hal_patch_movc.go new file mode 100644 index 0000000..5310f04 --- /dev/null +++ b/mshal/hal_patch_movc.go @@ -0,0 +1,51 @@ +package mshal + +func (h *HAL) patchReadCode(addr int) (byte, error) { + resp, err := h.patchExecFunc(true, h.patchCallAddrs[2], patchExecFuncRequest{DPTR: uint16(addr)}) + if err != nil { + return 0, err + } + return resp.A, nil +} + +type halPatchCodeMemoryRegion struct { + hal *HAL +} + +func (h halPatchCodeMemoryRegion) GetName() MemoryRegionNameType { + return MemoryRegionCODE +} + +func (h halPatchCodeMemoryRegion) GetLength() int { + return 0x10000 +} + +func (h halPatchCodeMemoryRegion) GetParent() (MemoryRegion, int) { + return nil, 0 +} + +func (h halPatchCodeMemoryRegion) Access(write bool, addr int, buf []byte) (int, error) { + if write { + return 0, ErrorWriteNotAllowed + } + + if len(buf) == 0 { + return 0, nil + } + + value, err := h.hal.patchReadCode(addr) + if err != nil { + return 0, err + } + + buf[0] = value + return 1, nil +} + +func (h *HAL) patchMakeCodeRegion() MemoryRegion { + if !h.patchInstalled { + return nil + } + + return regionWrapCompleteIO(halPatchCodeMemoryRegion{hal: h}) +} diff --git a/mshal/hal_region.go b/mshal/hal_region.go new file mode 100644 index 0000000..fb76860 --- /dev/null +++ b/mshal/hal_region.go @@ -0,0 +1,98 @@ +package mshal + +import "strings" + +func (h *HAL) memoryRegionXDATAIRAM(base int, len int) MemoryRegion { + read, write := romCommandMakeReadWrite(0xb5, true) + return h.romMemoryRegionMake(MemoryRegionRAM, base, len, &read, &write) +} + +func (h *HAL) memoryRegionEEPROM() MemoryRegion { + read, write := romCommandMakeReadWrite(0xe5, true) + if h.deviceType == 2109 { + read.maxPayload = 5 + read.cbApplyParam = romEepromV2HandleTwoByteAddress + } + region := h.romMemoryRegionMake(MemoryRegionEEPROM, 0, h.eepromSize, &read, &write) + + /* The EEPROM takes time to write, so try to read again. + MS2109 has internal delay (quite long) */ + if h.deviceType != 2109 { + write.cbPostExchange = romEepromVerify(region) + } + + return region +} + +func (h *HAL) memoryRegionRegisters2106TVD() MemoryRegion { + read, write := romCommandMakeReadWrite(0xa5, false) + return h.romMemoryRegionMake(MemoryRegionRegisters2106TVD, 0, 256, &read, &write) +} + +func (h *HAL) MemoryRegionList() []MemoryRegionNameType { + list := []MemoryRegionNameType{ + MemoryRegionRAM, + MemoryRegionIRAM, + MemoryRegionEEPROM, + MemoryRegionUserRAM, + MemoryRegionUserConfig, + } + + if h.patchInstalled { + list = append(list, MemoryRegionCODE) + } + + if h.deviceType == 2106 { + list = append(list, MemoryRegionRegisters2106TVD) + } + + return list +} + +func (h *HAL) MemoryRegionGet(name MemoryRegionNameType) MemoryRegion { + t := MemoryRegionNameType(strings.ToUpper(string(name))) + + switch t { + case MemoryRegionRAM: + return h.memoryRegionXDATAIRAM(0, 0x10000) + case MemoryRegionIRAM: + return regionWrapPartial(MemoryRegionIRAM, h.MemoryRegionGet(MemoryRegionRAM), 0, 0x100) + case MemoryRegionEEPROM: + if h.patchInstalled { + if h.config.LogFunc != nil { + h.config.LogFunc(1, "Using patched EEPROM access") + } + + return h.patchMakeEEPROMRegion() + } + + return h.memoryRegionEEPROM() + case MemoryRegionCODE: + return h.patchMakeCodeRegion() + } + + if h.deviceType == 2106 { + switch t { + case MemoryRegionUserRAM: + return regionWrapPartial(MemoryRegionUserRAM, h.MemoryRegionGet(MemoryRegionRAM), 0xC000, 0x1000) + + case MemoryRegionUserConfig: + return regionWrapPartial(MemoryRegionUserConfig, h.MemoryRegionGet(MemoryRegionUserRAM), 0x3F0, 0x10) + + case MemoryRegionRegisters2106TVD: + return h.memoryRegionRegisters2106TVD() + } + } + + if h.deviceType == 2109 { + switch t { + case MemoryRegionUserRAM: + return regionWrapPartial(MemoryRegionUserRAM, h.MemoryRegionGet(MemoryRegionRAM), 0xC000, 0x2000) + + case MemoryRegionUserConfig: + return regionWrapPartial(MemoryRegionUserConfig, h.MemoryRegionGet(MemoryRegionUserRAM), 0xBD0, 0x30) + } + } + + return nil +} diff --git a/mshal/hal_rom.go b/mshal/hal_rom.go new file mode 100644 index 0000000..8fadf91 --- /dev/null +++ b/mshal/hal_rom.go @@ -0,0 +1,213 @@ +package mshal + +import ( + "bytes" + "encoding/hex" + "time" +) + +type cbApplyParamType func(h *HAL, out []byte) error +type cbPostExchangeType func(h *HAL, addr int, buf []byte) error + +type romCommand struct { + id byte + + is16bit bool + isWrite bool + maxPayload int + + cbApplyParam cbApplyParamType + cbPostExchange cbPostExchangeType +} + +func romCommandMake(id byte, is16bit bool, isWrite bool) romCommand { + return romCommand{ + id: id, + is16bit: is16bit, + isWrite: isWrite, + } +} + +func romCommandMakeReadWrite(id byte, is16bit bool) (romCommand, romCommand) { + return romCommandMake(id, is16bit, false), romCommandMake(id+1, is16bit, true) +} + +func (h *HAL) romExchangeReport(out [9]byte, checkLen int) ([9]byte, error) { + var in [9]byte + + if h.config.LogFunc != nil { + h.config.LogFunc(3, "ROMOut: %s", hex.EncodeToString(out[:])) + } + + if _, err := h.dev.SendFeatureReport(out[:]); err != nil { + return in, err + } + + _, err := h.dev.GetFeatureReport(in[:]) + if err != nil { + return in, err + } + + if h.config.LogFunc != nil { + h.config.LogFunc(3, "ROMIn: %s", hex.EncodeToString(in[:])) + } + + if !bytes.Equal(out[:checkLen], in[:checkLen]) { + return in, ErrorInvalidResponse + } + return in, nil +} + +func (h *HAL) romProtocolMakeHeader(cmd byte, is16bit bool, addr int) ([9]byte, int) { + var out [9]byte + + out[0] = 0 + out[1] = byte(cmd) + + index := 2 + if is16bit { + out[index] = byte(addr >> 8) + index++ + } + out[index] = byte(addr) + index++ + + return out, index +} + +func (h *HAL) romProtocolReadReply(buf []byte, in []byte, index int, maxReply int) int { + if len(buf) > maxReply { + buf = buf[:maxReply] + } + return copy(buf, in[index:]) + +} + +func (h *HAL) romProtocolWritePayload(buf []byte, out []byte, index int, maxLen int) int { + if len(buf) > maxLen { + buf = buf[:maxLen] + } + return copy(out[index:], buf) +} + +func (h *HAL) romProtocolExec(cmd romCommand, addr int, buf []byte) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + maxPayload := cmd.maxPayload + if maxPayload <= 0 { + maxPayload = 1 + } + + out, index := h.romProtocolMakeHeader(cmd.id, cmd.is16bit, addr) + + txrLen := 0 + if cmd.isWrite { + txrLen = h.romProtocolWritePayload(buf, out[:], index, maxPayload) + } + + if cmd.cbApplyParam != nil { + if err := cmd.cbApplyParam(h, out[:]); err != nil { + return 0, err + } + } + + in, err := h.romExchangeReport(out, index) + if err != nil { + return 0, err + } + + if !cmd.isWrite { + txrLen = h.romProtocolReadReply(buf, in[:], index, maxPayload) + } + + if cmd.cbPostExchange != nil { + if err := cmd.cbPostExchange(h, addr, buf); err != nil { + return 0, err + } + } + + return txrLen, nil +} + +type halROMMemoryRegion struct { + hal *HAL + readCommand *romCommand + writeCommand *romCommand + baseAddr int + length int + name MemoryRegionNameType +} + +func (h halROMMemoryRegion) GetName() MemoryRegionNameType { + return h.name +} + +func (h halROMMemoryRegion) GetLength() int { + return h.length +} + +func (h halROMMemoryRegion) GetParent() (MemoryRegion, int) { + return nil, 0 +} + +func (h halROMMemoryRegion) Access(write bool, addr int, buf []byte) (int, error) { + if addr > h.length { + return 0, nil + } + if addr+len(buf) > h.length { + buf = buf[:h.length-addr] + } + + if write { + if h.writeCommand == nil { + return 0, ErrorWriteNotAllowed + } + return h.hal.romProtocolExec(*h.writeCommand, h.baseAddr+addr, buf) + } + + if h.writeCommand == nil { + return 0, ErrorWriteNotAllowed + } + return h.hal.romProtocolExec(*h.readCommand, h.baseAddr+addr, buf) +} + +func (h *HAL) romMemoryRegionMake(name MemoryRegionNameType, baseAddr int, length int, read *romCommand, write *romCommand) MemoryRegion { + return regionWrapCompleteIO(halROMMemoryRegion{ + hal: h, + baseAddr: baseAddr, + length: length, + readCommand: read, + writeCommand: write, + name: name, + }) +} + +func romEepromV2HandleTwoByteAddress(h *HAL, out []byte) error { + if h.eepromSize > 2048 { + out[8] = 1 + } + return nil +} + +func romEepromVerify(region MemoryRegion) cbPostExchangeType { + return func(h *HAL, addr int, buf []byte) error { + if buf[0] == 0 { + /* The chip returns 0 if there is no I2C response, so we just have to wait */ + time.Sleep(15 * time.Millisecond) + return nil + } + + var tmp [1]byte + for i := 0; i < 25; i++ { + if _, err := region.Access(false, addr, tmp[:]); err != nil { + return err + } + if tmp[0] == buf[0] { + return nil + } + } + return ErrorTimeout + } +} diff --git a/mshal/region.go b/mshal/region.go new file mode 100644 index 0000000..409cc59 --- /dev/null +++ b/mshal/region.go @@ -0,0 +1,97 @@ +package mshal + +type MemoryRegion interface { + GetLength() int + Access(write bool, addr int, buf []byte) (int, error) + GetParent() (MemoryRegion, int) + GetName() MemoryRegionNameType +} + +type regionCompleteIO struct { + MemoryRegion +} + +func regionWrapCompleteIO(parent MemoryRegion) MemoryRegion { + return regionCompleteIO{ + MemoryRegion: parent, + } +} + +func (m regionCompleteIO) Access(write bool, addr int, buf []byte) (int, error) { + total := 0 + for len(buf) > 0 { + n, err := m.MemoryRegion.Access(write, addr+total, buf) + total += n + buf = buf[n:] + + if err != nil || n == 0 { + return total, err + } + } + + return total, nil +} + +func WriteByte(m MemoryRegion, addr int, value byte) error { + _, err := m.Access(true, addr, []byte{value}) + return err +} + +func ReadByte(m MemoryRegion, addr int) (byte, error) { + var buf [1]byte + _, err := m.Access(false, addr, buf[:]) + return buf[0], err +} + +type regionPartial struct { + parent MemoryRegion + offset int + length int + name MemoryRegionNameType +} + +func regionWrapPartial(name MemoryRegionNameType, parent MemoryRegion, offset int, length int) MemoryRegion { + return regionPartial{ + parent: parent, + offset: offset, + length: length, + name: name, + } +} + +func (h regionPartial) GetName() MemoryRegionNameType { + return h.name +} + +func (h regionPartial) GetLength() int { + return h.length +} + +func (h regionPartial) GetParent() (MemoryRegion, int) { + return h.parent, h.offset +} + +func (h regionPartial) Access(write bool, addr int, buf []byte) (int, error) { + if len(buf)+addr > h.length { + if addr > h.length { + return 0, nil + } + buf = buf[:h.length-addr] + } + + return h.parent.Access(write, h.offset+addr, buf) +} + +func RecursiveGetParentAddress(region MemoryRegion, offset int) (MemoryRegion, int) { + for { + var parentOffset int + prevRegion := region + region, parentOffset = region.GetParent() + + offset += parentOffset + + if region == nil { + return prevRegion, offset + } + } +}