Created
September 9, 2020 21:06
-
-
Save tonyg/4ea14f4dfe414422c0648c6e0a8bcb5d to your computer and use it in GitHub Desktop.
Quick-and-dirty plain-kernel-interface python modem boot and operation scripts for Samsung Galaxy S7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Per cbd, "SS310 modem", "shannon310". This is a Samsung Galaxy S7 SM-G930F. | |
# ["/sbin/cbd", "-d", "-tss310", "-bm", "-mm", "-P", "platform/155a0000.ufs/by-name/RADIO"] | |
boot0_path = '/dev/umts_boot0' | |
firmware_partition = '/dev/disk/by-partlabel/RADIO' ## maps to /dev/sda8 on my phone | |
nv_data_path = '/efs/nv_data.bin' ## mount /dev/disk/by-partlabel/EFS (/dev/sda3 for me) on /efs | |
import cffi | |
import datetime | |
import os | |
import struct | |
import sys | |
from collections import namedtuple | |
from fcntl import ioctl | |
from time import sleep | |
ffi = cffi.FFI() | |
def mkstruct(name, fields, spec): | |
cls = namedtuple(name, fields) | |
def iter_load(bs): | |
return map(cls._make, struct.iter_unpack(spec, bs)) | |
cls.iter_load = iter_load | |
def load(bs): | |
return cls._make(struct.unpack(spec, bs)) | |
cls.load = load | |
def iter_save(items): | |
return b''.join(struct.pack(spec, *item) for item in items) | |
cls.iter_save = iter_save | |
def save(item): | |
return struct.pack(spec, *item) | |
cls.save = save | |
return cls | |
TOC = mkstruct('TOC', 'name offset loadaddr size crc entryid', '<12sIIIII') | |
# struct modem_firmware | |
FirmwareChunk = mkstruct('FirmwareChunk', 'binary size m_offset b_offset mode len', '<QIIIII') | |
# struct modem_sec_req | |
SecReq = mkstruct('SecReq', 'mode size_boot size_main', '<III4x') | |
# enum modem_state from modem_prj.h | |
enum_modem_state = [ | |
'STATE_OFFLINE', | |
'STATE_CRASH_RESET', | |
'STATE_CRASH_EXIT', | |
'STATE_BOOTING', | |
'STATE_ONLINE', | |
'STATE_NV_REBUILDING', | |
'STATE_LOADER_DONE', | |
'STATE_SIM_ATTACH', | |
'STATE_SIM_DETACH', | |
'STATE_CRASH_WATCHDOG', | |
] | |
def security_request(fd, req): | |
print(req, ioctl(fd, 0x6f53, bytearray(req.save()))) | |
def send_chunk(fd, total_size, load_offset, chunk, file_offset): | |
p = ffi.from_buffer(chunk) | |
mode = 0 ## cbd uses mode 0 for all three uploads | |
mf = FirmwareChunk(int(ffi.cast('long long', p)), total_size, load_offset, file_offset, mode, len(chunk)) | |
print(mf, ioctl(fd, 0x6f40, bytearray(mf.save()))) | |
def send_region(fd, fh, entry, base_loadaddr): | |
load_offset = entry.loadaddr - base_loadaddr | |
file_offset = entry.offset | |
fh.seek(file_offset) | |
total_size = entry.size | |
remaining = total_size | |
while remaining > 0: | |
chunksize = min(remaining, 62 * 1024) | |
chunk = fh.read(chunksize) | |
send_chunk(fd, total_size, load_offset, chunk, file_offset) | |
load_offset = load_offset + chunksize | |
file_offset = file_offset + chunksize | |
remaining = remaining - chunksize | |
print('qnd_cbd') | |
with open(firmware_partition, 'rb') as f: | |
toc = list(TOC.iter_load(f.read(512))) | |
(boot_toc_entry, main_toc_entry, nv_toc_entry) = toc[1:4] | |
print(boot_toc_entry) | |
print(main_toc_entry) | |
print(nv_toc_entry) | |
with open('/sys/power/wake_lock', 'wb') as f: | |
f.write(b'ss310') | |
boot0_fd = os.open(boot0_path, os.O_RDWR) | |
print('modem_reset', ioctl(boot0_fd, 0x6f21)) | |
# Aha! If we stay as root, then the security_request(2, 0, 0) fails | |
# with error code 11. cbd does setuid(1001) (while retaining | |
# capabilities!) before doing security_request(2, 0, 0), and in that | |
# case it succeeds with error code 0. HOWEVER it seems like you can | |
# ask cbd to stay as root with `-or`, in which case it gets the error | |
# code 11 too - which it ignores! The modem seems (?) well-configured | |
# afterward, so perhaps it's not required? | |
# | |
security_request(boot0_fd, SecReq(2, 0, 0)) | |
with open(firmware_partition, 'rb') as f: | |
send_region(boot0_fd, f, boot_toc_entry, boot_toc_entry.loadaddr) | |
send_region(boot0_fd, f, main_toc_entry, boot_toc_entry.loadaddr) | |
with open(nv_data_path, 'rb') as f: | |
send_region(boot0_fd, f, nv_toc_entry, boot_toc_entry.loadaddr) | |
security_request(boot0_fd, SecReq(0, boot_toc_entry.size, main_toc_entry.size)) | |
print('modem_on', ioctl(boot0_fd, 0x6f19)) | |
print('modem_boot_on', ioctl(boot0_fd, 0x6f22)) | |
print('modem_dl_start', ioctl(boot0_fd, 0x6f28)) | |
os.write(boot0_fd, bytearray(struct.pack('I', 0x0000900d))) | |
print(hex(struct.unpack('I', os.read(boot0_fd, 4))[0])) | |
os.write(boot0_fd, bytearray(struct.pack('I', 0x00009f00))) | |
print(hex(struct.unpack('I', os.read(boot0_fd, 4))[0])) | |
print('modem_boot_off', ioctl(boot0_fd, 0x6f23)) | |
os.close(boot0_fd) | |
with open('/sys/power/wake_unlock', 'wb') as f: | |
f.write(b'ss310') | |
#--------------------------------------------------------------------------- | |
sys.stdout.flush() | |
with open(boot0_path, 'rb') as f: | |
while True: | |
i = f.read(512) | |
if len(i) > 0: | |
status = enum_modem_state[ioctl(f.raw.fileno(), 0x6f27)] | |
print(str(datetime.datetime.now()), '({:03x})'.format(len(i)), status, i.hex()) | |
sys.stdout.flush() | |
else: | |
sleep(1) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
ipc0_path = '/dev/umts_ipc0' | |
rfs0_path = '/dev/umts_rfs0' | |
from collections import namedtuple | |
from fcntl import ioctl | |
import cffi | |
import datetime | |
import os | |
import select | |
import struct | |
import sys | |
ffi = cffi.FFI() | |
def mkstruct(name, fields, spec): | |
cls = namedtuple(name, fields) | |
def iter_load(bs): | |
return map(cls._make, struct.iter_unpack(spec, bs)) | |
cls.iter_load = iter_load | |
def load(bs): | |
return cls._make(struct.unpack(spec, bs)) | |
cls.load = load | |
def iter_save(items): | |
return b''.join(struct.pack(spec, *item) for item in items) | |
cls.iter_save = iter_save | |
def save(item): | |
return struct.pack(spec, *item) | |
cls.save = save | |
return cls | |
# struct sipc_fmt_hdr, modem_prj.h | |
Packet = mkstruct('Packet', 'len msg_seq ack_seq main_cmd sub_cmd cmd_type', '<HBBBBB') | |
# enum modem_state from modem_prj.h | |
enum_modem_state = [ | |
'STATE_OFFLINE', | |
'STATE_CRASH_RESET', | |
'STATE_CRASH_EXIT', | |
'STATE_BOOTING', | |
'STATE_ONLINE', | |
'STATE_NV_REBUILDING', | |
'STATE_LOADER_DONE', | |
'STATE_SIM_ATTACH', | |
'STATE_SIM_DETACH', | |
'STATE_CRASH_WATCHDOG', | |
] | |
class Driver(object): | |
def __init__(self, name, fd): | |
self.name = name | |
self.fd = fd | |
def read_ready(self): | |
i = os.read(self.fd, 65536) | |
self.decode(i) | |
print(str(datetime.datetime.now()), self.name, '({:04x})'.format(len(i)), self.statusreport()) | |
def decode(self, bs): | |
raise Exception('subclassResponsibility') | |
def statusreport(self): | |
raise Exception('subclassResponsibility') | |
class IpcDriver(Driver): | |
def __init__(self, *args): | |
super().__init__(*args) | |
self.head = None | |
self.body = b'' | |
self.leftover = b'' | |
def decode(self, bs): | |
self.head = Packet.load(bs[:7]) | |
bodysize = self.head.len - 7 | |
self.body = bs[7:self.head.len] | |
self.leftover = bs[self.head.len:] | |
def statusreport(self): | |
status = enum_modem_state[ioctl(self.fd, 0x6f27)] | |
return ' '.join([status, repr(self.head), self.body.hex(), repr(self.body), self.leftover.hex()]) | |
class RfsDriver(Driver): | |
def __init__(self, *args): | |
super().__init__(*args) | |
self.packet = b'' | |
def decode(self, bs): | |
self.packet = bs | |
def statusreport(self): | |
return self.packet.hex() | |
ipc0_fd = os.open(ipc0_path, os.O_RDWR) | |
rfs0_fd = os.open(rfs0_path, os.O_RDWR) | |
drivers = {} | |
drivers[ipc0_fd] = IpcDriver('ipc0', ipc0_fd) | |
drivers[rfs0_fd] = RfsDriver('rfs0', rfs0_fd) | |
while True: | |
(readfds, _writefds, errorfds) = select.select([ipc0_fd, rfs0_fd], [], [ipc0_fd, rfs0_fd]) | |
if len(errorfds) != 0: | |
print('AIEEE', errorfds) | |
break | |
for fd in readfds: | |
drivers[fd].read_ready() | |
sys.stdout.flush() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment