Initial commit of code

This commit is contained in:
Bertold Van den Bergh 2021-07-25 23:09:00 +02:00
parent 268e2ecee0
commit 680e4f77ab
37 changed files with 2311 additions and 0 deletions

1
cli/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
cli

5
cli/asm/asm.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
as31 -Fbin dumprom.asm
#as31 -Fbin dumprom.asm

52
cli/asm/dumprom.asm Normal file
View file

@ -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

BIN
cli/asm/dumprom.bin Normal file

Binary file not shown.

1
cli/cli.go Normal file
View file

@ -0,0 +1 @@
package main

174
cli/dumprom.go Normal file
View file

@ -0,0 +1,174 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"strings"
"time"
_ "embed"
"github.com/BertoldVdb/ms-tools/mshal"
)
type DumpROM struct {
Filename string `arg help:"File to write dump to."`
}
type dumpCodeParams struct {
addrMailbox int
addrTemp int
addrTempLen int
addrLoad int
addrHook int
valueHook byte
}
func (d *DumpROM) Run(c *Context) error {
var p dumpCodeParams
devType := c.hal.GetDeviceType()
if strings.Contains(devType, "MS2106") {
p.addrMailbox = 0xCF10
p.addrTemp = 0xCD00
p.addrTempLen = 256
p.addrLoad = 0xC4A0
p.addrHook = 9
p.valueHook = 0x96
} else if strings.Contains(devType, "MS2109") {
p.addrMailbox = 0xCBF0
p.addrTemp = 0xD300
p.addrTempLen = 256
p.addrLoad = 0xCC20
p.addrHook = 4
p.valueHook = 1 << 2
} else {
return mshal.ErrorUnknownDevice
}
code, err := d.work(c.hal, p)
if err != nil {
return err
}
return ioutil.WriteFile(d.Filename, code, 0644)
}
//go:embed asm/dumprom.bin
var dumpBlobBase []byte
func (d *DumpROM) work(ms *mshal.HAL, p dumpCodeParams) ([]byte, error) {
dumpBlob := bytes.Replace(dumpBlobBase, []byte{0xDE, 0xAD}, []byte{byte(p.addrMailbox >> 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
}

48
cli/gpio.go Normal file
View file

@ -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
}

68
cli/hexdump.go Normal file
View file

@ -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
}

41
cli/hid.go Normal file
View file

@ -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
}

62
cli/i2c.go Normal file
View file

@ -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
}

31
cli/listhid.go Normal file
View file

@ -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
})
}

98
cli/main.go Normal file
View file

@ -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)
}

26
cli/mappers.go Normal file
View file

@ -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
}

173
cli/memio.go Normal file
View file

@ -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
}

11
go.mod Normal file
View file

@ -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
)

34
go.sum Normal file
View file

@ -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=

11
mshal/asm/asm.sh Executable file
View file

@ -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

2
mshal/asm/code.asm Normal file
View file

@ -0,0 +1,2 @@
MOVC A, @A+DPTR
RET

1
mshal/asm/code.bin Normal file
View file

@ -0,0 +1 @@
<EFBFBD>"

11
mshal/asm/gpio.asm Normal file
View file

@ -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

1
mshal/asm/gpio.bin Normal file
View file

@ -0,0 +1 @@
å°N_õ°å L]õ ª «°"

38
mshal/asm/hook.asm Normal file
View file

@ -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

BIN
mshal/asm/hook_2106.bin Normal file

Binary file not shown.

BIN
mshal/asm/hook_2109.bin Normal file

Binary file not shown.

View file

@ -0,0 +1,3 @@
MOV 0x21.0, C
LCALL 0x4cf3
RET

View file

@ -0,0 +1 @@
<EFBFBD>L<>"

14
mshal/errors.go Normal file
View file

@ -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")
)

123
mshal/hal.go Normal file
View file

@ -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"
}

103
mshal/hal_patch_call.go Normal file
View file

@ -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
}

152
mshal/hal_patch_eeprom.go Normal file
View file

@ -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})
}

38
mshal/hal_patch_gpio.go Normal file
View file

@ -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<<index, 0, 1<<index, 0)
return err
}
func (h *HAL) GPIOClear(index int) error {
_, _, err := h.GPIOUpdate(0, 1<<index, 1<<index, 0)
return err
}
func (h *HAL) GPIORead(index int) (bool, error) {
p2, _, err := h.GPIOUpdate(0, 0, 0, 1<<index)
return p2&(1<<index) > 0, err
}

95
mshal/hal_patch_i2c.go Normal file
View file

@ -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
}

434
mshal/hal_patch_install.go Normal file
View file

@ -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)
}

51
mshal/hal_patch_movc.go Normal file
View file

@ -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})
}

98
mshal/hal_region.go Normal file
View file

@ -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
}

213
mshal/hal_rom.go Normal file
View file

@ -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
}
}

97
mshal/region.go Normal file
View file

@ -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
}
}
}