Skip to content

Commit

Permalink
drv/bluetooth: add special location for hub name
Browse files Browse the repository at this point in the history
This modifies the advertising data and device name characteristics to
use the hub name from a special place in the firmware flash memory.
The offset and size of this location is stored in the firmware metadata
json file to allow modifying the hub name before flashing the firmware.
The name must be zero-terminated since we are using strlen(), so the
max size in the metadata includes the zero-termination byte.

Issue: pybricks/support#52
  • Loading branch information
dlech committed Jun 15, 2021
1 parent 08c93a9 commit 53d4974
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 27 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

# Changelog

## [Unreleased]

### Added
- Added special location in firmware for storing hub name ([support#52]). Note:
Support will need to be added to tools separately to make use of this.

## [3.0.0] - 2021-06-08

### Added
Expand All @@ -26,6 +32,7 @@ Prerelease changes are documented at [support#48].
<!-- let's try to keep this list sorted -->
[issue#21]: https://github.com/pybricks/pybricks-micropython/issues/21
[support#48]: https://github.com/pybricks/support/issues/48
[support#52]: https://github.com/pybricks/support/issues/52
[support#321]: https://github.com/pybricks/support/issues/321
[support#347]: https://github.com/pybricks/support/issues/347
[support#352]: https://github.com/pybricks/support/issues/352
Expand Down
5 changes: 5 additions & 0 deletions bricks/stm32/common.ld
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ SECTIONS
. = ALIGN(4);
} >RAM

.name :
{
*(.name) /* customizable hub name */
} >FLASH

.user :
{
. = ALIGN(4);
Expand Down
8 changes: 4 additions & 4 deletions bricks/stm32/stm32.mk
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ $(BUILD)/firmware-no-checksum.elf: $(LD_FILES) $(OBJ)

# firmware blob used to calculate checksum
$(BUILD)/firmware-no-checksum.bin: $(BUILD)/firmware-no-checksum.elf
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .user -j .checksum $^ $@
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .name -j .user -j .checksum $^ $@

$(BUILD)/firmware.elf: $(BUILD)/firmware-no-checksum.bin $(OBJ)
$(ECHO) "RELINK $@"
Expand All @@ -596,13 +596,13 @@ $(BUILD)/firmware.elf: $(BUILD)/firmware-no-checksum.bin $(OBJ)
# firmware blob with main.mpy and checksum appended - can be flashed to hub
$(BUILD)/firmware.bin: $(BUILD)/firmware.elf
$(ECHO) "BIN creating firmware file"
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .user -j .checksum $^ $@
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .name -j .user -j .checksum $^ $@
$(ECHO) "`wc -c < $@` bytes"

# firmware blob without main.mpy or checksum - use as base for appending other .mpy
$(BUILD)/firmware-base.bin: $(BUILD)/firmware-no-checksum.elf
$(ECHO) "BIN creating firmware base file"
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data $^ $@
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .name $^ $@
$(ECHO) "`wc -c < $@` bytes"

# firmware blob with different starting flash memory address for dual booting
Expand All @@ -614,7 +614,7 @@ $(BUILD)/firmware-dual-boot-base.elf: $(LD_FILES) $(OBJ) $(DUAL_BOOT_OBJ)
# firmware blob without main.mpy or checksum - use as base for appending other .mpy
$(BUILD)/firmware-dual-boot-base.bin: $(BUILD)/firmware-dual-boot-base.elf
$(ECHO) "BIN creating dual-boot firmware base file"
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data $^ $@
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .name $^ $@
$(ECHO) "`wc -c < $@` bytes"

# firmware blob without main.mpy or checksum - use as base for appending other .mpy
Expand Down
37 changes: 32 additions & 5 deletions lib/pbio/drv/bluetooth/bluetooth_btstack.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
#define HUB_VARIANT 0x0000
#endif

#define HUB_NAME "Pybricks Hub"
// hub name goes in special section so that it can be modified when flashing firmware
__attribute__((section(".name")))
char pbdrv_bluetooth_hub_name[16] = "Pybricks Hub";

static hci_con_handle_t le_con_handle = HCI_CON_HANDLE_INVALID;
static hci_con_handle_t pybricks_con_handle = HCI_CON_HANDLE_INVALID;
Expand Down Expand Up @@ -134,6 +136,26 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe
}
}

// ATT Client Read Callback for Dynamic Data
// - if buffer == NULL, don't copy data, just return size of value
// - if buffer != NULL, copy data and return number bytes copied
// @param offset defines start of attribute value
static uint16_t att_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) {
uint16_t att_value_len;

switch (attribute_handle) {
case ATT_CHARACTERISTIC_GAP_DEVICE_NAME_01_VALUE_HANDLE:
att_value_len = strlen(pbdrv_bluetooth_hub_name);
if (buffer) {
memcpy(buffer, pbdrv_bluetooth_hub_name, att_value_len);
}
return att_value_len;

default:
return 0;
}
}

void pbdrv_bluetooth_init(void) {
static btstack_packet_callback_registration_t hci_event_callback_registration;

Expand Down Expand Up @@ -161,7 +183,7 @@ void pbdrv_bluetooth_init(void) {
sm_set_ir((uint8_t *)pdata->ir_key);

// setup ATT server
att_server_init(profile_data, NULL, NULL);
att_server_init(profile_data, att_read_callback, NULL);

device_information_service_server_init();
device_information_service_server_set_firmware_revision(PBIO_VERSION_STR);
Expand Down Expand Up @@ -207,12 +229,17 @@ static void init_advertising_data(void) {
// 0x00XX - Product ID Field - hub kind
// 0x00XX - Product Version Field - product variant
0x50, 0x2a, 0x01, 0x97, 0x03, HUB_KIND, 0x00, 0x00, 0x00,
sizeof(HUB_NAME), BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME,
0, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME,
};

scan_resp_data[9] = HUB_VARIANT;
memcpy(&scan_resp_data[13], HUB_NAME, sizeof(HUB_NAME) - 1);
gap_scan_response_set_data(13 + sizeof(HUB_NAME) - 1, scan_resp_data);

uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name);
scan_resp_data[11] = hub_name_len + 1;
memcpy(&scan_resp_data[13], pbdrv_bluetooth_hub_name, hub_name_len);
_Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max");

gap_scan_response_set_data(13 + hub_name_len, scan_resp_data);
}

void pbdrv_bluetooth_start_advertising(void) {
Expand Down
16 changes: 10 additions & 6 deletions lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
#include <hci_le.h>
#include <hci_tl.h>

// name used for standard GAP device name characteristic
#define DEV_NAME "Pybricks Hub"
// hub name goes in special section so that it can be modified when flashing firmware
__attribute__((section(".name")))
char pbdrv_bluetooth_hub_name[16] = "Pybricks Hub";

// used to identify which hub - Device Information Service (DIS).
// 0x2A50 - service UUID - PnP ID characteristic UUID
Expand Down Expand Up @@ -240,10 +241,13 @@ static PT_THREAD(set_discoverable(struct pt *pt, void *context)) {
response_data[0] = sizeof(PNP_ID);
response_data[1] = AD_TYPE_SERVICE_DATA;
memcpy(&response_data[2], PNP_ID, sizeof(PNP_ID) - 1);
response_data[11] = sizeof(DEV_NAME);
uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name);
response_data[11] = hub_name_len + 1;
response_data[12] = AD_TYPE_COMPLETE_LOCAL_NAME;
memcpy(&response_data[13], DEV_NAME, sizeof(DEV_NAME) - 1);
hci_le_set_scan_response_data_begin(sizeof(response_data), response_data);
memcpy(&response_data[13], pbdrv_bluetooth_hub_name, hub_name_len);
_Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max");

hci_le_set_scan_response_data_begin(13 + hub_name_len, response_data);
PT_WAIT_UNTIL(pt, hci_command_complete);
hci_le_set_scan_response_data_end();

Expand Down Expand Up @@ -708,7 +712,7 @@ static PT_THREAD(hci_init(struct pt *pt)) {

PT_WAIT_WHILE(pt, write_xfer_size);
aci_gatt_update_char_value_begin(gap_service_handle, gap_dev_name_char_handle,
0, strlen(DEV_NAME), DEV_NAME);
0, strlen(pbdrv_bluetooth_hub_name), pbdrv_bluetooth_hub_name);
PT_WAIT_UNTIL(pt, hci_command_complete);
aci_gatt_update_char_value_end();

Expand Down
19 changes: 12 additions & 7 deletions lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@
#define DBG(...)
#endif

// name used for standard GAP device name characteristic
#define DEV_NAME "Pybricks Hub"
// hub name goes in special section so that it can be modified when flashing firmware
__attribute__((section(".name")))
char pbdrv_bluetooth_hub_name[16] = "Pybricks Hub";

// used to identify which hub - Device Information Service (DIS).
// 0x2A50 - service UUID - PnP ID characteristic UUID
Expand Down Expand Up @@ -292,10 +293,13 @@ static PT_THREAD(set_discoverable(struct pt *pt, void *context)) {
data[0] = sizeof(PNP_ID); // same as 1 + strlen(PNP_ID)
data[1] = GAP_ADTYPE_SERVICE_DATA;
memcpy(&data[2], PNP_ID, sizeof(PNP_ID));
data[11] = sizeof(DEV_NAME); // same as 1 + strlen(DEV_NAME)
uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name);
data[11] = hub_name_len + 1;
data[12] = GAP_ADTYPE_LOCAL_NAME_COMPLETE;
memcpy(&data[13], DEV_NAME, sizeof(DEV_NAME));
GAP_updateAdvertistigData(GAP_AD_TYPE_SCAN_RSP_DATA, sizeof(PNP_ID) + sizeof(DEV_NAME) + 2, data);
memcpy(&data[13], pbdrv_bluetooth_hub_name, hub_name_len);
_Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max");

GAP_updateAdvertistigData(GAP_AD_TYPE_SCAN_RSP_DATA, 13 + hub_name_len, data);
PT_WAIT_UNTIL(pt, hci_command_complete);
// ignoring response data

Expand Down Expand Up @@ -546,8 +550,9 @@ static void handle_event(uint8_t *packet) {
attReadRsp_t rsp;
uint8_t buf[ATT_MTU_SIZE - 1];

memcpy(&buf[0], DEV_NAME, sizeof(DEV_NAME));
rsp.len = sizeof(DEV_NAME) - 1;
uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name);
memcpy(&buf[0], pbdrv_bluetooth_hub_name, hub_name_len);
rsp.len = hub_name_len;
rsp.pValue = buf;
ATT_ReadRsp(connection_handle, &rsp);
} else if (handle == gap_service_handle + 4) {
Expand Down
2 changes: 1 addition & 1 deletion lib/pbio/drv/bluetooth/pybricks_service.gatt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "Pybricks Hub"
CHARACTERISTIC, GAP_DEVICE_NAME, DYNAMIC | READ,

// not importing device_information_service.gatt since we are not using all optional characteristics
PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_DEVICE_INFORMATION
Expand Down
24 changes: 20 additions & 4 deletions tools/metadata.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# Copyright (c) 2019-2020 The Pybricks Authors
# Copyright (c) 2019-2021 The Pybricks Authors

"""
Pybricks firmware metadata file generation tool.
Generates a .json file with information about a Pybricks firmware binary blob.
v1.0.0:
v1.1.0:
metadata-version "1.0.0"
metadata-version "1.1.0"
firmware-version output of `git describe --tags --dirty`
device-id one of 0x40, 0x41, 0x80, 0x81
checksum-type one of "sum", "crc32"
mpy-abi-version number (MPY_VERSION)
mpy-cross-options array of string
user-mpy-offset number
max-firmware-size number
hub-name-offset number [v1.1.0]
max-hub-name-size number [v1.1.0]
"""

import argparse
Expand All @@ -36,7 +38,7 @@


# metadata file format version
VERSION = "1.0.0"
VERSION = "1.1.0"

# hub-specific info
HUB_INFO = {
Expand Down Expand Up @@ -69,6 +71,8 @@ def generate(

flash_origin = None # Starting address of firmware area in flash memory
flash_length = None # Size of firmware area of flash memory
name_start = None # Starting address of custom hub name
name_size = None # Size reserved for custom hub name
user_start = None # Starting address of user .mpy file

for line in map_file.readlines():
Expand All @@ -78,6 +82,12 @@ def generate(
flash_length = int(match[2], base=0)
continue

match = re.match(r"^\.name\s+(0x[0-9A-Fa-f]{8,16})\s+(0x[0-9A-Fa-f]+)", line)
if match:
name_start = int(match[1], base=0)
name_size = int(match[2], base=0)
continue

match = re.match(r"^\.user\s+(0x[0-9A-Fa-f]{8,16})", line)
if match:
user_start = int(match[1], base=0)
Expand All @@ -91,12 +101,18 @@ def generate(
print("Failed to find 'FLASH' length", file=sys.stderr)
exit(1)

if name_start is None:
print("Failed to find '.name' start address", file=sys.stderr)
exit(1)

if user_start is None:
print("Failed to find '.user' start address", file=sys.stderr)
exit(1)

metadata["user-mpy-offset"] = user_start - flash_origin
metadata["max-firmware-size"] = flash_length
metadata["hub-name-offset"] = name_start - flash_origin
metadata["max-hub-name-size"] = name_size

json.dump(metadata, out_file, indent=4, sort_keys=True)

Expand Down

0 comments on commit 53d4974

Please sign in to comment.