mirror of
https://github.com/BertoldVdb/ms-tools.git
synced 2026-01-28 01:07:20 +01:00
Initial commit of code
This commit is contained in:
parent
268e2ecee0
commit
680e4f77ab
37 changed files with 2311 additions and 0 deletions
1
cli/.gitignore
vendored
Normal file
1
cli/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
cli
|
||||
5
cli/asm/asm.sh
Executable file
5
cli/asm/asm.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
as31 -Fbin dumprom.asm
|
||||
#as31 -Fbin dumprom.asm
|
||||
|
||||
52
cli/asm/dumprom.asm
Normal file
52
cli/asm/dumprom.asm
Normal 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
BIN
cli/asm/dumprom.bin
Normal file
Binary file not shown.
1
cli/cli.go
Normal file
1
cli/cli.go
Normal file
|
|
@ -0,0 +1 @@
|
|||
package main
|
||||
174
cli/dumprom.go
Normal file
174
cli/dumprom.go
Normal 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
48
cli/gpio.go
Normal 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
68
cli/hexdump.go
Normal 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
41
cli/hid.go
Normal 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
62
cli/i2c.go
Normal 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
31
cli/listhid.go
Normal 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
98
cli/main.go
Normal 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
26
cli/mappers.go
Normal 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
173
cli/memio.go
Normal 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
11
go.mod
Normal 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
34
go.sum
Normal 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
11
mshal/asm/asm.sh
Executable 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
2
mshal/asm/code.asm
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
MOVC A, @A+DPTR
|
||||
RET
|
||||
1
mshal/asm/code.bin
Normal file
1
mshal/asm/code.bin
Normal file
|
|
@ -0,0 +1 @@
|
|||
<EFBFBD>"
|
||||
11
mshal/asm/gpio.asm
Normal file
11
mshal/asm/gpio.asm
Normal 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
1
mshal/asm/gpio.bin
Normal file
|
|
@ -0,0 +1 @@
|
|||
å°N_õ°å L]õ ª «°"
|
||||
38
mshal/asm/hook.asm
Normal file
38
mshal/asm/hook.asm
Normal 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
BIN
mshal/asm/hook_2106.bin
Normal file
Binary file not shown.
BIN
mshal/asm/hook_2109.bin
Normal file
BIN
mshal/asm/hook_2109.bin
Normal file
Binary file not shown.
3
mshal/asm/i2cRead2109.asm
Normal file
3
mshal/asm/i2cRead2109.asm
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
MOV 0x21.0, C
|
||||
LCALL 0x4cf3
|
||||
RET
|
||||
1
mshal/asm/i2cRead2109.bin
Normal file
1
mshal/asm/i2cRead2109.bin
Normal file
|
|
@ -0,0 +1 @@
|
|||
<EFBFBD>L<>"
|
||||
14
mshal/errors.go
Normal file
14
mshal/errors.go
Normal 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
123
mshal/hal.go
Normal 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
103
mshal/hal_patch_call.go
Normal 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
152
mshal/hal_patch_eeprom.go
Normal 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
38
mshal/hal_patch_gpio.go
Normal 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
95
mshal/hal_patch_i2c.go
Normal 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
434
mshal/hal_patch_install.go
Normal 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
51
mshal/hal_patch_movc.go
Normal 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
98
mshal/hal_region.go
Normal 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
213
mshal/hal_rom.go
Normal 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
97
mshal/region.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue