Implement XAP 'secure' core requirements (#16843)
Co-authored-by: Drashna Jaelre <drashna@live.com> Co-authored-by: Stefan Kerkmann <karlk90@pm.me>
This commit is contained in:
parent
ae4d518352
commit
92a61aa0cd
|
@ -32,6 +32,7 @@ GENERIC_FEATURES = \
|
||||||
KEY_OVERRIDE \
|
KEY_OVERRIDE \
|
||||||
LEADER \
|
LEADER \
|
||||||
PROGRAMMABLE_BUTTON \
|
PROGRAMMABLE_BUTTON \
|
||||||
|
SECURE \
|
||||||
SPACE_CADET \
|
SPACE_CADET \
|
||||||
SWAP_HANDS \
|
SWAP_HANDS \
|
||||||
TAP_DANCE \
|
TAP_DANCE \
|
||||||
|
|
|
@ -80,7 +80,8 @@ OTHER_OPTION_NAMES = \
|
||||||
LED_MIRRORED \
|
LED_MIRRORED \
|
||||||
RGBLIGHT_FULL_POWER \
|
RGBLIGHT_FULL_POWER \
|
||||||
LTO_ENABLE \
|
LTO_ENABLE \
|
||||||
PROGRAMMABLE_BUTTON_ENABLE
|
PROGRAMMABLE_BUTTON_ENABLE \
|
||||||
|
SECURE_ENABLE
|
||||||
|
|
||||||
define NAME_ECHO
|
define NAME_ECHO
|
||||||
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"
|
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"
|
||||||
|
|
|
@ -78,6 +78,9 @@
|
||||||
"QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
|
"QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
|
||||||
"QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
|
"QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
|
||||||
"QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
|
"QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
|
||||||
|
"SECURE_UNLOCK_SEQUENCE": {"info_key": "secure.unlock_sequence", "value_type": "array.array.int", "to_json": false},
|
||||||
|
"SECURE_UNLOCK_TIMEOUT": {"info_key": "secure.unlock_timeout", "value_type": "int"},
|
||||||
|
"SECURE_IDLE_TIMEOUT": {"info_key": "secure.idle_timeout", "value_type": "int"},
|
||||||
"SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"},
|
"SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"},
|
||||||
"SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"},
|
"SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"},
|
||||||
"SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},
|
"SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"MOUSEKEY_ENABLE": {"info_key": "mouse_key.enabled", "value_type": "bool"},
|
"MOUSEKEY_ENABLE": {"info_key": "mouse_key.enabled", "value_type": "bool"},
|
||||||
"NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"},
|
"NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"},
|
||||||
"PIN_COMPATIBLE": {"info_key": "pin_compatible"},
|
"PIN_COMPATIBLE": {"info_key": "pin_compatible"},
|
||||||
|
"SECURE_ENABLE": {"info_key": "secure.enabled", "value_type": "bool"},
|
||||||
"SPLIT_KEYBOARD": {"info_key": "split.enabled", "value_type": "bool"},
|
"SPLIT_KEYBOARD": {"info_key": "split.enabled", "value_type": "bool"},
|
||||||
"SPLIT_TRANSPORT": {"info_key": "split.transport.protocol", "to_c": false},
|
"SPLIT_TRANSPORT": {"info_key": "split.transport.protocol", "to_c": false},
|
||||||
"WAIT_FOR_USB": {"info_key": "usb.wait_for", "value_type": "bool"}
|
"WAIT_FOR_USB": {"info_key": "usb.wait_for", "value_type": "bool"}
|
||||||
|
|
|
@ -244,6 +244,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"secure": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {"type": "boolean"},
|
||||||
|
"unlock_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
|
||||||
|
"idle_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
|
||||||
|
"unlock_sequence": {
|
||||||
|
"type": "array",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 5,
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2,
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"min": 0,
|
||||||
|
"multipleOf": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"split": {
|
"split": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|
|
@ -94,7 +94,12 @@ def generate_config_items(kb_info_json, config_h_lines):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if key_type.startswith('array'):
|
if key_type.startswith('array.array'):
|
||||||
|
config_h_lines.append('')
|
||||||
|
config_h_lines.append(f'#ifndef {config_key}')
|
||||||
|
config_h_lines.append(f'# define {config_key} {{ {", ".join(["{" + ",".join(list(map(str, x))) + "}" for x in config_value])} }}')
|
||||||
|
config_h_lines.append(f'#endif // {config_key}')
|
||||||
|
elif key_type.startswith('array'):
|
||||||
config_h_lines.append('')
|
config_h_lines.append('')
|
||||||
config_h_lines.append(f'#ifndef {config_key}')
|
config_h_lines.append(f'#ifndef {config_key}')
|
||||||
config_h_lines.append(f'# define {config_key} {{ {", ".join(map(str, config_value))} }}')
|
config_h_lines.append(f'# define {config_key} {{ {", ".join(map(str, config_value))} }}')
|
||||||
|
|
|
@ -168,28 +168,46 @@ def _extract_pins(pins):
|
||||||
return [_pin_name(pin) for pin in pins.split(',')]
|
return [_pin_name(pin) for pin in pins.split(',')]
|
||||||
|
|
||||||
|
|
||||||
def _extract_direct_matrix(direct_pins):
|
def _extract_2d_array(raw):
|
||||||
|
"""Return a 2d array of strings
|
||||||
"""
|
"""
|
||||||
"""
|
out_array = []
|
||||||
direct_pin_array = []
|
|
||||||
|
|
||||||
while direct_pins[-1] != '}':
|
while raw[-1] != '}':
|
||||||
direct_pins = direct_pins[:-1]
|
raw = raw[:-1]
|
||||||
|
|
||||||
for row in direct_pins.split('},{'):
|
for row in raw.split('},{'):
|
||||||
if row.startswith('{'):
|
if row.startswith('{'):
|
||||||
row = row[1:]
|
row = row[1:]
|
||||||
|
|
||||||
if row.endswith('}'):
|
if row.endswith('}'):
|
||||||
row = row[:-1]
|
row = row[:-1]
|
||||||
|
|
||||||
direct_pin_array.append([])
|
out_array.append([])
|
||||||
|
|
||||||
for pin in row.split(','):
|
for val in row.split(','):
|
||||||
if pin == 'NO_PIN':
|
out_array[-1].append(val)
|
||||||
pin = None
|
|
||||||
|
|
||||||
direct_pin_array[-1].append(pin)
|
return out_array
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_2d_int_array(raw):
|
||||||
|
"""Return a 2d array of ints
|
||||||
|
"""
|
||||||
|
ret = _extract_2d_array(raw)
|
||||||
|
|
||||||
|
return [list(map(int, x)) for x in ret]
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_direct_matrix(direct_pins):
|
||||||
|
"""extract direct_matrix
|
||||||
|
"""
|
||||||
|
direct_pin_array = _extract_2d_array(direct_pins)
|
||||||
|
|
||||||
|
for i in range(len(direct_pin_array)):
|
||||||
|
for j in range(len(direct_pin_array[i])):
|
||||||
|
if direct_pin_array[i][j] == 'NO_PIN':
|
||||||
|
direct_pin_array[i][j] = None
|
||||||
|
|
||||||
return direct_pin_array
|
return direct_pin_array
|
||||||
|
|
||||||
|
@ -207,6 +225,21 @@ def _extract_audio(info_data, config_c):
|
||||||
info_data['audio'] = {'pins': audio_pins}
|
info_data['audio'] = {'pins': audio_pins}
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_secure_unlock(info_data, config_c):
|
||||||
|
"""Populate data about the secure unlock sequence
|
||||||
|
"""
|
||||||
|
unlock = config_c.get('SECURE_UNLOCK_SEQUENCE', '').replace(' ', '')[1:-1]
|
||||||
|
if unlock:
|
||||||
|
unlock_array = _extract_2d_int_array(unlock)
|
||||||
|
if 'secure' not in info_data:
|
||||||
|
info_data['secure'] = {}
|
||||||
|
|
||||||
|
if 'unlock_sequence' in info_data['secure']:
|
||||||
|
_log_warning(info_data, 'Secure unlock sequence is specified in both config.h (SECURE_UNLOCK_SEQUENCE) and info.json (secure.unlock_sequence) (Value: %s), the config.h value wins.' % info_data['secure']['unlock_sequence'])
|
||||||
|
|
||||||
|
info_data['secure']['unlock_sequence'] = unlock_array
|
||||||
|
|
||||||
|
|
||||||
def _extract_split_main(info_data, config_c):
|
def _extract_split_main(info_data, config_c):
|
||||||
"""Populate data about the split configuration
|
"""Populate data about the split configuration
|
||||||
"""
|
"""
|
||||||
|
@ -466,6 +499,7 @@ def _extract_config_h(info_data, config_c):
|
||||||
# Pull data that easily can't be mapped in json
|
# Pull data that easily can't be mapped in json
|
||||||
_extract_matrix_info(info_data, config_c)
|
_extract_matrix_info(info_data, config_c)
|
||||||
_extract_audio(info_data, config_c)
|
_extract_audio(info_data, config_c)
|
||||||
|
_extract_secure_unlock(info_data, config_c)
|
||||||
_extract_split_main(info_data, config_c)
|
_extract_split_main(info_data, config_c)
|
||||||
_extract_split_transport(info_data, config_c)
|
_extract_split_transport(info_data, config_c)
|
||||||
_extract_split_right_pins(info_data, config_c)
|
_extract_split_right_pins(info_data, config_c)
|
||||||
|
|
|
@ -562,6 +562,10 @@ void quantum_task(void) {
|
||||||
#ifdef AUTO_SHIFT_ENABLE
|
#ifdef AUTO_SHIFT_ENABLE
|
||||||
autoshift_matrix_scan();
|
autoshift_matrix_scan();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SECURE_ENABLE
|
||||||
|
secure_task();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \brief Keyboard task: Do keyboard routine jobs
|
/** \brief Keyboard task: Do keyboard routine jobs
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2022 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "secure.h"
|
||||||
|
#include "process_secure.h"
|
||||||
|
#include "quantum_keycodes.h"
|
||||||
|
|
||||||
|
bool preprocess_secure(uint16_t keycode, keyrecord_t *record) {
|
||||||
|
if (secure_is_unlocking()) {
|
||||||
|
if (!record->event.pressed) {
|
||||||
|
secure_keypress_event(record->event.key.row, record->event.key.col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal keypresses should be disabled until the sequence is completed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool process_secure(uint16_t keycode, keyrecord_t *record) {
|
||||||
|
#ifndef SECURE_DISABLE_KEYCODES
|
||||||
|
if (!record->event.pressed) {
|
||||||
|
if (keycode == SECURE_LOCK) {
|
||||||
|
secure_lock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (keycode == SECURE_UNLOCK) {
|
||||||
|
secure_unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (keycode == SECURE_TOGGLE) {
|
||||||
|
secure_is_locked() ? secure_unlock() : secure_lock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2022 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "action.h"
|
||||||
|
|
||||||
|
/** \brief Intercept keycodes and detect unlock sequences
|
||||||
|
*/
|
||||||
|
bool preprocess_secure(uint16_t keycode, keyrecord_t *record);
|
||||||
|
|
||||||
|
/** \brief Handle any secure specific keycodes
|
||||||
|
*/
|
||||||
|
bool process_secure(uint16_t keycode, keyrecord_t *record);
|
|
@ -212,6 +212,12 @@ bool process_record_quantum(keyrecord_t *record) {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#if defined(SECURE_ENABLE)
|
||||||
|
if (!preprocess_secure(keycode, record)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef VELOCIKEY_ENABLE
|
#ifdef VELOCIKEY_ENABLE
|
||||||
if (velocikey_enabled() && record->event.pressed) {
|
if (velocikey_enabled() && record->event.pressed) {
|
||||||
velocikey_accelerate();
|
velocikey_accelerate();
|
||||||
|
@ -247,6 +253,9 @@ bool process_record_quantum(keyrecord_t *record) {
|
||||||
process_record_via(keycode, record) &&
|
process_record_via(keycode, record) &&
|
||||||
#endif
|
#endif
|
||||||
process_record_kb(keycode, record) &&
|
process_record_kb(keycode, record) &&
|
||||||
|
#if defined(SECURE_ENABLE)
|
||||||
|
process_secure(keycode, record) &&
|
||||||
|
#endif
|
||||||
#if defined(SEQUENCER_ENABLE)
|
#if defined(SEQUENCER_ENABLE)
|
||||||
process_sequencer(keycode, record) &&
|
process_sequencer(keycode, record) &&
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -200,6 +200,11 @@ extern layer_state_t layer_state;
|
||||||
# include "process_dynamic_macro.h"
|
# include "process_dynamic_macro.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SECURE_ENABLE
|
||||||
|
# include "secure.h"
|
||||||
|
# include "process_secure.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef DYNAMIC_KEYMAP_ENABLE
|
#ifdef DYNAMIC_KEYMAP_ENABLE
|
||||||
# include "dynamic_keymap.h"
|
# include "dynamic_keymap.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -597,6 +597,10 @@ enum quantum_keycodes {
|
||||||
|
|
||||||
QK_MAKE,
|
QK_MAKE,
|
||||||
|
|
||||||
|
SECURE_LOCK,
|
||||||
|
SECURE_UNLOCK,
|
||||||
|
SECURE_TOGGLE,
|
||||||
|
|
||||||
// Start of custom keycode range for keyboards and keymaps - always leave at the end
|
// Start of custom keycode range for keyboards and keymaps - always leave at the end
|
||||||
SAFE_RANGE
|
SAFE_RANGE
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2022 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "secure.h"
|
||||||
|
#include "timer.h"
|
||||||
|
|
||||||
|
#ifndef SECURE_UNLOCK_TIMEOUT
|
||||||
|
# define SECURE_UNLOCK_TIMEOUT 5000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SECURE_IDLE_TIMEOUT
|
||||||
|
# define SECURE_IDLE_TIMEOUT 60000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SECURE_UNLOCK_SEQUENCE
|
||||||
|
# define SECURE_UNLOCK_SEQUENCE \
|
||||||
|
{ \
|
||||||
|
{ 0, 0 } \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static secure_status_t secure_status = SECURE_LOCKED;
|
||||||
|
static uint32_t unlock_time = 0;
|
||||||
|
static uint32_t idle_time = 0;
|
||||||
|
|
||||||
|
secure_status_t secure_get_status(void) {
|
||||||
|
return secure_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void secure_lock(void) {
|
||||||
|
secure_status = SECURE_LOCKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void secure_unlock(void) {
|
||||||
|
secure_status = SECURE_UNLOCKED;
|
||||||
|
idle_time = timer_read32();
|
||||||
|
}
|
||||||
|
|
||||||
|
void secure_request_unlock(void) {
|
||||||
|
if (secure_status == SECURE_LOCKED) {
|
||||||
|
secure_status = SECURE_PENDING;
|
||||||
|
unlock_time = timer_read32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void secure_activity_event(void) {
|
||||||
|
if (secure_status == SECURE_UNLOCKED) {
|
||||||
|
idle_time = timer_read32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void secure_keypress_event(uint8_t row, uint8_t col) {
|
||||||
|
static const uint8_t sequence[][2] = SECURE_UNLOCK_SEQUENCE;
|
||||||
|
static const uint8_t sequence_len = sizeof(sequence) / sizeof(sequence[0]);
|
||||||
|
|
||||||
|
static uint8_t offset = 0;
|
||||||
|
if ((sequence[offset][0] == row) && (sequence[offset][1] == col)) {
|
||||||
|
offset++;
|
||||||
|
if (offset == sequence_len) {
|
||||||
|
offset = 0;
|
||||||
|
secure_unlock();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset = 0;
|
||||||
|
secure_lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void secure_task(void) {
|
||||||
|
#if SECURE_UNLOCK_TIMEOUT != 0
|
||||||
|
// handle unlock timeout
|
||||||
|
if (secure_status == SECURE_PENDING) {
|
||||||
|
if (timer_elapsed32(unlock_time) >= SECURE_UNLOCK_TIMEOUT) {
|
||||||
|
secure_lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if SECURE_IDLE_TIMEOUT != 0
|
||||||
|
// handle idle timeout
|
||||||
|
if (secure_status == SECURE_UNLOCKED) {
|
||||||
|
if (timer_elapsed32(idle_time) >= SECURE_IDLE_TIMEOUT) {
|
||||||
|
secure_lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright 2022 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
*
|
||||||
|
* Exposes a set of functionality to act as a virtual padlock for your device
|
||||||
|
* ... As long as that padlock is made of paper and its currently raining.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/** \brief Available secure states
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
SECURE_LOCKED,
|
||||||
|
SECURE_PENDING,
|
||||||
|
SECURE_UNLOCKED,
|
||||||
|
} secure_status_t;
|
||||||
|
|
||||||
|
/** \brief Query current secure state
|
||||||
|
*/
|
||||||
|
secure_status_t secure_get_status(void);
|
||||||
|
|
||||||
|
/** \brief Helper to check if unlocking is currently locked
|
||||||
|
*/
|
||||||
|
#define secure_is_locked() (secure_get_status() == SECURE_LOCKED)
|
||||||
|
|
||||||
|
/** \brief Helper to check if unlocking is currently in progress
|
||||||
|
*/
|
||||||
|
#define secure_is_unlocking() (secure_get_status() == SECURE_PENDING)
|
||||||
|
|
||||||
|
/** \brief Helper to check if unlocking is currently unlocked
|
||||||
|
*/
|
||||||
|
#define secure_is_unlocked() (secure_get_status() == SECURE_UNLOCKED)
|
||||||
|
|
||||||
|
/** \brief Lock down the device
|
||||||
|
*/
|
||||||
|
void secure_lock(void);
|
||||||
|
|
||||||
|
/** \brief Force unlock the device
|
||||||
|
*
|
||||||
|
* \warning bypasses user unlock sequence
|
||||||
|
*/
|
||||||
|
void secure_unlock(void);
|
||||||
|
|
||||||
|
/** \brief Begin listening for an unlock sequence
|
||||||
|
*/
|
||||||
|
void secure_request_unlock(void);
|
||||||
|
|
||||||
|
/** \brief Flag to the secure subsystem that user activity has happened
|
||||||
|
*
|
||||||
|
* Call when some user activity has happened and the device should remain unlocked
|
||||||
|
*/
|
||||||
|
void secure_activity_event(void);
|
||||||
|
|
||||||
|
/** \brief Flag to the secure subsystem that user has triggered a keypress
|
||||||
|
*
|
||||||
|
* Call to trigger processing of the unlock sequence
|
||||||
|
*/
|
||||||
|
void secure_keypress_event(uint8_t row, uint8_t col);
|
||||||
|
|
||||||
|
/** \brief Handle various secure subsystem background tasks
|
||||||
|
*/
|
||||||
|
void secure_task(void);
|
Loading…
Reference in New Issue