Skip to content

Instantly share code, notes, and snippets.

@charasyn
Created March 18, 2022 17:42
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save charasyn/206b2537534b6679b0961be64cf9c35f to your computer and use it in GitHub Desktop.
Save charasyn/206b2537534b6679b0961be64cf9c35f to your computer and use it in GitHub Desktop.
PineNote boot selector Uboot patch

Hello! This gist contains a Python script for extracting and inserting default enviroments from/to a PineNote Uboot image. Hopefully this works but I provide no guarantees. Maybe give it a read-over before running it, don't run code from the internet without knowing what it does, etc...

It also contains a patch for adding a boot "menu" selector based on the contents of a file. It will read mmc0:11:/boot/which_os.txt and based on the first character in that file, boot either Android or Linux. In case of an error, it should fallback to Android. Currently, the details for booting Linux are hard-coded in there to boot from mmc0:11, and it's expecting an extlinux config file in /extlinux/extlinux.conf or /boot/extlinux/extlinux.conf.

Hope this is useful!

1c1
< bootcmd=boot_fit;boot_android ${devtype} ${devnum};run distro_bootcmd;
---
> bootcmd=run bootsel;boot_android mmc 0
22,24d21
< mmc_boot=if mmc dev ${devnum}; then setenv devtype mmc; run scan_dev_for_boot_part; fi
< boot_net_usb_start=usb start
< usb_boot=usb start; if usb dev ${devnum}; then setenv devtype usb; run scan_dev_for_boot_part; fi
27,28d23
< boot_script_dhcp=boot.scr.uimg
< boot_targets=mmc1 mmc0 usb0 pxe dhcp
34,40c29,36
< scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done
< bootcmd_mmc1=setenv devnum 1; run mmc_boot
< bootcmd_mmc0=setenv devnum 0; run mmc_boot
< bootcmd_usb0=setenv devnum 0; run usb_boot
< bootcmd_pxe=run boot_net_usb_start; dhcp; if pxe get; then pxe boot; fi
< bootcmd_dhcp=run boot_net_usb_start; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;
< distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done
---
> sel_part=11
> sel_path=/boot/which_os.txt
> p_sel=0xe00000
> p_android=0xe00010
> p_linux=0xe00020
> b_android=echo Booting Android...;boot_android mmc 0
> b_linux=echo Booting Linux...;setenv devtype mmc;setenv devnum 0;setenv distro_bootpart 11;run scan_dev_for_boot
> bootsel=mw.b ${p_sel} 0x00;load mmc 0:${sel_part} ${p_sel} ${sel_path} 1;mw.b ${p_android} 0x61;mw.b ${p_linux} 0x6c;if cmp.b ${p_sel} ${p_android} 1;then run b_android;fi;if cmp.b ${p_sel} ${p_linux} 1;then run b_linux;fi
#!/usr/bin/env python3
# pinenote-uboot-envtool.py
# charasyn 2022
# based on https://github.com/DorianRudolph/pinenotes/blob/main/py/uboot_img.py
#
# This tool extracts and inserts the default environment from/to a PineNote
# Uboot image.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
from hashlib import sha256
import sys
from typing import List
LOG_ERROR = 0
LOG_WARNING = 1
LOG_VERBOSE = 2
LOG_DEBUG = 3
def set_loglevel(level):
global loglevel
loglevel = level
def pd(*args, **kwargs):
if loglevel < LOG_DEBUG:
return
kwargs['file'] = sys.stderr
print('[DBG]', *args, **kwargs)
def pv(*args, **kwargs):
if loglevel < LOG_VERBOSE:
return
kwargs['file'] = sys.stderr
print('[NFO]', *args, **kwargs)
def pw(*args, **kwargs):
if loglevel < LOG_WARNING:
return
kwargs['file'] = sys.stderr
print('[WRN]', *args, **kwargs)
def pe(*args, **kwargs):
if loglevel < LOG_ERROR:
return
kwargs['file'] = sys.stderr
print('[ERR]', *args, **kwargs)
loglevel = LOG_WARNING
preserve_comments = True
IMAGE_SZ = 0x200000
UBOOT_OFFSET = 0xE00
UBOOT_SZ = 0x128288
ENV_OFFSET = 0xC1730
ENV_SZ = 0xE29
def read_image_data(image_fp: str) -> bytes:
with open(image_fp, 'rb') as f:
data = f.read()
# the uboot image contains the same data two times
data_firstcopy = data[:IMAGE_SZ]
assert data_firstcopy == data[IMAGE_SZ:]
return data_firstcopy
def extract_uboot(img_data: bytes) -> bytes:
return img_data[UBOOT_OFFSET:UBOOT_OFFSET+UBOOT_SZ]
def get_uboot_hash_offset(img_data: bytes) -> int:
h = sha256(extract_uboot(img_data)).digest()
hash_offset = img_data.find(h)
pd(f'Uboot hash offset is {hash_offset:#08x}')
assert hash_offset > 0
return hash_offset
def extract(src_img, dst_env):
img = read_image_data(src_img)
uboot = extract_uboot(img)
env = uboot[ENV_OFFSET:ENV_OFFSET+ENV_SZ]
env_before_terminator, _, _ = env.partition(b'\0\0')
env_lines = (str(entry, encoding='ascii') for entry in env_before_terminator.split(b'\0'))
with open(dst_env, 'w') as f:
f.writelines(f'{line}\n' for line in env_lines)
def insert(src_img, src_env, dst_img):
with open(src_env) as f:
env_lines_raw = f.readlines()
if preserve_comments:
env_lines = (line.rstrip('\n') for line in env_lines_raw)
else:
env_lines = (line.partition('#')[0].strip() for line in env_lines_raw)
env_lines = [line for line in env_lines if line]
for line in env_lines:
assert line.isprintable(), f"Non-printable characters in line '{line}'"
assert line.isascii(), f"Non-ascii characters in line '{line}'"
env_data = b'\0'.join(bytes(line, encoding='ascii') for line in env_lines)
env_data += b'\0'
pv(f"Environment uses {len(env_data)} bytes of space out of {ENV_SZ} available")
assert len(env_data) <= ENV_SZ, f"Environment exceeds maximum size"
env_data_padded = env_data.ljust(ENV_SZ, b'\0')
if not src_img:
pv(f"Using destination image '{dst_img}' as Uboot source image")
src_img = dst_img
img = read_image_data(src_img)
hash_offset = get_uboot_hash_offset(img)
uboot = extract_uboot(img)
uboot_patched = bytearray(uboot)
uboot_patched[ENV_OFFSET:ENV_OFFSET+ENV_SZ] = env_data_padded
uboot_patched_hash = sha256(uboot_patched).digest()
img_patched = bytearray(img)
img_patched[UBOOT_OFFSET:UBOOT_OFFSET+UBOOT_SZ] = uboot_patched
img_patched[hash_offset:hash_offset+len(uboot_patched_hash)] = uboot_patched_hash
with open(dst_img, 'wb') as f:
f.write(img_patched)
f.write(img_patched)
def usage():
print('''
usage: pinenote-uboot-envtool.py <extract|insert> ...
This program extracts and modifies the default environment in a PineNote
Uboot image. It transforms the environment from a series of null-terminated
lines into a plain-text file that can be easily modified and re-inserted.
extract: Extracts the default environment.
pinenote-uboot-envtool.py extract <input image> <output environment>
insert: Inserts a new default environment.
pinenote-uboot-envtool.py insert <input image> <input environment> <output image>
This program is provided with no guarantees or warranty. Use at your own risk!
''')
def main(argv: List[str]):
while argv and argv[0][0] == '-':
if argv[0] in ['-h', '--help']:
argv.pop(0)
usage()
return
elif argv[0] in ['-d', '--debug']:
argv.pop(0)
set_loglevel(LOG_DEBUG)
elif argv[0] in ['-v', '--verbose']:
argv.pop(0)
set_loglevel(LOG_VERBOSE)
# Expect verb
if not argv:
usage()
pe(f'Arguments not provided')
return
verb = argv.pop(0)
def check_argcount(argcount):
if len(argv) != argcount:
usage()
pe(f'Wrong arguments provided for {verb}, expected {argcount} but got {len(argv)}')
return False
return True
if verb == 'extract':
if not check_argcount(2):
return
extract(*argv)
elif verb == 'insert':
if not check_argcount(3):
return
insert(*argv)
else:
usage()
pe(f'Invalid argument verb {argv[0]}')
return
if __name__ == '__main__':
main(sys.argv[1:])
@ClashTheBunny
Copy link

Note that u-boot (at least the default/non-mainline one) treats partitions as hex, so change the 11 everywhere to be whatever your partition is. If you shrunk 16 and added a 17 afterwards, 11 is correct. If you reused /cache, which is 11, you'll want b.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment