Skip to content

Commit fdb0e68

Browse files
committed
pbio/sys/storage_data: fix strict aliasing issues
Fix strict aliasing issues between block drivers and pbsys storage. Casting a uint8_t* to a struct pointer violates strict aliasing rules, which can lead to hard-to-find bugs. We need to ensure that the memory being pointed to has the same alignment, etc. as the struct it is being cast to. The usual way to do this is to use a union of the struct and the other type we want to use (in this case, uint8_t array). To do this, we need to move the storage data struct definition to a header file that can be included by both pbdrv/block_device and pbsys/storage. Additionally, in block_device_test, the size of the ramdisk block was wrong and was causing the NXT to crash sometime after starting the REPL. It also incorrectly had the .noinit section attribute, but depends on being initialized to zero, so that is removed. Fixes: pybricks/support#2272 Fixes: ce4253a ("pbio/sys: Move block device read to driver level.")
1 parent 96cdd12 commit fdb0e68

File tree

9 files changed

+117
-93
lines changed

9 files changed

+117
-93
lines changed

lib/pbio/drv/block_device/block_device_ev3.c

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,6 @@
2020
#include <stdint.h>
2121
#include <string.h>
2222

23-
#include "../core.h"
24-
25-
#include <pbdrv/block_device.h>
26-
#include <pbdrv/clock.h>
27-
#include <pbdrv/compiler.h>
28-
#include <pbdrv/gpio.h>
29-
3023
#include <tiam1808/edma.h>
3124
#include <tiam1808/spi.h>
3225
#include <tiam1808/psc.h>
@@ -36,9 +29,15 @@
3629
#include <tiam1808/armv5/am1808/edma_event.h>
3730
#include <tiam1808/armv5/am1808/interrupt.h>
3831

39-
#include "block_device_ev3.h"
32+
#include "../core.h"
4033
#include "../drv/gpio/gpio_ev3.h"
34+
#include "../sys/storage_data.h"
35+
#include "block_device_ev3.h"
4136

37+
#include <pbdrv/block_device.h>
38+
#include <pbdrv/clock.h>
39+
#include <pbdrv/compiler.h>
40+
#include <pbdrv/gpio.h>
4241
#include <pbio/error.h>
4342
#include <pbio/int_math.h>
4443
#include <pbio/util.h>
@@ -831,7 +830,11 @@ static struct {
831830
* portion of this, up to pbdrv_block_device_get_writable_size() bytes,
832831
* gets saved to flash at shutdown.
833832
*/
834-
uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE];
833+
union {
834+
// ensure that data is properly aligned for pbsys_storage_data_map_t
835+
pbsys_storage_data_map_t data_map;
836+
uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE];
837+
};
835838
} ramdisk __attribute__((section(".noinit"), used));
836839

837840
uint32_t pbdrv_block_device_get_writable_size(void) {
@@ -840,8 +843,8 @@ uint32_t pbdrv_block_device_get_writable_size(void) {
840843

841844
static pbio_error_t pbdrv_block_device_load_err = PBIO_ERROR_FAILED;
842845

843-
pbio_error_t pbdrv_block_device_get_data(uint8_t **data) {
844-
*data = ramdisk.data;
846+
pbio_error_t pbdrv_block_device_get_data(pbsys_storage_data_map_t **data) {
847+
*data = &ramdisk.data_map;
845848

846849
// Higher level code can use the ramdisk data if initialization completed
847850
// successfully. Otherwise it should reset to factory default data.

lib/pbio/drv/block_device/block_device_flash_stm32.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
#include <stdint.h>
1111
#include <string.h>
1212

13-
#include <contiki.h>
14-
1513
#include "../core.h"
14+
#include "../sys/storage_data.h"
1615

1716
#include <pbdrv/block_device.h>
1817

@@ -41,7 +40,11 @@ static struct {
4140
* portion of this, up to pbdrv_block_device_get_writable_size() bytes,
4241
* gets saved to flash at shutdown.
4342
*/
44-
uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE];
43+
union {
44+
// ensure that data is properly aligned for pbsys_storage_data_map_t
45+
pbsys_storage_data_map_t data_map;
46+
uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE];
47+
};
4548
} ramdisk __attribute__((section(".noinit"), used));
4649

4750
const uint32_t header_size = sizeof(ramdisk.saved_size) + sizeof(ramdisk.checksum_complement);
@@ -70,8 +73,8 @@ void pbdrv_block_device_init(void) {
7073
memcpy(&ramdisk, _pbdrv_block_device_storage_start, size);
7174
}
7275

73-
pbio_error_t pbdrv_block_device_get_data(uint8_t **data) {
74-
*data = ramdisk.data;
76+
pbio_error_t pbdrv_block_device_get_data(pbsys_storage_data_map_t **data) {
77+
*data = &ramdisk.data_map;
7578
return init_err;
7679
}
7780

lib/pbio/drv/block_device/block_device_test.c

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
#include <stdint.h>
1111
#include <string.h>
1212

13-
#include <pbdrv/block_device.h>
13+
#include "../sys/storage_data.h"
1414

15+
#include <pbio/os.h>
1516
#include <pbio/version.h>
1617

1718
/**
@@ -59,33 +60,30 @@ static const uint8_t _program_data[] = {
5960
// version at the right place. FIXME: Move the git version to pybricks build
6061
// system, since it isn't actually the micropython git version.
6162
#include "genhdr/mpversion.h"
62-
#include <pbsys/storage.h>
63+
6364

6465
static struct {
65-
uint8_t user_data[PBSYS_CONFIG_STORAGE_USER_DATA_SIZE];
66-
char stored_firmware_hash[8];
67-
pbsys_storage_settings_t settings;
68-
uint32_t program_offset;
69-
uint32_t program_size;
70-
uint8_t program_data[sizeof(_program_data)];
71-
} ramdisk __attribute__((section(".noinit"), used)) = { 0 };
66+
// ensure that data is properly aligned for pbsys_storage_data_map_t
67+
pbsys_storage_data_map_t data_map;
68+
uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE];
69+
} ramdisk;
7270

7371
uint32_t pbdrv_block_device_get_writable_size(void) {
7472
return 0;
7573
}
7674

77-
pbio_error_t pbdrv_block_device_get_data(uint8_t **data) {
78-
*data = (void *)&ramdisk;
75+
pbio_error_t pbdrv_block_device_get_data(pbsys_storage_data_map_t **data) {
76+
*data = &ramdisk.data_map;
7977

8078
// Higher level code can use the ramdisk data if initialization completed
8179
// successfully. Otherwise it should reset to factory default data.
8280
return PBIO_SUCCESS;
8381
}
8482

8583
void pbdrv_block_device_init(void) {
86-
ramdisk.program_size = sizeof(_program_data);
87-
memcpy(&ramdisk.stored_firmware_hash[0], MICROPY_GIT_HASH, sizeof(ramdisk.stored_firmware_hash));
88-
memcpy(&ramdisk.program_data[0], _program_data, sizeof(_program_data));
84+
ramdisk.data_map.slot_info[0].size = sizeof(_program_data);
85+
memcpy(ramdisk.data_map.stored_firmware_hash, MICROPY_GIT_HASH, sizeof(ramdisk.data_map.stored_firmware_hash));
86+
memcpy(ramdisk.data_map.program_data, _program_data, sizeof(_program_data));
8987
}
9088

9189
// Don't store any data in this implementation.

lib/pbio/drv/block_device/block_device_w25qxx_stm32.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313

1414
#include STM32_HAL_H
1515

16-
#include <contiki.h>
17-
1816
#include "../core.h"
17+
#include "../sys/storage_data.h"
1918
#include "block_device_w25qxx_stm32.h"
2019

2120
#include <pbdrv/block_device.h>
@@ -36,15 +35,19 @@ static struct {
3635
* portion of this, up to pbdrv_block_device_get_writable_size() bytes,
3736
* gets saved to flash at shutdown.
3837
*/
39-
uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE];
38+
union {
39+
// ensure that data is properly aligned for pbsys_storage_data_map_t
40+
pbsys_storage_data_map_t data_map;
41+
uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE];
42+
};
4043
} ramdisk __attribute__((section(".noinit"), used));
4144

4245
uint32_t pbdrv_block_device_get_writable_size(void) {
4346
return PBDRV_CONFIG_BLOCK_DEVICE_W25QXX_STM32_SIZE - sizeof(ramdisk.saved_size);
4447
}
4548

46-
pbio_error_t pbdrv_block_device_get_data(uint8_t **data) {
47-
*data = ramdisk.data;
49+
pbio_error_t pbdrv_block_device_get_data(pbsys_storage_data_map_t **data) {
50+
*data = &ramdisk.data_map;
4851

4952
// Higher level code can use the ramdisk data if initialization completed
5053
// successfully. Otherwise it should reset to factory default data.

lib/pbio/include/pbdrv/block_device.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111

1212
#include <stdint.h>
1313

14+
#include "../sys/storage_data.h"
15+
1416
#include <pbdrv/config.h>
1517
#include <pbio/error.h>
16-
1718
#include <pbio/os.h>
1819

1920
#if PBDRV_CONFIG_BLOCK_DEVICE
@@ -28,7 +29,7 @@
2829
* ::PBIO_ERROR_IO (driver-specific error), though boot
2930
* does not proceed if this happens.
3031
*/
31-
pbio_error_t pbdrv_block_device_get_data(uint8_t **data);
32+
pbio_error_t pbdrv_block_device_get_data(pbsys_storage_data_map_t **data);
3233

3334
/**
3435
* Writes the "RAM Disk" to storage. May erase entire disk prior to writing.
@@ -57,7 +58,7 @@ uint32_t pbdrv_block_device_get_writable_size(void);
5758

5859
#else
5960

60-
static inline pbio_error_t pbdrv_block_device_get_data(uint8_t **data) {
61+
static inline pbio_error_t pbdrv_block_device_get_data(pbsys_storage_data_map_t **data) {
6162
return PBIO_ERROR_NOT_SUPPORTED;
6263
}
6364

lib/pbio/platform/nxt/pbdrvconfig.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
#define PBDRV_CONFIG_BLOCK_DEVICE (1)
1212
#define PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE (10 * 1024)
1313
#define PBDRV_CONFIG_BLOCK_DEVICE_TEST (1)
14-
#define PBDRV_CONFIG_BLOCK_DEVICE_TEST_SIZE (8 * 1024)
15-
#define PBDRV_CONFIG_BLOCK_DEVICE_TEST_SIZE_USER (512)
1614

1715
#define PBDRV_CONFIG_BUTTON (1)
1816
#define PBDRV_CONFIG_BUTTON_NXT (1)

lib/pbio/platform/nxt/pbsysconfig.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#define PBSYS_CONFIG_MAIN (1)
1212
#define PBSYS_CONFIG_STORAGE (1)
1313
#define PBSYS_CONFIG_STORAGE_NUM_SLOTS (1)
14-
#define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_TEST_SIZE_USER)
14+
#define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (512)
1515
#define PBSYS_CONFIG_STATUS_LIGHT (0)
1616
#define PBSYS_CONFIG_STATUS_LIGHT_BATTERY (0)
1717
#define PBSYS_CONFIG_STATUS_LIGHT_BLUETOOTH (0)

lib/pbio/sys/storage.c

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,62 +22,11 @@
2222
#include "core.h"
2323
#include "hmi.h"
2424

25-
/**
26-
* Information about one code slot.
27-
*
28-
* A size of 0 means that this slot is not used. The offset indicates where
29-
* the program is stored in user storage.
30-
*
31-
* Code slots are *not* stored chronologically by slot id. Instead they are
32-
* stored consecutively as they are received, with the newest program last.
33-
*
34-
* If a slot is already in use and a new program should be loaded into it,
35-
* it is deleted by mem-moving any subsequent programs into its place, and
36-
* appending the new program to be last again. Since a user is typically only
37-
* iterating code in one slot, this is therefore usually the last stored
38-
* program. This means memmoves happen very little, only when needed.
39-
*
40-
*/
41-
typedef struct {
42-
uint32_t offset;
43-
uint32_t size;
44-
} pbsys_storage_slot_info_t;
45-
4625
/**
4726
* Slot at which incoming program data is currently being received.
4827
*/
4928
static uint8_t incoming_slot = 0;
5029

51-
/**
52-
* Map of loaded data. All data types are little-endian.
53-
*/
54-
typedef struct {
55-
/**
56-
* End-user read-write accessible data. Everything after this is also
57-
* user-readable but not writable.
58-
*/
59-
uint8_t user_data[PBSYS_CONFIG_STORAGE_USER_DATA_SIZE];
60-
/**
61-
* First 8 symbols of the git hash of the firmware version used to create
62-
* this data map. If this does not match the version of the running
63-
* firmware, user data will be reset to 0.
64-
*/
65-
char stored_firmware_hash[8];
66-
/**
67-
* System settings. Settings will be reset to defaults when the firmware
68-
* version changes due to an update.
69-
*/
70-
pbsys_storage_settings_t settings;
71-
/**
72-
* Size and offset info for each slot.
73-
*/
74-
pbsys_storage_slot_info_t slot_info[PBSYS_CONFIG_STORAGE_NUM_SLOTS];
75-
/**
76-
* Data of the application program (code + heap).
77-
*/
78-
uint8_t program_data[] __attribute__((aligned(sizeof(void *))));
79-
} pbsys_storage_data_map_t;
80-
8130
/**
8231
* Map of loaded data. This is kept in memory between successive runs of the
8332
* end user application (like MicroPython).
@@ -399,7 +348,7 @@ static pbio_error_t pbsys_storage_deinit_process_thread(pbio_os_state_t *state,
399348
*/
400349
void pbsys_storage_init(void) {
401350

402-
pbio_error_t err = pbdrv_block_device_get_data((uint8_t **)&map);
351+
pbio_error_t err = pbdrv_block_device_get_data(&map);
403352

404353
// Test that storage successfully loaded and matches current firmware,
405354
// otherwise reset storage.

lib/pbio/sys/storage_data.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2025 The Pybricks Authors
3+
4+
// Data structures for non-volatile storage.
5+
6+
// NB: Normally, pbsys code isn't referenced by drivers, however this is a
7+
// special case where the block device driver needs to have the actual struct
8+
// definition in order to ensure correct memory layout and alignment.
9+
10+
#ifndef _PBSYS_SYS_STORAGE_DATA_H_
11+
#define _PBSYS_SYS_STORAGE_DATA_H_
12+
13+
#include <stdint.h>
14+
15+
#include <pbsys/config.h>
16+
#include <pbsys/storage_settings.h>
17+
18+
/**
19+
* Information about one code slot.
20+
*
21+
* A size of 0 means that this slot is not used. The offset indicates where
22+
* the program is stored in user storage.
23+
*
24+
* Code slots are *not* stored chronologically by slot id. Instead they are
25+
* stored consecutively as they are received, with the newest program last.
26+
*
27+
* If a slot is already in use and a new program should be loaded into it,
28+
* it is deleted by mem-moving any subsequent programs into its place, and
29+
* appending the new program to be last again. Since a user is typically only
30+
* iterating code in one slot, this is therefore usually the last stored
31+
* program. This means memmoves happen very little, only when needed.
32+
*
33+
*/
34+
typedef struct {
35+
uint32_t offset;
36+
uint32_t size;
37+
} pbsys_storage_slot_info_t;
38+
39+
/**
40+
* Map of loaded data. All data types are little-endian.
41+
*/
42+
typedef struct {
43+
/**
44+
* End-user read-write accessible data. Everything after this is also
45+
* user-readable but not writable.
46+
*/
47+
uint8_t user_data[PBSYS_CONFIG_STORAGE_USER_DATA_SIZE];
48+
/**
49+
* First 8 symbols of the git hash of the firmware version used to create
50+
* this data map. If this does not match the version of the running
51+
* firmware, user data will be reset to 0.
52+
*/
53+
char stored_firmware_hash[8];
54+
/**
55+
* System settings. Settings will be reset to defaults when the firmware
56+
* version changes due to an update.
57+
*/
58+
pbsys_storage_settings_t settings;
59+
/**
60+
* Size and offset info for each slot.
61+
*/
62+
pbsys_storage_slot_info_t slot_info[PBSYS_CONFIG_STORAGE_NUM_SLOTS];
63+
/**
64+
* Data of the application program (code + heap).
65+
*/
66+
uint8_t program_data[] __attribute__((aligned(sizeof(void *))));
67+
} pbsys_storage_data_map_t;
68+
69+
#endif // _PBSYS_SYS_STORAGE_DATA_H_

0 commit comments

Comments
 (0)