diff --git a/pylink/enums.py b/pylink/enums.py index ec8914e..f6d54e6 100644 --- a/pylink/enums.py +++ b/pylink/enums.py @@ -330,6 +330,12 @@ class JLinkFunctions(object): ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int) + UNLOCK_IDCODE_HOOK_PROTOTYPE = ctypes.CFUNCTYPE(ctypes.c_int, + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_uint32, + ctypes.c_void_p, + ctypes.c_int) class JLinkCore(object): @@ -747,3 +753,45 @@ class JLinkPowerTraceRef(object): NONE = 0 BYTES = 1 TIME = 2 + + +class JLinkIndirectFunctionIndex(object): + """J-Link indirect function index.""" + SET_HOOK_DIALOG_UNLOCK_IDCODE = 0 + SPI_TRANSFER_MULTIPLE = 1 + PIN_OVERRIDE = 2 + PIN_OVERRIDE_GET_PIN_CAPS = 3 + MRU_GETLIST = 4 + RESERVED3 = 5 + RESERVED4 = 6 + RESERVED5 = 7 + GET_SESSION_ID = 8 + CORESIGHT_TRIGGER_READ_APDP_REG = 9 + CAN_ACC_MEM_WHILE_RUNNING = 10 + UPDATE_BTL = 11 + GET_CURRENT_ENDIANESS = 12 + ALGODB_GET_PALGO_INFO = 13 + ALGODB_GET_PALGO_INFO_CFI = 14 + ALGODB_GET_ALGO_NO = 15 + PCODE_SET_ENTRY_FUNC = 16 + PCODE_DOWNLOAD = 17 + PCODE_EXEC_EX = 18 + START_MERGE_COMMANDS = 19 + END_MERGE_COMMANDS = 20 + RAWTRACE_BIST_STARTSTOP = 21 + RAWTRACE_BIST_READ_ERR_STATS = 22 + GET_PF_GET_INST_INFO = 23 + CORESIGHT_ACC_APDP_REG_MUL = 24 + PCODE_DATA_DOWNLOAD = 25 + PCODE_EXEC_EX2 = 25 + PCODE_FREE = 26 + EMU_COMMANDLINE_WRITE_READ = 27 + GET_PF_DISASSEMBLE_BUFFER = 28 + EMU_GET_TARGET_IMG_AREA_INFO = 29 + EMU_READ_TARGET_IMG_AREA = 30 + EMU_WRITE_TARGET_IMG_AREA = 31 + EMU_GET_CURR_CONN_INFO = 31 + GET_PF_EXP_DEVICE_LIST_XML = 32 + SCRIPTFILE_EXEC_FUNC = 33 + EMU_ADD_FW_IMAGES = 34 + NUM_FUNC_INDEXES = 35 diff --git a/pylink/jlink.py b/pylink/jlink.py index 70f22ad..fb37c44 100644 --- a/pylink/jlink.py +++ b/pylink/jlink.py @@ -1925,6 +1925,22 @@ def unlock(self): return True + @open_required + def set_unlock_idcode(self, id_code): + """Sets the J-Link unlock ``IDCODE`` and enables function redirect. This is + only supported by certain devices such as Renesas. + + Args: + self (JLink): the ``JLink`` instance + id_code (str): ``IDCODE`` to unlock debug access in hexadecimal format + + Returns: + ``None`` + """ + unlockers.set_unlock_idcode(self, id_code) + + return True + @connection_required def cpu_capability(self, capability): """Checks whether the J-Link has support for a CPU capability. diff --git a/pylink/unlockers/__init__.py b/pylink/unlockers/__init__.py index c928586..33276db 100644 --- a/pylink/unlockers/__init__.py +++ b/pylink/unlockers/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. from .unlock_kinetis import unlock_kinetis +from .unlock_idcode import set_unlock_idcode def unlock(jlink, name): diff --git a/pylink/unlockers/unlock_idcode.py b/pylink/unlockers/unlock_idcode.py new file mode 100644 index 0000000..39f3b1d --- /dev/null +++ b/pylink/unlockers/unlock_idcode.py @@ -0,0 +1,77 @@ +# Copyright 2025 Jeremiah Gillis +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .. import enums +from .. import errors + +import ctypes + +# Global variable to store the ID code +global_id_code = None + + +def set_unlock_idcode(self, id_code): + """Sets the J-Link unlock ``IDCODE`` and enables function redirect. This is + only supported by certain devices such as Renesas. + + Args: + self (JLink): the ``JLink`` instance + id_code (str): ``IDCODE`` to unlock debug access in hexadecimal format + + Returns: + ``None`` + + Raises: + ValueError: if ``id_code`` is not a hexadecimal string. + JLinkException: if function is not found. + """ + try: + int(id_code, 16) + except ValueError: + raise ValueError('id_code must be a hexadecimal string.') + + global global_id_code + global_id_code = id_code + + self._dll.JLINK_GetpFunc.restype = ctypes.c_void_p + self.unlock_idcode_cb = enums.JLinkFunctions.UNLOCK_IDCODE_HOOK_PROTOTYPE(unlock_idcode_hook_dialog) + function_ptr = self._dll.JLINK_GetpFunc(enums.JLinkIndirectFunctionIndex.SET_HOOK_DIALOG_UNLOCK_IDCODE) + if not function_ptr: + raise errors.JLinkException('Could not find Set Hook Dialog Unlock IDCODE function.') + + callback_override = ctypes.CFUNCTYPE(None, ctypes.c_void_p) + function = ctypes.cast(function_ptr, callback_override) + function(self.unlock_idcode_cb) + return + + +def unlock_idcode_hook_dialog(title, msg, flags, id_code, max_num_bytes): + """Unlocks debug access using J-Link IDCODE hook. + + Args: + title (str): title of the unlock id code dialog + msg (str): text of the unlock id code dialog + flags (int): flags specifying which values can be returned + id_code (void pointer): buffer pointer to store IDCODE + max_num_bytes (int): maximum number of bytes that can be written to IDCODE buffer + + Returns: + ``enums.JLinkFlags.DLG_BUTTON_OK`` + """ + global global_id_code + code_bytes = bytes.fromhex(global_id_code) + data = ctypes.cast(id_code, ctypes.POINTER(ctypes.c_byte * max_num_bytes)) + data.contents[:min(len(code_bytes), max_num_bytes)] = code_bytes + + return enums.JLinkFlags.DLG_BUTTON_OK diff --git a/tests/unit/unlockers/test_unlock_idcode.py b/tests/unit/unlockers/test_unlock_idcode.py new file mode 100644 index 0000000..b979a20 --- /dev/null +++ b/tests/unit/unlockers/test_unlock_idcode.py @@ -0,0 +1,107 @@ +# Copyright 2025 Jeremiah Gillis +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pylink.enums as enums +from pylink.errors import JLinkException +import pylink.unlockers as unlock +import ctypes + +import mock + +import unittest + + +class TestUnlockIDCODE(unittest.TestCase): + """Tests the `unlock_idcode` submodule.""" + + def setUp(self): + """Called before each test. + + Performs setup. + + Args: + self (TestUnlockIDCODE): the `TestUnlockIDCODE` instance + + Returns: + `None` + """ + pass + + def tearDown(self): + """Called after each test. + + Performs teardown. + + Args: + self (TestUnlockIDCODE): the `TestUnlockIDCODE` instance + + Returns: + `None` + """ + pass + + def test_set_unlock_idcode_errors(self): + """Tests calling `set_unlock_idcode()` for errors. + + Args: + self (TestUnlockIDCODE): the `TestUnlockIDCODE` instance + + Returns: + `None` + """ + jlink = mock.Mock() + + with self.assertRaises(ValueError): + unlock.set_unlock_idcode(jlink, '') + + with self.assertRaises(ValueError): + unlock.set_unlock_idcode(jlink, '00112233445566778899AABBCCDDEEHH') + + jlink._dll.JLINK_GetpFunc.return_value = None + with self.assertRaises(JLinkException): + unlock.set_unlock_idcode(jlink, '00112233445566778899AABBCCDDEEFF') + + def test_set_unlock_idcode(self): + """Tests calling `set_unlock_idcode()` for success. + + Args: + self (TestUnlockIDCODE): the `TestUnlockIDCODE` instance + + Returns: + `None` + """ + jlink = mock.Mock() + + def dummy_function(address): + return + + ptr_type = ctypes.CFUNCTYPE(None, ctypes.c_void_p) + jlink._dll.JLINK_GetpFunc.return_value = ptr_type(dummy_function) + result = unlock.set_unlock_idcode(jlink, '00112233445566778899AABBCCDDEEFF') + self.assertIsNone(result) + + id_code_t = ctypes.c_byte * 16 + id_code = id_code_t() + id_code_p = ctypes.cast(ctypes.pointer(id_code), ctypes.c_void_p) + result = jlink.unlock_idcode_cb(ctypes.c_char_p('Test'.encode('utf-8')), + ctypes.c_char_p('Unlock IDCODE'.encode('utf-8')), + 4, + id_code_p, + 16) + self.assertEqual(result, enums.JLinkFlags.DLG_BUTTON_OK) + self.assertEqual(bytes(id_code).hex().upper(), '00112233445566778899AABBCCDDEEFF') + + +if __name__ == '__main__': + unittest.main()