USMI

Объявление

12/06/2025 (Administration) - Spamming for the purpose of boosting messages will be punished with a ban. Спам с целью накрутки сообщений будет караться баном.

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » USMI » MCU, SoC, CPU Микроконтроллеры » Bluetrum SoC


Bluetrum SoC

Сообщений 141 страница 160 из 162

141

Обновлено.

Подпись автора

USMicro® 2026©

142

https://upforme.ru/uploads/001b/ca/8a/234/t652973.jpg
Я попробовал, и похоже, что чтение и запись прошли успешно, однако, похоже, CODE KEY отличается, поэтому плата не работает. У тебя есть какой-нибудь способ?

143

Вы записываете на другой чип. На другой чип просто так без перезборки или перешивровки как я понял XCOD региона, дамп просто так нельзя просто записать.
Я как раз работаю над этим.

Подпись автора

USMicro® 2026©

144

BIOS написал(а):

Bạn đang ghi dữ liệu vào một chip khác. Theo như tôi hiểu, bạn không thể chỉ ghi dữ liệu sao lưu vào một chip khác mà không cần lắp ráp lại hoặc nạp lại vùng XCOD.
Tôi hiện đang nghiên cứu vấn đề này.

Да, именно так. Я считал данные и записал их в аналогичную плату, но с другим CODE KEY, и сейчас она не работает.

145

Я вижу, что ты тоже прошивал два чипа с разными CODE KEY.

146

Разобрался, причем сам)
Все просто оказалось. ЩАс перепроверю все и покажу что и как.

Подпись автора

USMicro® 2026©

147

Так, обнаружил проблему.
Распаковал, запаковал штатным Xmaker. Прошился - вроде все ок. Но не работают кнопки и некоторые функции, глюки, зависания.
Дело в том, что в распакованных прошивках нет xcfg.bin. А он и отвечает за всякое. Это типа файл с настройками IO  и тд.
Для полной создания полной прошивки DCF нужны следующие файлы -
header.bin,
app.bin,
res.bin,
xcfg.bin,
updater.bin
------------------------------------------------------------------------------------------
Тоесть в распаковщик нужно добавить парсинг xcfg.bin.

Подпись автора

USMicro® 2026©

148

Проще будет перешифровать XCOD прям в дампе, чем разбирать его полностью. Этот xcfg.bin какой то странный. Не понятно где он, часть в начале дампа где имя БТ, а вторая вообще фиг знает.

Подпись автора

USMicro® 2026©

149

BIOS написал(а):

Проще будет перешифровать XCOD прям в дампе, чем разбирать его полностью

Разобрался - перешифровка XCOD работает.
Нужно узнать codekey от дампа старого чипа, и от нового (зашив в них что то их под сдк), перешифровав с старого на новый - прошить, заработало.
С этим понятно. Но есть НО -
Некоторые прошивки содержат в себе NV Data region. Их несколько
Там хранится - XCFG, но не в том виде что брал сборщик SDK, он как то шифруется. Не так как XCOD area.
xcfg NV в дампе зашифрован аппаратным DMA ключом.
И вот с этим пока проблема.

Подпись автора

USMicro® 2026©

150

Xmaker использует следующее -
make(dcf_buf, header.bin, app.bin, res.bin, xcfg.bin, updater.bin);
save(dcf_buf, app.dcf);
Он берет готовые бинарники и сшивает их. В дампе мы находим header.bin, app.bin, res.bin, - но не xcfg.bin, updater.bin.

Подпись автора

USMicro® 2026©

151

Нашел!
Некоторые прошивки содержат NV xcfg если при сборке в app.xm были следующие параметры - setflash(1, 0x100000, 1, 1, 1).
Когда setflash(..., 0) — xcfg хранится только в DCF. Кнопки/GPIO конфигурируются через app code напрямую.
Когда setflash(..., 1) — xcfg пишется в NV область flash. Прошивка при старте читает его оттуда. Без него кнопки/BT не работают.

Подпись автора

USMicro® 2026©

152

https://upforme.ru/uploads/001b/ca/8a/2/t329809.png

Подпись автора

USMicro® 2026©

153

Привет всем! Удалось ли кому-нибудь из вас отладить микросхему серии JAZZ?

154

SnowPoke
Что именно интересует?

Подпись автора

USMicro® 2026©

155

Bluetrum_Flash_Tool v1 - Для Windows. Без установки. Без привязки к питону или чему либо.
Скачал - запустил - подключил - работает.
Просто выбрав в настройках нужный порт. и скорость. В зависимости от UART чипа. Если происходят ошибки синхро или чтения/записи - понизить скорость.
Пока v1. Я тестировал не все, пару чипов почитал/пописал.
https://upforme.ru/uploads/001b/ca/8a/2/t611795.png

https://drive.google.com/file/d/1vlCo78 … drive_link

Подпись автора

USMicro® 2026©

156

Та штука что не дает читать с  адреса 0x2000 - не защита, а алгоритм типа такой. Там не бит разблокировки, а флаг шифрования XCOD, там их несколько.
AB5301A
https://upforme.ru/uploads/001b/ca/8a/2/t513789.png

Подпись автора

USMicro® 2026©

157

kagaimiq
Вопрос  к тебе, какой в реале в JL формат Efuse Key? 0xFFFF Или это производное crc от  ff ff ff ff битов?
Я щас расколупал чтение и перешифровку XCOD региона на горячую прям на лету, что бы ручками не делать ничего,
Я думаю лучше сделать представление как в твоих прогах JL...
Тут в bluetrum Fuse имеют формат
Chip ID:       b'BLUECRWN\x01\x00\x00\x00'
Load address:  $00012000
Init. commkey: $10C3890C
New commkey:   $AAC1330E
Changing baudrate to 921600 baud...
- Flash JEDEC ID:     856014
- Flash UID:          415034553635330300a7ec4907103678
- Flash size:         1048576 bytes
- App codekey (XCOD): 444BF5C3
- eFuse Key:          0x5B2A
Как я понял - нужно прожигать то что в самих efuse, ити достаточно того же самого crc?

Подпись автора

USMicro® 2026©

158

Фикс для downloader.py для работы с чипами серии EPIC.
Чтение работает отлично, а вот запись работает странно: бьются следующие области:
0x00000–0x0FFFF OK
0x10000–0x1FFFF битый
0x20000–0x2FFFF OK
0x30000–0x3FFFF битый
0x40000–0x4FFFF OK
0x50000–0x5FFFF битый
0x60000 + в основном OK.
Так что при записи лучше перепроверять и перезаписывать битые области.

Код:
from bluetrum.cipher import ab_calckey
from bluetrum.utils import *

import struct
import argparse

from base64 import b64decode
from tqdm import tqdm

###############################################################################

try:
    from serial import Serial
    from bluetrum.dl.uart import UARTDownload
    have_uart = True
except ImportError:
    have_uart = False

try:
    from scsiio import SCSIDev
    have_scsi = True
except ImportError:
    have_scsi = False

if not have_uart and not have_scsi:
    print('No available ways to communicate with the hardware.')
    print('Install pyserial for UART or rip "scsiio" from jl-uboot-tool for USB MSC.')
    exit(1)

###############################################################################

ap = argparse.ArgumentParser(description='Tool to communicate with the bootloader in the Bluetrum chips.')

if have_uart:
    ap.add_argument('--init-baud', type=int, default=115200,
                    help='Initial baudrate (default: %(default)d baud)')
    ap.add_argument('--baud', type=int, default=921600,
                    help='Baudrate to use (default: %(default)d baud)')
    ap.add_argument('--port',
                    help='Serial port to use for UART bootloader')
    ap.add_argument('--no-echo', action='store_true', default=False,
                    help='Disable echo (for CRWN/AB530X chips)')

if have_scsi:
    ap.add_argument('--mscdev',
                    help='USB MSC (SCSI) device to use for USB bootloader')

ap.add_argument('-r', '--reboot', action='store_true',
                help='Reboot the chip after completion')
ap.add_argument('--debug', action='store_true', default=False,
                help='Enable extra diagnostic output')

actsp = ap.add_subparsers(dest='action')

asp_erase = actsp.add_parser('erase', help='Erase one or more flash areas')
asp_erase.add_argument('areas', metavar='address size', nargs='+',
                       help='Erase <size> bytes at <address>.'
                            ' If <size> is 0, assumed to be whole flash.')

asp_read = actsp.add_parser('read', help='Read the flash into the file')
asp_read.add_argument('areas', metavar='address size file', nargs='+',
                      help='Read <size> bytes from <address> into <file>.'
                           ' If <size> is 0, assumed to be whole flash.')

asp_write = actsp.add_parser('write', help='Write the file into flash')
asp_write.add_argument('areas', metavar='address file', nargs='+',
                       help='Write <file> starting at <address>.')

args = ap.parse_args()

###############################################################################

# PRAO (AB560x) — hand-built blob; bytes [4..23] are reserved for
# chipid / iface / blocksize injection at load time.
dl_blob = b64decode(
    "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"
)

# CRWN (AB530x) — decrypted uartdown-001.dll.
# Bytes [4..23] contain live UART helper functions used by the blob itself;
# do NOT overwrite them at load time (unlike the PRAO blob).
dl_blob_crwn = b64decode(
    "bwAAJiMooAaDJ8AGE5f3AONMB/6CgCMoAAaDJ8AGE5f3AONMB/4DJQAHE3X1D4KAgyfABmER"
    "BsIuwJPnFwAjJvAGE2UFIH0/gkUuhWU/gyfABpJAIQH5myMm8AaCgIMnwAZxEQbAk+cXACMm"
    "8AYTZQUQST9NN4MnwAaCQBEB+ZsjJvAGgoAREYVnIsomyCqESsZOxFLCVsAGzJOHJ6CuhCMq"
    "8F4JRRMZRAFJIKKUMwmJQIlJDUqpSmMQlAIJRb0g4kABRVJEwkQySaJJEkqCSiMqAF5xAYKA"
    "swckAbIHIyzwXk6FoSBWhSMqQF+BIIMnwF8FBKMP9P4jKjBfdb8AAKqVYxO1AIKABQWDR/X/"
    "IyDwNMW/AyXADYKAgyfADTOFp0AzNbUAE0UVAIKAgUdjk6cAgoABAIUH3b+TBwADMwX1AgMn"
    "wA2pRoMnwA2Zj2PjpwCCgCMk0AbFvyqWqodjk8cAgoCFB6OPt/7VvyqWqodjk8cAgoCFBQPH"
    "9f+FB6OP5/79txHOKpaqhwPFBwADxwUAhQeFBRmNY4XHAH3VgoABRYKAg0cFAANHFQDiB0IH"
    "2Y8DRzUAA0UlANmPIgVdjYKAg0cFAANFFQCiB12NgoCT14UAowG1ACMB9QCT1wUB4YGjAPUA"
    "IwC1AIKAowC1AKGBIwC1AIKAQRGTdvUPk1eFABNXBQEVZmGBk/f3DxN39w+jAaEAIwKhABMG"
    "VlWZRQqFBsYixCMA0QCjAPEAIwHhAKMC4QAjA/EAowPRAO/QtmMtZhMUBQETBqaqEwUhAJlF"
    "79B2YrJAQY0iREEBgoATAUH8JtiFZCLaStZO1AbcUtJW0FrOXsxiymbIasZuxJOHBKojJPAG"
    "gyeAN0BBkwkAApP39/wjLPA2g0cEACqJY4E3W2Ph+QppR2OI5zxjZPcGQUcEQWOO5zhFR2OF"
    "5zrjlwc21WlVZZOHCSAFSxMGACCBRRMFBQADShQAg0okAANJRACjhGcDjT3VZyOmByaTB/AP"
    "E4QJIGMc+hYjgGQBo4AEACOBBACjgQQAEUQihe/QllVtrnVHY4LnTHlHY4fnTnFH45XnMBxB"
    "VWejgQcAI4AHAKOABwAjgQcAA0eXIqOB5wDhtxMHoALjjecAY2/3AhMHUAKDKQUAY4TneBMH"
    "gAJjgud6EwcgAuOS5yzBRU6FETuD14kAo4AJACOACQCjgfkAoYMjgfkASbcTB+AC44PnEBMH"
    "8ALjiucEEwfQAuOX5ygTBRQA9TuDR1QAE/cnAOMNBwA39P8AaYxVaRMHCSADR4cBEwkJIBGL"
    "YwEHMIWL44UHANVqIoqilFVrkwsAIJOKikt9XGNzmi6DJ4kAgUZehtKFEwWLK4KX/VYTB4sr"
    "UEMcQxMHBwjxjwMmh/jxjwMmx/jxjwMmB/nxjwMmR/nxjwMmh/nxjwMmx/nxjwMmB/rxjwMm"
    "R/rxjwMmh/rxjwMmx/rxjwMmB/vxjwMmR/vxjwMmh/vxjwMmx/vxj/2O42BX+2ObhncTCgog"
    "rb+NR2PzRwEBSmOTCgCNSmMTCQANSR1F4T6TZXUAk/X1Dx1FeT5jAwoGSobWhVKF7wDQHe8A"
    "UDwqiRMFAAzvAFBlkwfwDyOgCSBjE/kKiUdjEyULg6cJIAVHY4HnCglHY4PnDLc3AQCTh8fj"
    "HMi3NwEAk4fH4xzEtzcBAJOHx+NcxLc3AQCTh8fjYaiDJ8BxEwVABpPnBwEjLvBwgyfAcJPn"
    "BwEjJvBwGTmDJ4BwSobWhcGLjesJRe8AUBXvANAzKosTBQAM7wDQXJMH8A8JSuMS+/bjEGX3"
    "SobWhQ1F7wDwEg1Kgb8FRe8AUBIFSpm34xL19oVHI6D5IKm/tzcBAJOHZy0cyLc3AQCTh+cQ"
    "HMS3NwEAk4dnJlzEtzcBAJOHhwlcyBWotzcBAJOHB1QcyLc3AQCTh0c9HMS3NwEAk4enSFzE"
    "tzcBAJOHBzeBRRMFAApcyO8A0FfBRROFRAAjLAQAXTyDx8QAhYuZ54NHhAGT5+cAIwz0AJMH"
    "UAQjgPQAkwfgBKOA9ACTBwAEI4H0AJMHMASjgfQAkwcgBSOE9ACTB5AFo4T0AJMHAAUjhfQA"
    "kwdABaOF9ACxRSaF7xDQIZMFRQAhRhMFxAEVPiMiBAIjgEQBg6cJIKOA9AAcTCOB9ABludVl"
    "EwYAIJOFBQAmhTk2EwQAIG2xhUcjgAQAo4AEACOBBACjgfQAUbmDR2QAA0d0AFVpogfZj4VG"
    "EwcJIINJVACjBNcCBUcTCQkgY2n3AqHrVWSRRRMFhCvxOgNHlCuDR4QrIgddj4NHpCvCB12P"
    "g0e0K+IH2Y+Z46MECQLiUAFFUlTCVDJZolkSWoJackviS1JMwkwyTaJNEwHBA4KA79DWF5MH"
    "ACDjG/X8g1TEH7dWS1mThoYEk8f0/8IHE5cEATWPwYO5jyMk8DSTBsQfoodjndcGQWaDJ4A0"
    "fRaTBcAfIoXv0FYU45uk+BhAt1dXTpOHNyTjFPf4A1ZkANVkE4WEK7KFMsA9MgJGEwoEAZOG"
    "hCszB8QAooezhYdABQdj5cUEjUdjmfkAkUcjDPQAMwfKACME9wD9GYlH4/E39dKFE4WEK/0y"
    "DdkVv4UHAyeANAPG9/8xj6OP5/4DJ8A0E2cHECMm4DStt4PFBwEDRfcAhQeFBumNo4e3AIPF"
    "9v8DRfcA6Y2jj7b+Qb/VZ4OnByCFRQhBvYtjjrcACUdjjOcAIwAFAKMABQAjAQUAowEFACm2"
    "gUXvABALCbY3NG4BqUQTBPRfAyfADYMniQGBx+/Q1gTNvyMkkAaDJ8ANmY/jdfT+twcACpOH"
    "BxEjJPAGAaAEQYFGCUaTBQAIEwVhAANKVACDSWQAg0p0AO8AMAuDV2EAEwYABFVpk5YHAd2O"
    "t0dQUJOHhxW9jrKFEwXJIu8A8AgDJ0A0g1dhABN6GgBjD/cKYxsKAAFEQUaBRSaFbTgjgoQA"
    "QUSdtBOUiQAzZFQBfdDVaSYEAUlVahMLACCTiYlL/VqBRsqFWoYTBYor7wDwAxMJCSD9VhMH"
    "iitQQxxDEwcHCPGPAyaH+PGPAybH+PGPAyYH+fGPAyZH+fGPAyaH+fGPAybH+fGPAyYH+vGP"
    "AyZH+vGPAyaH+vGPAybH+vGPAyYH+/GPAyZH+/GPAyaH+/GPAybH+/GP/Y7jYDf745tW9eMQ"
    "ifgFRLm3kwbJIgFGAUmRRdhCiEITB/cfE3cH4JNXhwCTh/cgk/cH4CqXupdjc/kAPokFBsEG"
    "4x22/IVnVWT9Fz6ZEwUEJ/13gUYTBrADkwVACDN5+QDvAAB3g0cUJwNHBCcTBAQnogfZjzVn"
    "Ewe3DGOc5wABR4FHkwagAwUHYxzXBJP39w+Zw6MBBAADRzQArUdj9OcAowH0ANVpEwYAQIFG"
    "gUUThYkr7wDgcYln4QcjIvA0A0g0AAFGFUWTCPA/E4OJK2NzBgUzB6YCgUYil4NFhwA9oDMG"
    "5AADRhYAspdxv4NHdwADTmcAogez58cBk4cHELaXY+X4AJqXI4AHAIUG4+C2/gUGdb8ThYkr"
    "kwUAQO/wb+qDK0A0iWcFZBOLh4ATBISBkwkAQBMKACCTivf/Y+ApA0IEQUaBRSaFM2R0Ae/w"
    "D+0joCQBgMQjpmQBObWBRlKGzoUmhe8AwGZj7ToBIyJwNdKFJoXv8O/kgytANJOJCSB1vyMi"
    "gDTShSaF7/CP49KFAyRANCaFIyJgNe/wj+IDK0A06b9BRoFFToXv8C/n79CGV9VnI6KnIO/w"
    "L/CqhROFyQDv8A/tTbMTBRQA7/Cv6YNHZAADRHQA1WSiB4lqKoldjH1bk4QEIBMKACCTigrg"
    "fRTjD2S5g8eEAYWLmeNj7ioBnESBRlKGyoVOhYKXUoXv0KZREwkJIOG/UoaBRU6F7/DP3+23"
    "EwUUAO/wz+MDSWQAg0d0ANVkIgkzafkAg0dUAKqJk4QEIAFKmcMDqkQAfVuTCgAgfRljBmkB"
    "79AmTWMKVQGDx4QBiYvjgwey3EiClzm+g8eEAYmLmcfcRNKGVobOhSKFgpeTiQkg6bc3BP8A"
    "aYzBZJMJgA1v8E/+gycJAaKFToWCl4MnSQHRt4MpBQATBRQA7/AP24NEZACDR3QAg0pUAKIE"
    "3YyT9xoAKouBS4HH1WeDq0cgVWoBRP1cEwoKIFVsk/oqABMN8B/9FGOWlAHjDgSob/CP7YNH"
    "igGhi7nDgyeKANqF3oYTBgAgEwWMK4KXqo2TBQAgEwWMK+8AUBETCwsgY4MKAiOgqQARBJEJ"
    "432N+iKF79AGPxMEBOCDKQkAZbcBRYFN4b8jkLkBCQSJCfm/1WmThwkg3FMEQZOJCSCTChQA"
    "Y40HFpMHUAQDSlQAI4D0AJMH4ASjgPQAkwcABCOB9ACTBzAEo4H0AIOlSQIThUQA7/CPzyFG"
    "k4XJATOFxADv8G/IEUaTBYkAE4UEAe/wj8e3V1dOk4c3JNzIhUeczJMHIAUjgPQCkweQBaOA"
    "9AKTBwAFI4H0ApMHQAWjgfQCkwVAAiOORAGjjgQAI48EAKOPBAAmhe8QoDCTBUUAGUYmhe/w"
    "T8J5RoFFE4VkAO/wb8Amhe/wr8Qqi1aF7/AvxGMZqwoDx0QAg8dUAINGdAAiB12Pg0dkAKIH"
    "1Y9jG/cIg6eJAbNn+gAjrPkAI4D0AKOABAAjgQQAo4EEAANHiQCDx8kBMUS5jyOC9AADR5kA"
    "g8fZAbmPo4L0AANHqQCDx+kBuY8jg/QAA0e5AIPH+QG5j6OD9AADR4kAg8cJArmPI4T0AANH"
    "mQCDxxkCuY+jhPQAA0epAIPHKQK5jyOF9ACDxzkCA0e5ALmPo4X0AG/wr88jogkCI4AEAJ2/"
    "VoXv8A+4I6KpAsW/AycJAAVEIwD3AG/wb82CgIMnwAFBR5P3Bw9jlucAkUcjIvBwgoCJRyMi"
    "8GiCgIMnwAFBR5P3Bw9jlucAkUcjIPBwgoCJRyMg8GiCgCMkAAqJR2MN9QoNR2MB5RCFR2Md"
    "9QSRRyMg8HCDJ8Bw7ZsjJvBwpUcjIPBwgyfAcNmbIybwcMFHIy7wAIMnQHGT5ycDIyrwcIMn"
    "wHCT9/f8IybwcIMnwHGT5ycAIy7wcIMnwHCT5ycAIybwcCMosAqDJ4AKk+cXACMk8AqTB7AD"
    "Ywr2DJMHsAZjDvYMkwdW/5O3FwDVZhOHhmsjBfcAkQcjjMZqIwT3AP1XowTHACMC9wCjAvcA"
    "IwP3AKMD9wCCgCMgoGiDJ8BoDUb1myMm8GiTBwACIy7wAIMnQGmT58cBIyrwaIMnwGjdmyMm"
    "8GiDJ8Bpk+dHACMu8GiDJ8Bok+dHACMm8GiRRyMk8AqFtyMg8GiDJ8Bo9ZsjJvBokwcAAiMu"
    "8ACDJ0Bpk+fHASMq8GiDJ8BozZsjJvBogyfAaZPnBwEjLvBogyfAaJPnBwEjJvBoKb+DJ4AK"
    "k+eHACMk8AqFRw2/gyeACpPnxwDFv5MH8A8jJvAKgyeAChOX9wDjXAf+AyXAChN19Q+CgCMm"
    "oAqDJ4AKE5f3AONcB/6CgAEAAQABAAEAAQCCgGERIsAqhEGBE3X1DwbC0T8TVYQAE3X1D+k3"
    "E3X0D5JAAkQhAX2/cREGwMU7gyeAChlFvZsjJPAKbTf9O4JAEQFVv2ERBsLRO4MngAoVRb2b"
    "IyTwCnk3gyeACpPnBwEjJPAKST+FNyrAwTtpN5JAAkUhAYKAYREiwAbCEwRABiKF7/BviX0/"
    "BYl9+ZJAAkQhAYKAUREiwibAKoQGxK6EtTuDJ4AKEwXwCb2bIyTwChU/jeiDJ4AKk+cHASMk"
    "8AodPxE3IwCkAP09owCkAOU9IwGkAM09owGkAKJAEkSCRDEBobsBRf09/RThtzERKsYuxDLC"
    "NsAGyBUzokXVZ5OHh2sT1wUBo4DnABPXhQAjgecAo4G3AAMngAoyRRJGPZsjJOAKIy7wCoPH"
    "hwCCRiMs8AqDJ4AKE5f3AONcB/6DJ4AKk+cHESMk8AqDJ8A0k+dXACMm8DTBZyMk0DT9FyMi"
    "8DSDJ4AKk+cHMSMk8AojLqAKIyzACoMngAoTl/cA41wH/oMnwDTpmyMm8DSDJ4AKk/f3zyMk"
    "8ApNMcJAAyVANFEBgoAhESrGLsQywjbABsqlPa0xokWJRyMI8QCT1wUBowjxAJPXhQAjCfEA"
    "owmxAIMngAoyRRJGvZsjJPAKgkYcCCMu8AqRRyMs8AqDJ4AKE5f3AONcB/6DJ8A0k+dHACMm"
    "8DQjJNA0gyeACpPnBxAjJPAKIy6gCiMswAqDJ4AKE5f3AONcB/6DJ8A00kBhAe2bIybwNIMn"
    "gAqT9/fvIyTwCuW+EwHB/SLOTshSxlbEWsJewAbQJsxKyi6KMoQ2i6qJkwoAEJML8B8J7IJQ"
    "ckTiRFJJwkkySqJKEkuCSxMBQQKCgCKJY/OKAFaJIyRgNc6E1TODJoA00oUmhUqGGTfKlLOH"
    "NEFKmuP1+/4TBATgpollv1ERBsQuwCrCtTO5PoMngAoSRb2bIyTwChN19Q8RO4JFLoU1M6JA"
    "MQG5vlERBsQiwiqEHT6DJ4AKPUW9myMk8ArFORN19A/tMYMngAqT5wcBIyTwCv0xdTkqwDU2"
    "3TGiQAJFEkQxAYKAUREGxC7AIsIqhN08gyeACn1FvZsjJPAKRTkTdfQPbTGCRRP19Q9NMd08"
    "okASRDEBbbFhESLABsITBAAMIoVRNwWJbf2SQAJEIQGCgGERBsIiwCqEdTSDJ4AKTUW9myMk"
    "8AqdMSKFQTFdPJJAAkQhAcm3YREGwiLAKoRZNIMngApBRb2bIyTwCoExIoWpOZJAAkQhAWm0"
    "IREiyCqEE9W1ADLENsIuwAbKVTeZPIJFjUcjBvEAk/X1f5PXhQCjBvEAIwexAKMHAQCDJ4AK"
    "IkaSRr2bIyTwCnwAIy7wCpFHIyzwCoMngAoTl/cA41wH/oMngAqT5wcRIyTwCoMnwDST51cA"
    "IybwNMFnIyTQNP0XIyLwNCMugAojLMAKgyeAChOX9wDjXAf+gyfANOmbIybwNIMngAqT9/fv"
    "IyTwCu/wP57SQAMlQDRCRGEBgoARETLENsIuwAbMIsomyBPUtQCqhMk9IoXlNV027/CfmYJF"
    "iUcjBvEAk/X1f5PXhQCjBvEAIwexAIMngAoiRpJGvZsjJPAKfAAjLvAKjUcjLPAKgyeAChOX"
    "9wDjXAf+gyfANJPnRwAjJvA0IyTQNIMngAqT5wcQIyTwCiMukAojLMAKgyeAChOX9wDjXAf+"
    "gyfANO2bIybwNIMngAqT9/fvIyTwCu/w/5IihbU94kBSRMJEcQGCgFERBsQuwCrCJTUBNu/w"
    "P4+DJ4AKEkW9myMk8AoTdfUPXTSCRRPVtQB1PKJAMQFv8P+O0WaqlX1Xk4YGCGMUtQA6hYKA"
    "g0cFAAUFuY+T9/cPige2l5xDIYM9j823tydFZ5OHFzAcxbe3ze+Th5e4XMW357qYk4fnzxzJ"
    "t1cyEJOHZ0cjIAUAIyIFAFzJgoCuhzOHt0BjY8cAgoCYQxEFkQcjDuX+A6fH/yGDow7l/gPX"
    "5/8jD+X+A8f3/6MP5f7Jvy6Hswe3QGPjxwCCgINHFwCDRicAEQWiB8IG1Y+DRgcAEQfVj4NG"
    "9//iBtWPIy71/sm/EwHB7SMugRAjLJEQKoREQSMqIREjKDERAymFAIMpxQAjJkEREwYABAMq"
    "BQAKhSMgERIjJFERIyJhESMgcRFBP4JKt6dq15OHh0fWlzOHRwGzRzkB5Y+ST7PHNwG6lze2"
    "x+gTl3cAEwZmdeWDfpbZj6aXswU2ATPGJAF9jiJFM0YmAS6Wt3YgJBMXxgCThrYNUYKqllmO"
    "PpYzhyYBs8b0APGOpY66lrJDE9f2AMYG2Y43173BEwfn7h6XspazBZcAM8fHAHWPPY8ul8JI"
    "k1WnAFoHTY+3FXz1k4X1+saVNpe+lbNH1gD5j7GPrpdSSZOVdwDlg82Pt8WHR5OFpWLKlbqX"
    "spUzxuYAfY41ji6WYk6TFcYAUYJNjrdFMKiThTVh8pU+lraVs0b3APGOuY6ulnJKk9X2AMYG"
    "zY63lUb9k4UVUNKVspa6lTPHxwB1jz2PLpcCX5NVpwBaB02Pt6WAaZOFhY36lTaXvpWzR9YA"
    "+Y+xj66Xk5V3AOWDzY+SVTf4RIsTCPh6Lpi6lzKYM8bmAH2ONY5ClqJSExjGAFGCM2YGAVl4"
    "EwgYuxaYPpY2mLNG9wDxjrmOwpYT2PYAxgaz5gYBMlg301yJEwPje0KTspY6kzPHxwB1jz2P"
    "GpfCVBNTpwBaBzNnZwA3E5BrEwMjEiaTNpc+k7NH1gD5j7GPmpcTk3cA5YOz52cAUlO3fpj9"
    "k44+GZqeupeynjPG5gB9jjWOdpbiWZMexgBRgjNm1gG3Tnmmk47uOM6ePpa2nrNG9wDxjrmO"
    "9paT3vYAxgaz5tYB8l43G7RJEwsbgnabspY6mzPHxwB1jz2PWpcTW6cAWgczZ2cBNyse9hML"
    "K1Y2l36bPpuzx+YA8Y+1j9qXE5tXAO2Ds+dnATe7QMATCws0updymzKbM0b3AHWOOY5alhMb"
    "lgBdgjNmZgE3a14mEwsbpT6WQps2m7PGxwD5jr2O2pYTm+YAyYKz5mYBN8u26RMLq3qyllab"
    "OpszR9YAfY8xj1qXE1vHAFIHM2dnATcbL9YTC9sFNpdKmz6bs8fmAPGPtY/alxObVwDtg7Pn"
    "ZwE3G0QCEws7RbqXFpsymzNG9wB1jjmOWpYTG5YAXYIzZmYBN+uh2BMLG2g+lnabNpuzxscA"
    "+Y69jtqWE5vmAMmCs+ZmATcL1OcTC4u8spZGmzqbM0fWAH2PMY9alxNbxwBSBzNnZwE32+Eh"
    "Ewtr3jaXLps+m7PH5gDxj7WP2pcTm1cA7YOz52cBNws3wxMLa326l06bMpszRvcAdY45jlqW"
    "ExuWAF2CM2ZmATcb1fQTC3vYPpYemzabs8bHAPmOvY7alhOb5gDJgrPmZgE3G1pFEwvbTrKW"
    "eps6mzNH1gB9jzGPWpcTW8cAUgczZ2cBN/vjqRMLW5A2lxqbPpuzx+YA8Y+1j9qXE5tXAO2D"
    "s+dnATer7/wTC4s/upcqmzKbM0b3AHWOOY5alhMblgBdgjNmZgE3C29nEwubLT6WUps2m7PG"
    "xwD5jr2O2pYTm+YAyYKz5mYBt1sqjbKWk4uryDNL1gCmm7qbM/dnATGPXpeTW8cAUgczZ3cB"
    "t0v6/5OLK5Q2l8qbvpuzR2cB3pcTm0cA8YOz52cBN/txhxMLG2h6m7qXMpszxuYAPY5alhMb"
    "tgBVgjNmZgE3a51tEwsrEkKbPpY2m7NG9wCxjtqWE5sGAcGCs+ZmATdL5f0TC8uATpuyllqX"
    "M8vHADNL2wA6mxNXmwBeCzNr6wA3976kEwdHpH6XNps+l7NH1gCzx2cBupcTl0cA8YPZjzfX"
    "3ksTB5f6RpfalzKXM8ZmAT2OOpYTF7YAVYJZjjdXu/YTBwe2Upc+lrqWM0f7ADGPNpeTFgcB"
    "QYNVj7fGv76ThgbHlpYylzabs8bHALmO2pYT25YA3gaz5mYBN4ubKBMLa+wam7qW2pczS+YA"
    "M0vbAD6bkxdLABNbywEza/sAtyeh6pOHp3/WlzabPpazR9cAs8dnAbKXE5a3ANWD0Y83Nu/U"
    "EwZWCB6W2pc6ljPHZgE9jzKXExYHAUGDUY83JogEEwZW0HKWPpc2lrNG+wC5jrKWE9aWAN4G"
    "0Y431tTZEwaWAy6WupZaljPL5wAzS9sAMpsTFksAE1vLATNrywA3ptvmEwZWniaWNpuylzNG"
    "1wAzRmYBPpaTF7YAVYJdjreHoh+Th4fP9pdalrqXM8dmATGPPpeTFwcBQYNdj7dXrMSTh1dm"
    "qpcyl7aXs0bLALmOvpaT15YA3gbdjrcnKfSTh0ckvpq6llabk0r2/7Pq2gCzyuoA2pqTl2oA"
    "k9qqAbPq+gC3BytDk4d3+dKXtpo+lpNH9/+z51cBtY+ylxOWpwDZg9GPNyaUqxMGdjqymdaX"
    "upkTx/b/XY8zR1cBTpcTFvcARYNRjzemk/wTBpYDMpk+lzaZk8b6/9mOvY7KlhPWtgDWBtGO"
    "N2ZbZRMGNpyylLqWppqTxPf/1Yy5jNaUE5ZkAOmA0Yw31gyPEwYmybKTtpSel5ND9/+z45MA"
    "s8PTAL6TN/bv/5OXowATBtZHk9NjAbPj8wAWlqaTMpcTxvb/M2Z2ACWOOpaTF/YARYJdjrdn"
    "hIWThxfdvp8elrafk8b0/9GOs8Z2AP6Wk9e2ANYG3Y63h6hvk4f35D6fspb6lBPP8/8zb98A"
    "M0/PACafkxdvABNfrwEzb/8At+cs/pOHB272lzafvpOTR/b/s+fnAbWPnpcTl6cA2YPZjzdH"
    "AaMTB0cxcpf6lzqWE8f2/12PM0fnATKXExb3AEWDUY83FghOEwYWGjKTPpealhND//8zY+MA"
    "M0PzADaTk1azAFYDM2PTALeGU/eThibotpg6k0afk8j3/7PoaACzyOgA+piTlmgAk9ioAbPo"
    "2AC39jq9k4ZWIzaYmpg+mJNH9/+z5xcBs8dnAMKXk5anANmD1Y+31tcqk4a2KzaVxpc6lRNH"
    "8/9djzNHFwEql5MW9wBFg1WPt9aG65OGFjm2lRRAPpealRPD+P8zY+MAM0PzALaYVEAukxNW"
    "swBWA7qWM2NmADaTFESDIAESIyAUATaXGMRYRCMiZACDJIERupdcxAMpQREDJMERgykBEQMq"
    "wRCDKoEQAytBEIMrARATAUESgoAcQRERExc2ACLKJshKxk7EKoQGzBPVNwBSwlbAupcTdfUD"
    "kwQABBzArokyiYmMY/XnAFxAhQdcwFhAk1fZAbqXXMBja5kEYQXOhSaGIpXv4C+dkwqEAJMF"
    "hAFWhe/wr+gTigQEY3NJAwFFYQUzBplAs4WZACKV4kBSRMJEMkmiSRJKgkpxAW/gr5mzhZkA"
    "VoXv8G/l0oTpt4FEwb8QQSERIsgNgibGSsQGyhN29gOTB3ADqoQuiRMEgANj9McAEwSABxGM"
    "poUKhSFG7/CP29FlIoYmhZOFBUglN4qFJoUhRgU3k4WEAEqFQUbv8I/Z0kBCRLJEIklhAYKA"
    "QREixFVkJsKqhBMFRGwGxi7A7/BP1AJGpoUTBURs5TXVZBMFRGyThcRxpT+yQBOFxHEiRJJE"
    "QQGCgAAAAACWMAd3LGEO7rpRCZkZxG0Hj/RqcDWlY+mjlWSeMojbDqS43Hke6dXgiNnSlytM"
    "tgm9fLF+By2455Edv5BkELcd8iCwakhxufPeQb6EfdTaGuvk3W1RtdT0x4XTg1aYbBPAqGtk"
    "evli/ezJZYpPXAEU2WwGY2M9D/r1DQiNyCBuO14QaUzkQWDVcnFnotHkAzxH1ARL/YUN0mu1"
    "CqX6qLU1bJiyQtbJu9tA+bys42zYMnVc30XPDdbcWT3Rq6ww2SY6AN5RgFHXyBZh0L+19LQh"
    "I8SzVpmVus8Ppb24nrgCKAiIBV+y2QzGJOkLsYd8by8RTGhYqx1hwT0tZraQQdx2BnHbAbwg"
    "0pgqENXviYWxcR+1tgal5L+fM9S46KLJB3g0+QAPjqgJlhiYDuG7DWp/LT1tCJdsZJEBXGPm"
    "9FFra2JhbBzYMGWFTgBi8u2VBmx7pQEbwfQIglfED/XG2bBlUOm3Euq4vot8iLn83x3dYkkt"
    "2hXzfNOMZUzU+1hhsk3OUbU6dAC8o+Iwu9RBpd9K15XYPW3E0aT79NbTaulpQ/zZbjRGiGet"
    "0Lhg2nMtBETlHQMzX0wKqsl8Dd08cQVQqkECJxAQC76GIAzJJbVoV7OFbyAJ1Ga5n+Rhzg75"
    "3l6YydkpIpjQsLSo18cXPbNZgQ20LjtcvbetbLrAIIO47bazv5oM4rYDmtKxdDlH1eqvd9Kd"
    "FSbbBIMW3HMSC2PjhDtklD5qbQ2oWmp6C88O5J3/CZMnrgAKsZ4HfUSTD/DSowiHaPIBHv7C"
    "BmldV2L3y2dlgHE2bBnnBmtudhvU/uAr04laetoQzErdZ2/fufn5776OQ763F9WOsGDoo9bW"
    "fpPRocTC2DhS8t9P8We70WdXvKbdBrU/SzaySNorDdhMGwqv9koDNmB6BEHD72DfVd9nqO+O"
    "bjF5vmlGjLNhyxqDZryg0m8lNuJoUpV3DMwDRwu7uRYCIi8mBVW+O7rFKAu9spJatCsEarNc"
    "p//XwjHP0LWLntksHa7eW7DCZJsm8mPsnKNqdQqTbQKpBgmcPzYO64VnB3ITVwAFgkq/lRR6"
    "uOKuK7F7OBu2DJuO0pINvtXlt+/cfCHf2wvU0tOGQuLU8fiz3Whug9ofzRa+gVsmufbhd7Bv"
    "d0e3GOZaCIhwag//yjsGZlwLARH/nmWPaa5i+NP/a2FFz2wWeOIKoO7SDddUgwROwrMDOWEm"
    "Z6f3FmDQTUdpSdt3bj5KatGu3FrW2WYL30DwO9g3U668qcWeu95/z7JH6f+1MBzyvb2KwrrK"
    "MJOzU6ajtCQFNtC6kwbXzSlX3lS/Z9kjLnpms7hKYcQCG2hdlCtvKje+C7ShjgzDG98FWo3v"
    "Ai2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    "AAAAAAAAAAAAAAAA"
)

###############################################################################

# EPIC (AB532x / BLUEEPIC) — extracted from official Downloader v2.2.0
# during a real erase+write session.  On-wire data (already prepared).
# Do NOT patch header at load time (same as CRWN).
dl_blob_epic = b64decode(
    "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"
)



class BlCmd:
    IFACE_PARAM     = 0x50
    AUTHORIZE       = 0x55
    MEM_WRITE       = 0x57
    SET_CMD_HANDLER = 0x58
    GET_INFO        = 0x5A
    REBOOT          = 0x5E


class NitDlCmd:
    """Command codes for PRAO (AB560x) blob."""
    INIT      = 0x00
    DEV_READ  = 0x01
    DEV_WRITE = 0x02
    DEV_ERASE = 0x03


class NitDlCmdCRWN:
    """Command codes for CRWN (AB530x) blob (uartdown-001.dll)."""
    INIT      = 0x00   # returns 4 bytes: [status, ?, density, ?]
    DEV_READ  = 0x28   # arg1=addr  arg3=n_blocks(x512B)  recv=n_blocks*512
    DEV_WRITE = 0x2a   # arg1=addr  arg3=1  send=512B  recv=1  (one block)
    DEV_ERASE = 0x2f   # arg1=addr  arg3=1  recv=512   (one 64K block)
    GET_INFO  = 0x25   # recv=16 → bytes [0xC..0xE] = JEDEC device ID


def make_cb(cmd, arg1=0, arg2=0, arg3=0):
    return struct.pack('>BIBH', cmd, arg1, arg2, arg3)


def patch_crwn_blob(blob_bytes):
    """
    Fix six bugs in the CRWN blob (uartdown-001.dll).

    Each patch replaces one wrong instruction with the correct one.
    All offsets are relative to the start of the decrypted blob binary.

    P3  READ  0x0B3C  BLTU x21,x18      → NOP
        Removes a hardcoded address ceiling (0x1E00) that caused the READ
        loop to return zeros for any flash address >= 0x1E00.

    P4a INIT  0x04D6  addi a5,a5,-0x1C4 → addi a5,a5,+0x2D6
        In the flash-detection fallback path, INIT stores a do-nothing stub
        at all iface function slots. This patch redirects the iface[0x10]
        store to the real flash_erase function (VA 0x132D6).

    P4b INIT  0x04EA  addi a5,a5,-0x1C4 → addi a5,a5,+0x266
        Same fallback path — redirects the iface[0x0C] store to the real
        flash_write function (VA 0x13266).

    P5  ERASE 0x0C40  c.beqz a5,+70    → c.nop
        A flag check (iface[0x18].bit3) gates the DMA erase path. The flag
        is never set on this chip variant so the handler always branched to
        a no-op. This patch makes it always take the DMA path.

    P6  ERASE 0x0C42  lw a5,8(s4)      → lw a5,0x10(s4)
        The handler was loading iface[8] (flash_read pointer) into a5 and
        calling it as if it were flash_erase. Changed load offset from 8 to
        0x10 so it loads iface[0x10] (flash_erase pointer) instead.

    P7  WRITE 0x0BB8  c.beqz a5,+14    → c.nop
        A bit1 guard inside the per-block success path skipped the
        flash_write call when the flag was clear. The flag is never set by
        INIT on this chip. Removed so flash_write is always called.

    P8  ERASE 0x0C4E  addi a0,s8,0x2B8 → addi a0,zero,0xD8
        The handler passed a buffer pointer as a0 to flash_erase. The low
        byte (0xB8) is not a valid SPI opcode so the flash ignored every
        erase command. Replaced with the correct 64K block-erase opcode
        (0xD8).
    """
    b = bytearray(blob_bytes)

    def patch(off, expected, replacement, name):
        exp = bytes(expected)
        assert b[off:off+len(exp)] == exp, (
            f'CRWN blob {name}: expected {exp.hex()} at {off:#06x}, '
            f'got {b[off:off+len(exp)].hex()}')
        b[off:off+len(replacement)] = bytes(replacement)

    patch(0x0b3c, [0x63,0xee,0x2a,0x01], [0x13,0x00,0x00,0x00], 'P3')
    patch(0x04d6, [0x93,0x87,0xc7,0xe3], [0x93,0x87,0x67,0x2d], 'P4a')
    patch(0x04ea, [0x93,0x87,0xc7,0xe3], [0x93,0x87,0x67,0x26], 'P4b')
    patch(0x0c40, [0xb9,0xc3],           [0x01,0x00],           'P5')
    patch(0x0c42, [0x83,0x27,0x8a,0x00], [0x83,0x27,0x0a,0x01], 'P6')
    patch(0x0bb8, [0x99,0xc7],           [0x01,0x00],           'P7')
    patch(0x0c4e, [0x13,0x05,0x8c,0x2b], [0x13,0x05,0x80,0x0d], 'P8')

    return bytes(b)


def do_the_stuff(execcmd, blocksize, iface):
    # -------------------------------------------------------------------------
    # Bootloader handshake
    # -------------------------------------------------------------------------
    resp = execcmd(make_cb(BlCmd.GET_INFO, arg1=0x5259414E, arg3=0x67ca), recv=24)
    chipid, loadaddr, commskey, _ = struct.unpack('>12sIII', resp)
    print(f' Chip ID:       {chipid}')
    print(f' Load address:  ${loadaddr:08X}')
    print(f' Init. commkey: ${commskey:08X}')

    resp = execcmd(make_cb(BlCmd.AUTHORIZE, arg1=ab_calckey(commskey)), recv=4)
    commskey, = struct.unpack('>I', resp)
    print(f' New commkey:   ${commskey:08X}')

    if iface == 'uart' and args.baud != args.init_baud:
        print(f'Changing baudrate to {args.baud} baud...')
        execcmd(make_cb(BlCmd.IFACE_PARAM, arg2=0xf0), recv=2)
        execcmd(make_cb(BlCmd.IFACE_PARAM, arg1=args.baud, arg2=0x02),
                recv=2, switch_baud=args.baud)

    # -------------------------------------------------------------------------
    # Load and patch blob
    # -------------------------------------------------------------------------
    is_crwn = b'CRWN' in chipid
    is_epic = b'EPIC' in chipid

    if is_crwn:
        Cmd       = NitDlCmdCRWN
        blob_data = patch_crwn_blob(dl_blob_crwn)
        print('[*] Using CRWN agent blob')
    elif is_epic:
        # BIOS: EPIC works with CRWN blob
        Cmd       = NitDlCmdCRWN
        blob_data = patch_crwn_blob(dl_blob_crwn)
        print(f'[*] Using CRWN agent blob for EPIC ({len(blob_data)} bytes)')
    else:
        Cmd       = NitDlCmd
        blob_data = dl_blob
        print('[*] Using PRAO agent blob')

    data = bytearray(blob_data) + b'\x00' * align_by(len(blob_data), blocksize)
    # PRAO template needs header patch; CRWN/EPIC blobs are pre-built
    if not is_crwn and not is_epic:
        struct.pack_into('<12s4sI', data, 4, chipid, iface.encode(), blocksize)

    execcmd(make_cb(BlCmd.MEM_WRITE, arg1=loadaddr,
                    arg3=(len(data) // blocksize)), send=data)
    execcmd(make_cb(BlCmd.SET_CMD_HANDLER, arg1=loadaddr))

    # -------------------------------------------------------------------------
    # Blob init — detect flash, print chip info
    # -------------------------------------------------------------------------
    if is_crwn or is_epic:
        init_resp = execcmd(make_cb(Cmd.INIT, arg1=0), recv=4)
        density   = init_resp[2] or init_resp[1]
        fsize     = (1 << (density + 6)) if density else None

        # Codekey is derived by INIT from flash[0x1FC..0x1FD] using:
        #   val     = uint16_LE( flash[0x1FC] )
        #   codekey = (~val & 0xFFFF) ^ (val << 16) ^ 0x594B5048
        # This mirrors what the chip writes to SFR 0x348 (flash DMA key).
        raw_ck  = execcmd(make_cb(Cmd.DEV_READ, arg1=0x1fc, arg3=1), recv=512)
        val     = struct.unpack_from('<H', raw_ck, 0)[0]
        codekey = ((~val & 0xffff) ^ (val << 16) ^ 0x594b5048) & 0xffffffff

        # CMD 0x25 reads the JEDEC device ID via ROM 0x80064 (opcode 0x9F).
        # The blob returns it big-endian in response bytes [0xC..0xF].
        # Bytes [0x0..0x7] of the response buffer may contain the 64-bit Unique
        # ID (opcode 0x4B) if the AB530x ROM fills it — this depends on the ROM
        # revision.  We extract it opportunistically and skip if all-zero.
        try:
            resp25   = execcmd(make_cb(Cmd.GET_INFO), recv=16)
            flash_id = struct.unpack_from('>I', resp25, 0xc)[0] & 0xffffff
            flash_uid_raw = resp25[0:8]
            flash_uid = flash_uid_raw if any(b != 0 for b in flash_uid_raw) else None
        except Exception:
            flash_id  = 0
            flash_uid = None

        print(f'- Code key: >>>> {codekey:08X} <<<<')
        if flash_id:
            print(f'- Flash device ID: {flash_id:06X}')
        if flash_uid:
            print(f'- Flash unique ID: {flash_uid.hex()}')
        if fsize:
            print(f'- Flash size: {fsize} bytes')
        else:
            print('- Flash size: unknown')

    else:
        codekey, flashid, flashuid = struct.unpack(
            'II16s', execcmd(make_cb(Cmd.INIT), recv=48))
        density = flashid & 0xff
        fsize   = (1 << density) if 0x10 <= density <= 0x18 else None

        print(f'- Code key: >>>> {codekey:08X} <<<<')
        print(f'- Flash device ID: {flashid:06X}')
        print(f'- Flash unique ID: {flashuid.hex()}')
        if fsize:
            print(f'- Flash size: {fsize} bytes')
        else:
            print('- Flash size: unknown')

    # -------------------------------------------------------------------------
    # Flash operations
    # -------------------------------------------------------------------------

    def do_dev_erase(addr, size):
        """Erase a flash region, choosing 64K or 4K blocks automatically."""
        saddr = addr & ~0xFFF
        eaddr = (addr + size + 0xFFF) & ~0xFFF

        tq = tqdm(desc='Erasing', total=(eaddr - saddr),
                  unit='B', unit_divisor=1024, unit_scale=True)
        try:
            cur = saddr
            while cur < eaddr:
                aligned_64k = (cur & 0xFFFF) == 0
                space_64k   = (eaddr - cur) >= 0x10000

                if is_crwn or is_epic:
                    # CMD 0x2F / arg3=1: blob sends one SPI erase command (0xD8,
                    # 64K block erase) via flash_erase and returns immediately —
                    # without polling BUSY.  We then issue a DEV_READ (0x28) for
                    # one 512-byte block: the ROM flash_read driver waits for the
                    # flash BUSY bit (WIP, STATUS[0]) to clear before asserting
                    # CS and sending the SPI READ command, so this dummy read
                    # naturally blocks until the erase is complete.
                    execcmd(make_cb(Cmd.DEV_ERASE, arg1=cur,
                                    arg2=0x01, arg3=1), recv=512)
                    execcmd(make_cb(Cmd.DEV_READ, arg1=0, arg3=1), recv=512)
                    cur += 0x10000
                    tq.update(0x10000)
                else:
                    if aligned_64k and space_64k:
                        blksize, flags = 0x10000, 0x00
                    else:
                        blksize, flags = 0x1000, 0x02
                    execcmd(make_cb(Cmd.DEV_ERASE, arg1=cur, arg2=flags))
                    cur += blksize
                    tq.update(blksize)
        finally:
            tq.close()

    # ------------------------------------------------------------------

    try:
        if args.action == 'erase':
            for i in range(0, len(args.areas), 2):
                addr = int(args.areas[i],     0)
                size = int(args.areas[i + 1], 0)
                if size <= 0:
                    if fsize is None: raise RuntimeError('Unknown flash size')
                    size = fsize - addr
                do_dev_erase(addr, size)

        elif args.action == 'read':
            for i in range(0, len(args.areas), 3):
                addr = int(args.areas[i],     0)
                size = int(args.areas[i + 1], 0)
                path = args.areas[i + 2]
                if size <= 0:
                    if fsize is None: raise RuntimeError('Unknown flash size')
                    size = fsize - addr

                io_size = min(0x8000, max(blocksize,
                                          align_to(size // 100, blocksize)))
                print(f'Reading {size} bytes from @{addr:06X} into "{path}"...')

                tq = tqdm(desc='Reading', total=size,
                          unit='B', unit_divisor=1024, unit_scale=True)
                try:
                    with open(path, 'wb') as f:
                        done = 0
                        while done < size:
                            n     = min(io_size, size - done)
                            _a3   = (n // 512) if (is_crwn or is_epic) else n
                            f.write(execcmd(
                                make_cb(Cmd.DEV_READ, arg1=addr + done, arg3=_a3),
                                recv=n))
                            tq.update(n)
                            done += n
                finally:
                    tq.close()

        elif args.action == 'write':
            for i in range(0, len(args.areas), 2):
                addr = int(args.areas[i], 0)
                path = args.areas[i + 1]

                with open(path, 'rb') as f:
                    raw = f.read()

                BLOCK = 512
                if len(raw) % BLOCK:
                    raw += b'\xff' * (BLOCK - len(raw) % BLOCK)
                n_blocks = len(raw) // BLOCK

                print(f'Writing {len(raw)} bytes to @{addr:06X} from "{path}"...')
                do_dev_erase(addr, len(raw))

                if is_crwn or is_epic:
                    # CMD 0x2A — CRWN write, one 512-byte block per call.
                    #
                    # Flow for each block:
                    #   send_packet(CB)   — BL ACKs immediately, then calls blob.
                    #                       Blob enters ROM recv() and waits for
                    #                       the data packet.
                    #   send_packet(data) — BL's UART ISR independently receives
                    #                       this framed 512-byte packet, ACKs it,
                    #                       and stores the payload in the buffer
                    #                       pointed to by s0 (= iface[4], the CB
                    #                       pointer set at blob entry).  ROM recv()
                    #                       reads from that same buffer and returns
                    #                       512.
                    #   recv=1            — drains the 1-byte blob response, keeps
                    #                       BL's TX queue empty and the packet
                    #                       counter in sync for the next block.
                    #
                    # arg2=0x00 keeps a3=0 inside the blob so flash_write programs
                    # flash with DMA key=0 (no encryption).  arg2=0x02 would load
                    # iface[4] as the DMA key and silently corrupt every byte.
                    tq = tqdm(desc='Writing', total=len(raw),
                              unit='B', unit_divisor=1024, unit_scale=True)
                    try:
                        for blk in range(n_blocks):
                            cb = make_cb(Cmd.DEV_WRITE,
                                         arg1=addr + blk * BLOCK,
                                         arg2=0x00, arg3=1)
                            execcmd(cb,
                                    send=raw[blk * BLOCK : (blk + 1) * BLOCK],
                                    recv=1)
                            tq.update(BLOCK)
                    finally:
                        tq.close()

                else:
                    io_size = min(0x8000, max(blocksize,
                                              align_to(len(raw) // 100, blocksize)))
                    tq = tqdm(desc='Writing', total=len(raw),
                              unit='B', unit_divisor=1024, unit_scale=True)
                    try:
                        done = 0
                        while done < len(raw):
                            block = raw[done:done + io_size]
                            execcmd(make_cb(Cmd.DEV_WRITE,
                                            arg1=addr + done, arg3=len(block)),
                                    send=block)
                            tq.update(len(block))
                            done += len(block)
                    finally:
                        tq.close()

    except KeyboardInterrupt:
        print('interrupted!')
    except Exception as e:
        print('failed:', e)
        raise

    if args.reboot:
        execcmd(make_cb(BlCmd.REBOOT))

###############################################################################

if have_uart and args.port is not None:
    with Serial(args.port) as port:
        # Detect whether the adapter has a TX→RX loopback (e.g. CH340 with
        # 200 Ω resistor) or not (e.g. Bluetrum's original half-duplex dongle).
        #
        # Half-duplex dongle (no loopback):
        #   port_write sends bytes to chip, chip replies → those replies are
        #   read back as the "echo".  has_echo=True is correct.
        #
        # Full-duplex adapter + resistor (loopback present):
        #   Our own TX bytes come straight back on RX before the chip replies.
        #   port_write must NOT try to consume them; the real chip reply is
        #   read later by send_packet/_recv_token_packet.  has_echo=False.
        #
        # Detection: send one byte at low baud with a short timeout.
        # If it echoes back → loopback adapter → has_echo=False.
        # If nothing comes back → half-duplex dongle → has_echo=True.
        if args.no_echo:
            has_echo = False
        else:
            # Detect TX→RX loopback.
            #
            # CH340 (standard driver + 200Ω resistor): TX bytes loop back on
            # RX immediately (within ~1 byte-time = 87µs at 115200).
            # has_echo=True so port_write consumes them before reading the
            # real chip response.
            #
            # Original Bluetrum dongle (AB5305A / CP2102 modified driver):
            # driver suppresses local echo → TX bytes do NOT return on RX.
            # has_echo=False so port_write doesn't try to read phantom bytes.
            #
            # Detection: send 0xFF (ignored by BL sync state machine) with a
            # 5 ms read timeout — long enough for loopback (arrives in <1 ms)
            # but shorter than the minimum chip processing + reply time.
            port.baudrate = args.init_baud
            port.timeout  = 0.005
            port.write(b'\xff')
            loopback  = port.read(1) == b'\xff'
            port.reset_input_buffer()
            port.timeout  = 0.01
            has_echo  = loopback

        udl = UARTDownload(port, has_echo=has_echo)

        print('Trying to synchronize.', end='')
        port.timeout = .01

        try:
            done = False
            num  = 0
            turn = 0
            while not done:
                if num < 10:
                    udl.port.reset_input_buffer()
                    udl.port.write(UARTDownload.SYNC_TOKEN)
                    while not done:
                        recv = udl.port.read(4)
                        if recv == b'': break
                        if recv == UARTDownload.SYNC_RESP:
                            done = True
                    num += 1
                else:
                    print('.', end='', flush=True)
                    if turn == 0:
                        udl.port.baudrate = args.init_baud
                        udl.send_reset(True)
                        turn = 1
                    else:
                        udl.port.baudrate = args.baud
                        udl.send_reset(True)
                        udl.port.baudrate = args.init_baud
                        turn = 0
                    num = 0
        except Exception as e:
            print(' failed:')
            raise e
        else:
            print(' done.')

        port.reset_input_buffer()
        port.timeout = .1

        def execcmd(cb, send=None, recv=None, max_io=512, switch_baud=None):
            udl.send_packet(cb)

            if switch_baud is not None:
                port.baudrate = switch_baud

            if send is not None:
                sent = 0
                while sent < len(send):
                    n = min(len(send) - sent, max_io)
                    udl.send_packet(send[sent:sent + n])
                    sent += n

            elif recv is not None:
                data = b''
                while len(data) < recv:
                    n     = min(recv - len(data), max_io)
                    block = udl.recv_packet()
                    data += block
                    if len(block) != n:
                        break
                return data

        do_the_stuff(execcmd, 512, 'uart')

elif have_scsi and args.mscdev is not None:
    with SCSIDev(args.mscdev) as dev:
        def execcmd(cb, send=None, recv=None, **_):
            if recv is not None:
                recv = bytearray(recv)
            if send is not None and not isinstance(send, bytes):
                send = bytes(send)
            dev.execute(b'\xfc' + cb, send, recv)
            return recv

        do_the_stuff(execcmd, 512, 'usb')

else:
    print('No device specified:')
    if have_uart: print(' - UART: specify the serial port via --port')
    if have_scsi: print(' - USB MSC: specify the device via --mscdev')

Скрипт для разделения областей которые бьются конкретно у меня:

Код:
r=open('dump.bin','rb').read()
open('fix_10000.bin','wb').write(r[0x10000:0x20000])
open('fix_30000.bin','wb').write(r[0x30000:0x40000])
open('fix_50000.bin','wb').write(r[0x50000:0x60000])
print('ok')

Ну и пишем их в чип:

Код:
download.py --port COM8 write 0x10000 fix_10000.bin
download.py --port COM8 write 0x30000 fix_30000.bin
download.py --port COM8 write 0x50000 fix_50000.bin

159

Givanich написал(а):

Так что при записи лучше перепроверять и перезаписывать битые области.

Или не та ветка в блобе, или не та последоватоельность. Ну и скорость. И флаги.

Подпись автора

USMicro® 2026©

160

А, кажись догнал - не та ветка в блобе, без ACK/NACK части, снизить скорость уарта - должно не пропускать.

Подпись автора

USMicro® 2026©


Вы здесь » USMI » MCU, SoC, CPU Микроконтроллеры » Bluetrum SoC