ms-tools/mshal/hal_patch_install.go
2022-11-20 18:19:34 +01:00

448 lines
9.9 KiB
Go

package mshal
import (
"bytes"
_ "embed"
"encoding/binary"
"encoding/hex"
"errors"
"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 {
// PUSH R7
// MOV R0, #R0Value
// LCALL hookAddr
// POP R7
// ...orig... -> If this returns there will be no jump to origAddr, which is what we want.
// LJMP origAddr
result := []byte{
0xC0, 0x7,
0x78, R0Value,
0x12, byte(hookAddr >> 8), byte(hookAddr),
0xD0, 0x7,
}
/* Add snippet of replaced code */
result = append(result, orig...)
if origAddr != 0 {
/* Jump to rest of original code */
result = append(result, []byte{0x02, byte(origAddr >> 8), byte(origAddr)}...)
} else {
/* Just return if no origAddr */
result = append(result, 0x22)
}
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 CodeBlob struct {
Data []byte
Relocate 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 = []CodeBlob{
{
Data: codeCallgate2106,
Relocate: relocateCallgate,
}, {
Data: codeGpio,
}, {
Data: codeMOVC,
}}
var installBlobs2109 = []CodeBlob{
{
Data: codeCallgate2109,
Relocate: 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) {
var installBlobs []CodeBlob
if h.deviceType == 2109 {
installBlobs = installBlobs2109
} else if h.deviceType == 2106 {
installBlobs = installBlobs2106
} else {
return false, errors.New("this device does not support runtime patching")
}
h.patchCallAddrsExternalStart = len(installBlobs)
installBlobs = append(installBlobs, h.config.PatchBlobs...)
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.Relocate != nil {
dataCopy := make([]byte, len(data))
copy(dataCopy, data)
callAddr, data = m.Relocate(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)
}