Skip to content

Instantly share code, notes, and snippets.

@pexcn
Forked from Lekensteyn/patch-rtc.py
Last active September 15, 2015 09:49
Show Gist options
  • Save pexcn/009a66cc2a0edc8f06aa to your computer and use it in GitHub Desktop.
Save pexcn/009a66cc2a0edc8f06aa to your computer and use it in GitHub Desktop.
Script to patch an Android recovery.img file, changing the qcom,qpnp-rtc-write Device Tree property (tested with an Android 5.1 image based on kernel 3.4 for a Nexus 5 (rev_11)).
#!/usr/bin/env python
"""
Patch a recovery image to enable RTC writing
Unfortunately it has not the intended effect (tested from recovery):
~ # /system/xbin/hwclock -w
hwclock: RTC_SET_TIME: Operation not permitted
<3>[ 25.821247] spmi_pmic_arb fc4cf000.qcom,spmi: pmic_arb_wait_for_done: transaction denied (0x5)
<3>[ 25.821338] qcom,qpnp-rtc qpnp-rtc-ee162000: SPMI write failed
<3>[ 25.821408] qcom,qpnp-rtc qpnp-rtc-ee162000: Write to RTC reg failed
"""
__author__ = "Peter Wu"
__email__ = "[email protected]"
__license__ = "MIT"
import argparse, hashlib, logging, struct, sys
_logger = logging.getLogger(__name__)
def check(actual, expected, message):
if expected != actual:
raise RuntimeError("CHECK (%s == %s) failed: %s" % \
(expected, actual, message))
# Expected page size
PAGE_SIZE = 2048
uint32_le_type = struct.Struct("<I") # For boot header
uint32_be_type = struct.Struct(">I") # For FDT
# unsigned 32-bit integers
KERNEL_SIZE_OFFSET = 8
RAMDISK_SIZE_OFFSET = 16
SECOND_SIZE_OFFSET = 24
PAGE_SIZE_OFFSET = 36
CHECKSUM_OFFSET = 576
CHECKSUM_LENGTH = 20
# Node name containing the qcom,qpnp-rtc-write property.
QCOM_RTC_NODE_NAME = b"qcom,pm8941_rtc"
# offset in structure for qcom,qpnp-rtc-write int property
QPNP_RTC_WRITE_PROP_NAMEOFF = 0x00001fa7
# https://github.com/we3des/psychic-octo-robot/wiki/Android-boot.img-zImage-format
# https://github.com/android/platform_system_core/blob/master/mkbootimg/bootimg.h
def parse_boot_img_header(block):
check(block.startswith(b"ANDROID!"), True, "Boot magic")
kernel_size, = uint32_le_type.unpack_from(block, KERNEL_SIZE_OFFSET)
ramdisk_size, = uint32_le_type.unpack_from(block, RAMDISK_SIZE_OFFSET)
second_size, = uint32_le_type.unpack_from(block, SECOND_SIZE_OFFSET)
page_size, = uint32_le_type.unpack_from(block, PAGE_SIZE_OFFSET)
check(page_size, PAGE_SIZE, "page size")
checksum = block[CHECKSUM_OFFSET:CHECKSUM_OFFSET+CHECKSUM_LENGTH]
return kernel_size, ramdisk_size, second_size, checksum
# SHA1(kernel | kernel_size | initrd | initrd_size | extra | extra_size)
def calculate_checksum(kernel, ramdisk, second):
h = hashlib.sha1()
for item in (kernel, ramdisk, second):
h.update(item)
h.update(uint32_le_type.pack(len(item)))
return h.digest()
def calc_padding_size(size, block_size=4):
"""Returns the needed padding count to align data at word size."""
return (block_size - (size % block_size)) % block_size
def print_size(label, size):
_logger.info("%s", "{0:<19} {1:#010x} ({1})".format(label + ":", size))
def read_aligned(f, size):
data = f.read(size)
padding_size = calc_padding_size(size, PAGE_SIZE)
return data, f.read(padding_size)
def read_image(image_filename):
with open(image_filename, "rb") as f:
header = f.read(PAGE_SIZE)
kernel_size, ramdisk_size, second_size, checksum = \
parse_boot_img_header(header)
print_size("Kernel size", kernel_size)
print_size("Ramdisk size", ramdisk_size)
print_size("Second stage size", second_size)
kernel, kernel_padding = read_aligned(f, kernel_size)
ramdisk, ramdisk_padding = read_aligned(f, ramdisk_size)
second, second_padding = read_aligned(f, second_size)
checksum_expected = calculate_checksum(kernel, ramdisk, second)
check(checksum, checksum_expected, "Invalid checksum")
trailer = f.read()
check(0, len(trailer), "expected no trailing data")
return [header, kernel, kernel_padding, ramdisk, ramdisk_padding,
second, second_padding]
def write_image(image_filename, blocks):
with open(image_filename, "wb") as f:
for block in blocks:
f.write(block)
# Scan for structure block in FDT
# https://www.power.org/wp-content/uploads/2012/06/Power_ePAPR_APPROVED_v1.1.pdf#page=91
def find_structure_block(fdt, node_name, pos=0):
assert pos % 4 == 0
byteseq = b'\x00\x00\x00\x01'
byteseq += node_name
byteseq += b'\x00' * calc_padding_size(len(node_name))
_logger.debug("Looking for %s in %d bytes", byteseq, len(fdt))
# For each possible FDT_BEGIN_NODE, find properties.
while True:
pos = fdt.find(byteseq, pos)
_logger.debug("FDT_BEGIN_NODE query result: %d", pos)
if pos == -1:
break
pos += len(byteseq) # skip FDT_BEGIN_NODE
while True:
token_type, = uint32_be_type.unpack_from(fdt, pos)
_logger.debug("FDT token type: %#010x", token_type)
pos += 4 # skip token type
if token_type == 2: # FDT_END_NODE
break # Nothing interesting, look for next node.
elif token_type == 4: # FDT_NOP
pass # ignore, no data
elif token_type == 3: # FDT_PROP
# Scan for property (uint32_t len, namelen; uint8_t value[len])
prop_len, = uint32_be_type.unpack_from(fdt, pos)
prop_nameoff, = uint32_be_type.unpack_from(fdt, pos + 4)
pos += 8
_logger.debug("Prop len=%d, nameoff=%d", prop_len, prop_nameoff)
if prop_nameoff == QPNP_RTC_WRITE_PROP_NAMEOFF:
return pos
# skip value and padding
pos += prop_len + calc_padding_size(prop_len)
else:
# Perhaps we did not run into a FDT node...
_logger.warning("Unexpected token type %d", token_type)
break
return pos
def iter_props(kernel):
pos = 0
while pos != -1:
pos = find_structure_block(kernel, QCOM_RTC_NODE_NAME, pos)
if pos != -1:
_logger.info("Found property value at offset %d", pos)
yield pos
def patch_kernel(kernel, new_value):
kernel = bytearray(kernel)
for pos in iter_props(kernel):
value, = uint32_be_type.unpack_from(kernel, pos)
if value != new_value:
_logger.info("Setting qcom,qpnp-rtc-write = <%#x>", 1)
kernel[pos + 3] = new_value
else:
_logger.info("Not changing qcom,qpnp-rtc-write = <%#x>", value)
return kernel
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--debug", action="store_true",
help="Enable verbose debug logging")
parser.add_argument("-n", "--dry-run", action="store_true",
help="Avoid writing the file")
parser.add_argument("image_file", help="recovery.img file")
parser.add_argument("enable_write", type=int, choices=(0,1),
help="Enable or disable RTC writing")
def main():
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO,
format="%(name)s: %(message)s")
image_filename = args.image_file
new_value = args.enable_write
blocks = read_image(image_filename)
header = blocks[0]
kernel = blocks[1]
ramdisk = blocks[3]
second = blocks[5]
kernel = patch_kernel(kernel, new_value)
checksum = calculate_checksum(kernel, ramdisk, second)
header = header[0:CHECKSUM_OFFSET] + checksum + \
header[CHECKSUM_OFFSET+CHECKSUM_LENGTH:]
blocks[0:2] = header, kernel
if not args.dry_run:
write_image(image_filename, blocks)
else:
_logger.info("Would write new image %s", image_filename)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment