Запретить чтение памяти МК можно из кода программы, но для повторного программирования придется снять запрет. И все бы ничего, но под Linux, для микроконтроллеров WCH, нет решения «из коробки» для разблокировки памяти. Для преодоления этого неудобства появилось решение — расширить возможности скрипта OpenOCD для работы с МК.
Поиск в документации на МК по ключевому слову «protection» привел в раздел 32.6.4 Read Protection Release, посвященный снятию запрета на считывание памяти. В нем сказано, что необходимо сначала очистить область User Option Bytes, а затем записать в поле RDPR значение 0xA5. Чуть больше про это поле написано в описании регистров области User Option Bytes (рисунок 1).
Получается, нужно уметь очищать область User Option Bytes и записывать в него конкретное значение. Алгоритм очистки представлен в параграфе 32.6.3 User Option Bytes Erasure, а записи — в параграфе 32.6.2 User Option Bytes Programming. Осталось реализовать их средствами OpenOCD...
Коротко про OpenOCD
Для работы с регистрами OpenOCD предоставляет несколько функций (раздел 15.3 Memory access commands):
Чтение |
Запись |
Размер |
mdw |
mww |
32 бита |
mdh |
mwh |
16 бит |
mdb |
mwb |
8 бит |
Для чтения регистра целиком (32 бита), с сохранением в переменную, команда имеет вид:
set reg_data [ $_TARGETNAME.0 mdw $REG_ADDR ]
$_TARGETNAME.0 — конкретное целевое устройство, созданное командой target create.
При первом обращении к регистрам через OpenOCD выяснилось, что выдается не просто значение регистра, а еще и адрес регистра, и пустая строка (рисунок 2).
Возможно, это даже для чего‑то нужно, но не в моем случае. Поэтому для извлечения значения регистра написал такую функцию:
proc get_reg_value { data } {
set reg_str [string trimright $data "\n "]
lassign [split $reg_str ":"] reg out
set hex 0x[string trim $out] # превращение в hex
return $hex
}
Значение регистра возвращается в шестнадцатеричном формате, но в виде строки, поэтому о системе счисления лучше явно сообщить интерпретатору tcl. В противном случае результат будет интерпретирован как десятичное число.
Использование функций
Реализация алгоритма очистки области User Option Bytes получила название lock, а запись значения в поле RDPR — unlock:
Реализация
#
# Return hex value of register
#
proc get_reg_value { data } {
set reg_str [string trimright $data "\n " ]
lassign [split $reg_str ":"] reg out
set hex 0x[string trim $out]
return $hex
}
#
# Unlock any flash by register
#
proc _unlock_flash_by_reg { TARGET REG } {
$TARGET mww $REG 0x45670123
$TARGET mww $REG 0xCDEF89AB
}
#
# Unlock flash (FLASH_KEYR - FPEC key register)
#
proc _unlock_flash { TARGET } {
set _FLASH_KEYR_ADDR 0x40022004
_unlock_flash_by_reg $TARGET $_FLASH_KEYR_ADDR
}
#
# Unlock the User Option Bytes
#
proc _unlock_user_ob { TARGET } {
set _FLASH_OBKEYR_ADDR 0x40022008
_unlock_flash_by_reg $TARGET $_FLASH_OBKEYR_ADDR
}
set _TARGET $_TARGETNAME.0
set FLASH_CTLR_ADDR 0x40022010
set FLASH_STATR_ADDR 0x4002200C
set FLASH_OBR_ADDR 0x4002201C
set RDPR_ADDR 0x1FFFF800
# Status Register (FLASH_STATR)
set BSY_BIT_MASK [expr {1 << 0}]
set EOP_BIT_MASK [expr {1 << 5}]
# Control Register (FLASH_CTLR)
set LOCK_BIT_MASK [expr {1 << 7}]
set OBWRE_BIT_MASK [expr {1 << 9}]
# Selection word register
set RDPRT_BIT_MASK [expr {1 << 1}]
set RDPR_MASK_LOCK 0x5aa5
# ms
set TIMEOUT_STEP 10
set TIMEOUT 500
#
# Read Protection Release
# 32.6.2 User Option Bytes Programming
#
proc unlock { } {
set op "UNLOCK"
global _TARGET
global FLASH_CTLR_ADDR
global FLASH_STATR_ADDR
global FLASH_OBR_ADDR
global RDPR_ADDR
# Status Register (FLASH_STATR)
global BSY_BIT_MASK
global EOP_BIT_MASK
# Control Register (FLASH_CTLR)
set OBPG_BIT_MASK [expr {1 << 4}]
global LOCK_BIT_MASK
global OBWRE_BIT_MASK
global RDPRT_BIT_MASK
global RDPR_MASK_LOCK
# ms
global TIMEOUT_STEP
global TIMEOUT
# Read protection status.
# 0: Current read protection of flash is invalid;
set RDPRT_byte [ get_reg_value [ $_TARGET mdb $FLASH_OBR_ADDR ] ]
if { [ expr {$RDPRT_byte & $RDPRT_BIT_MASK} ] == 0 } {
echo "** \[$op\] Memory is unlocked already **"
return
}
# Erase the entire User Option Bytes area
# 1)
# Check the LOCK bit in the FLASH_CTLR register.
# If it is 1, you need to perform the "Release Flash Memory Lock" operation.
# Lock. When this bit is '1', it means that FPEC and FLASH_CTLR
# are locked and cannot be written. After detecting the correct unlock sequence, the
# hardware will clear this bit to ‘0’.
# After an unsuccessful unlock operation, this bit will not change until the next system reset.
set flash_ctrl_data [ get_reg_value [ $_TARGET mdb $FLASH_CTLR_ADDR ] ]
if { [ expr {$flash_ctrl_data & $LOCK_BIT_MASK} ] == $LOCK_BIT_MASK } {
echo "** \[$op\] FLASH_CTLR are locked. Need Release Flash Memory. Releasing ... **"
_unlock_flash $_TARGET
}
# 2)
# Check the BSY bit in the FLASH_STATR register to ensure that
# there is no programming operation in progress.
set timeout $TIMEOUT
while { 1 } {
set flash_statr_data [ get_reg_value [ $_TARGET mdb $FLASH_STATR_ADDR ] ]
if { [ expr {$flash_statr_data & $BSY_BIT_MASK} ] != $BSY_BIT_MASK } {
break
}
if {$timeout == $TIMEOUT} {
echo "** \[$op\] Flash memory operation is in the process. Waiting ... **"
} elseif {$timeout == 0} {
echo "\[$op\] !Err 1: Fail flash processing **"
return
}
incr timeout -$TIMEOUT_STEP
after $TIMEOUT_STEP
}
# 3)
# Check the OBWRE bit in the FLASH_CTLR register.
# If it is 0, you need to perform the "User Option Bytes Unlock" operation.
set flash_ctrl_data [ get_reg_value [ $_TARGET mdw $FLASH_CTLR_ADDR ] ]
if { [ expr {$flash_ctrl_data & $OBWRE_BIT_MASK} ] == 0 } {
echo "** \[$op\] User option bytes is locked **"
_unlock_user_ob $_TARGET
}
# 4)
# Set the OBPG bit in the FLASH_CTLR register to `1`.
set flash_ctrl_data [ get_reg_value [ $_TARGET mdh $FLASH_CTLR_ADDR ] ]
$_TARGET mwh $FLASH_CTLR_ADDR [expr {$flash_ctrl_data | $OBPG_BIT_MASK}]
# 5)
# Write the half word (2 bytes) to be programmed to the designated address.
# Set correct RDPR code 0xA5 to the User Option Bytes
$_TARGET mwh $RDPR_ADDR $RDPR_MASK_LOCK
# 6)
# When the BSY bit changes to '0' or the EOP bit in the FLASH_STATR register to be '1',
# it indicates the end of programming. Clear the EOP bit to 0.
set timeout $TIMEOUT
while { 1 } {
set flash_statr_data [ get_reg_value [ $_TARGET mdb $FLASH_STATR_ADDR ] ]
if {[ expr {$flash_statr_data & $BSY_BIT_MASK} ] != $BSY_BIT_MASK || [expr {$flash_statr_data & $EOP_BIT_MASK}] == $EOP_BIT_MASK} {
# Clear the EOP bit to 0.
$_TARGET mww $FLASH_STATR_ADDR [expr {$flash_statr_data & !$EOP_BIT_MASK}]
break
}
if {$timeout == $TIMEOUT} {
echo "** \[$op\] Flash memory operation is in the process. Waiting ... **"
} elseif {$timeout == 0} {
echo "\[$op\] !Err 2: Fail flash processing **"
return
}
incr timeout -$TIMEOUT_STEP
after $TIMEOUT_STEP
}
# 7)
# Read the programming address data for verification.
# State of RDPR byte in the User Option Bytes
# RDPR_ADDR should be `5aa5` already
set result [ get_reg_value [ $_TARGET mdh $RDPR_ADDR ] ]
if { $result == $RDPR_MASK_LOCK} {
echo "** \[$op\] Memory unlocked **"
} else {
echo "\[$op\] !Err 3: Fail memory unlocking **"
return
}
}
proc lock { } {
set op "LOCK"
global _TARGET
global FLASH_CTLR_ADDR
global FLASH_STATR_ADDR
global FLASH_OBR_ADDR
global RDPR_ADDR
# Status Register (FLASH_STATR)
global BSY_BIT_MASK
global EOP_BIT_MASK
# Control Register (FLASH_CTLR)
set OBER_BIT_MASK [expr {1 << 5}]
set STRT_BIT_MASK [expr {1 << 6}]
global LOCK_BIT_MASK
global OBWRE_BIT_MASK
global RDPRT_BIT_MASK
global RDPR_MASK_LOCK
set RDPR_MASK_UNLOCK 0xe339
# ms
global TIMEOUT_STEP
global TIMEOUT
# Read protection status.
# 1: Current read protection of flash is valid.
set RDPRT_byte [ get_reg_value [ $_TARGET mdb $FLASH_OBR_ADDR ] ]
if { [ expr {$RDPRT_byte & $RDPRT_BIT_MASK} ] == $RDPRT_BIT_MASK } {
echo "** \[$op\] Memory is locked already **"
return
}
# 1)
# Check the LOCK bit in the FLASH_CTLR register.
# If it is 1, you need to perform the "Release Flash Memory Lock" operation.
set flash_ctrl_data [ get_reg_value [ $_TARGET mdb $FLASH_CTLR_ADDR ] ]
if { [ expr {$flash_ctrl_data & $LOCK_BIT_MASK} ] == $LOCK_BIT_MASK } {
echo "** \[$op\] FLASH_CTLR are locked. Need Release Flash Memory. Releasing ... **"
_unlock_flash $_TARGET
}
# 2)
# Check the BSY bit in the FLASH_STATR register to ensure that
# there is no programming operation in progress.
set timeout $TIMEOUT
while { 1 } {
set flash_statr_data [ get_reg_value [ $_TARGET mdh $FLASH_STATR_ADDR ] ]
if { [ expr {$flash_statr_data & $BSY_BIT_MASK} ] != $BSY_BIT_MASK } {
break
}
if {$timeout == $TIMEOUT} {
echo "** \[$op\] Flash memory operation is in the process. Waiting ... **"
} elseif {$timeout == 0} {
echo "\[$op\] !Err 1: Fail flash processing **"
return
}
incr timeout -$TIMEOUT_STEP
after $TIMEOUT_STEP
}
# 3)
# Check the OBWRE bit in the FLASH_CTLR register.
# If it is 0, you need to perform the "User Option Bytes Unlock" operation.
set flash_ctrl_data [ get_reg_value [ $_TARGET mdh $FLASH_CTLR_ADDR ] ]
if { [ expr {$flash_ctrl_data & $OBWRE_BIT_MASK} ] == 0 } {
echo "** \[$op\] User option bytes is locked. Need to unlock. Unlocking ... **"
_unlock_user_ob $_TARGET
}
# 4)
# Set the OBER bit in the FLASH_CTLR register to ‘1’,
# then set the STRT bit in the FLASH_CTLR register to ‘1’,
# to enable the user option bytes erasure.
# Write OBER bit to 1 in FLASH_CTLR
$_TARGET mwb $FLASH_CTLR_ADDR $OBER_BIT_MASK
# Write STRT bit to 1 in FLASH_CTLR
$_TARGET mwb $FLASH_CTLR_ADDR $STRT_BIT_MASK
# 5)
# When the BSY bit changes to '0' or the EOP bit in the FLASH_STATR register to be '1',
# it indicates the end of programming. Clear the EOP bit to 0.
set timeout $TIMEOUT
while { 1 } {
set flash_statr_data [ get_reg_value [ $_TARGET mdb $FLASH_STATR_ADDR ] ]
if {[ expr {$flash_statr_data & $BSY_BIT_MASK} ] != $BSY_BIT_MASK || [expr {$flash_statr_data & $EOP_BIT_MASK}] == $EOP_BIT_MASK} {
# Clear the EOP bit to 0.
$_TARGET mww $FLASH_STATR_ADDR [expr {$flash_statr_data & !$EOP_BIT_MASK}]
break
}
if {$timeout == $TIMEOUT} {
echo "** \[$op\] Flash memory operation is in the process. Waiting ... **"
} elseif {$timeout == 0} {
echo "\[$op\] !Err 2: Fail flash processing **"
return
}
incr timeout -$TIMEOUT_STEP
after $TIMEOUT_STEP
}
# 6)
# Read the erasure address data for verification.
# State of RDPR byte in the User Option Bytes
# 0x1ffff800: e339e339
set result [ get_reg_value [ $_TARGET mdh $RDPR_ADDR ] ]
if {$result == $RDPR_MASK_UNLOCK} {
echo "** \[$op\] Flash succefully locked **"
} else {
echo "\[$op\] !Err 3: Fail flash locking **"
}
# Clear the OBER bit to 0.
set flash_statr_data [ get_reg_value [ $_TARGET mdw $FLASH_CTLR_ADDR ] ]
$_TARGET mww $FLASH_CTLR_ADDR [expr {$flash_statr_data & !$OBER_BIT_MASK}]
}
Расширив оригинальный файл скрипта wch‑riscv.cfg (находится в папке IDE MounRiver_Studio_Community_Linux_x64_V170/MRS_Community/toolchain/OpenOCD/bin/) функциями lock и unlock можно вызывать их через IDE в окне настройки Run Configurations или Debug Configurations (в меню Run или на панели инструментов) во вкладке Startup (рисунок 3).
Так как MounRiver запускает сервер OpenOCD и GDB, обращение к функциям происходит через ключевое слово monitor.
Если биты блокировки памяти выставляются из программы МК, то функцию lock можно не вызывать.
Запрограммировать с предварительным снятием и последующей установкой защиты можно запустить командами OpenOCD:
$WCH_OPENOCD/openocd -f $WCH_OPENOCD/wch-riscv.cfg -c "init" -c "unlock" \
-c "program your_firmware.hex verify" -c "lock" -c "reset" -c "exit"
В скрипт добавил сообщения о текущих этапах, поэтому на выходе будет такая картина (рисунок 4), если память была закрыта.
Для блокировки без прочих операций с памятью:
$WCH_OPENOCD/openocd -f $WCH_OPENOCD/wch-riscv.cfg -c "init" -c "lock" -c "reset" -c "exit"
Ну и для разблокировки:
$WCH_OPENOCD/openocd -f $WCH_OPENOCD/wch-riscv.cfg -c "init" -c "unlock" -c "reset" -c "exit"
Некоторые наблюдения
В процессе работы с документацией обратил внимание на следующее:
в разделе 32.6.4 Read Protection Release сказано, что после очистки области User Option Bytes байт RDPR примет значение 0xFF. Но нет. Как минимум на микроконтроллере ch32v203 поле принимает значение 0x39 (а регистр — 0xe339e339). Возможно, для каких то контроллеров, на которые распространяется документация, 0xFF является верным значением, но тогда было бы уместно уточнение.
AKudinov
Любопытно. А в чём смысл функции защиты от вычитывания памяти, если её (защиту) можно просто снять отладчиком? То, что я видел ранее, снималось только полным стиранием флэши, и в таком варианте действительно есть смысл.
bchirkov Автор
При снятии отладчиком флеш потрется