Secure fault on i2c in one thread, but not another

This is odd, one thread can access i2c API just fine.

Another tread the same API call causes Secure Fault.

(These threads are mutually exclusive btw), the environment is set to "ns".

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>

LOG_MODULE_REGISTER(i2c_scan, LOG_LEVEL_INF);


// GPIO LED3 configuration (i2c Heartbeat)
//static const struct gpio_dt_spec led3 = GPIO_DT_SPEC_GET(DT_ALIAS(led3), gpios);

// GPIO LED2 configuration (blinks on SPI activity)
static const struct gpio_dt_spec led_i2c = GPIO_DT_SPEC_GET(DT_ALIAS(led4), gpios);

#define I2C_DEV      DEVICE_DT_GET(DT_NODELABEL(i2c1))  // Use I2C1
#define SLAVE_ADDR_1 0x08  // Example address of first I2C device
#define SLAVE_ADDR_2 0x09  // Example address of second I2C device


//#define I2C_DEV_LABEL DT_LABEL(DT_NODELABEL(i2c0))  // I2C0 on nRF5340DK
#define SCAN_INTERVAL K_SECONDS(5)                  // Scan every 5 seconds

#define LED2_NODE DT_NODELABEL(led4) // LED4 (P1.31) on nRF5340DK


void i2c_scan_thread(void) {

    //return; // Disable I2C scan thread for now
    LOG_DBG("I2C scan thread started");


    const struct device *i2c_dev = I2C_DEV;

    if (!device_is_ready(i2c_dev)) {
        LOG_ERR("I2C device not ready");
        return;
    }
    if (!device_is_ready(led_i2c.port)) {
        LOG_ERR("LED2 device not ready");
        return;
    }
    gpio_pin_configure_dt(&led_i2c, GPIO_OUTPUT_ACTIVE);

    while (1) {
        LOG_INF("I2C Scan Started...");

        // Toggle LED2 at the beginning of each scan
        gpio_pin_toggle_dt(&led_i2c);

        for (uint8_t addr = 0x03; addr <= 0x77; addr++) {
            uint8_t  data = 0x00;
            int      ret = 0;
            uint8_t  reg = 0x00;  // Register to read from (current RTC Seconds)
            

                // Perform a dummy read test
                ret = i2c_read(i2c_dev, &data, 1, addr);
                if (ret < 0) {
                    //LOG_ERR("I2C read failed: %d", ret);
                } else {
                    LOG_INF("I2C read successful, address 0x%02x, data: 0x%02x", addr, data);
                }

                // Perform a combined write-read transaction
                ret = i2c_write_read(i2c_dev, addr, &reg, sizeof(reg), &data, sizeof(data));
                if (ret < 0) {
                    //LOG_ERR("I2C read from register 0x%02x failed: %d", reg, ret);
                } else {
                    LOG_INF("I2C read from register 0x%02x successful, data: 0x%02x", reg, data);
                }

       }

        LOG_INF("I2C Scan Complete.");

        k_sleep(SCAN_INTERVAL);  // Sleep before next scan
    }
}

This all works as expected.

However, this tread causes Security Fault ay the first i2c API call.

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>

#include "sensor_bme280.h"


LOG_MODULE_REGISTER(sensor_bme280, LOG_LEVEL_INF);

// Define the thread stack size and priorities
#define THREAD_STACK_SIZE 1024
#define THREAD_PRIORITY 5

// Declare the sensor device (BME280) The driver is compatible with BMP280 as well.
static const struct device *bme280_dev;

#define BME280_I2C_ADDR  0x77  // Change to 0x76 if needed
#define BME280_REG_ID    0xD0


void bme280_thread(void)
{

    struct sensor_value temp, humidity, pressure;

   // Get the device handle for the BME280 sensor
    bme280_dev = DEVICE_DT_GET(DT_NODELABEL(bme280));  // Get Sensor device by label
    if (!device_is_ready(bme280_dev)) {
        printk("BME280 sensor not found\n");
        return;
    }


    LOG_INF("BME280 sensor thread started");

    bool is_bme280 = false;



    uint8_t reg = BME280_REG_ID;  // Register to read the chip ID
    uint8_t bme280_chip_id;
    int     ret=-1;
    ret = i2c_reg_read_byte(bme280_dev, BME280_I2C_ADDR, BME280_REG_ID, &bme280_chip_id);
    //ret = i2c_write(bme280_dev, &reg, 1, BME280_I2C_ADDR);  // Write the register address

  
    if (ret < 0) {
        printk("Failed to read chip ID!\n");
    } else {
        printk("Chip ID: 0x%02X\n", bme280_chip_id);
        if (bme280_chip_id == 0x60) {
            printk("BME280 detected!\n");
        } else if (bme280_chip_id == 0x58) {
            printk("BMP280 detected!\n");
        } else {
            printk("Unknown chip ID: 0x%02X\n", bme280_chip_id);
        }
    }

    while (1) {

        int ret = sensor_sample_fetch(bme280_dev);
        if (ret < 0) {
            LOG_ERR("Failed to fetch sensor data (err %d)", ret);
            k_sleep(K_MSEC(1000));  // Retry after 1 second
            continue;
        }

        // Get the sensor data
        ret = sensor_channel_get(bme280_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
        if (ret < 0) {
            LOG_ERR("Failed to get temperature data (err %d)", ret);
        } else {
            LOG_INF("Temperature: %3d.%d C", temp.val1, temp.val2);  }


        ret = sensor_channel_get(bme280_dev, SENSOR_CHAN_PRESS, &pressure);
        if (ret < 0) {
            LOG_ERR("Failed to get pressure data (err %d)", ret);
        } else {
            LOG_INF("Pressure:    %3d.%d Pa", pressure.val1, pressure.val2);
       }


        if (is_bme280) {
                // Not applicable to BMP280 (Only BME280 has the humidity sensor)
            ret = sensor_channel_get(bme280_dev, SENSOR_CHAN_HUMIDITY, &humidity);
            if (ret < 0) {
                 LOG_ERR("Failed to get humidity data (err %d)", ret);
            } else {
                LOG_INF("Humidity:    %3d.%d %%", humidity.val1, humidity.val2);
            }
 
        }


            k_sleep(K_SECONDS(5));  // Sleep for 5 seconds before next reading

        }
}

And this is the console output:

*** Booting nRF Connect SDK v2.9.0-7787b2649840 ***
*** Using Zephyr OS v3.7.99-1f8f3dc29142 ***
Starting nRF5340 Multi-Threaded PWM + GPIO + SPI4 + I2c2 Sensor Example!
[00:00:00.458,587] <inf> blinky_pwm: System initialized. Threads running.
[00:00:00.465,789] <inf> blinky_pwm: GPIO LED_heartbeat thread started
[00:00:00.472,656] <inf> blinky_pwm: LED_heartbeat is on port: gpio@842500, pin: 29
[00:00:00.480,712] <inf> blinky_pwm: PWM-based blinky thread started
[00:00:00.487,426] <dbg> pwm_nrf_sw: pwm_nrf_sw_set_cycles: channel 0, period 16000000, pulse 8000000
[00:00:00.497,009] <dbg> pwm_nrf_sw: pwm_nrf_sw_set_cycles: channel 0, period 16000000, pulse 8000000
[00:00:00.506,561] <inf> blinky_pwm: PWM period set to 1000000000
[00:00:00.513,061] <inf> sensor_bme280: BME280 sensor thread started
[00:00:00.519,805] <err> os: ***** SECURE FAULT *****
[00:00:00.525,451] <err> os:   Invalid entry point
[00:00:00.530,914] <err> os: r0/a1:  0x0001d56c  r1/a2:  0x200095a0  r2/a3:  0x00000002
[00:00:00.539,550] <err> os: r3/a4:  0x00000077 r12/ip:  0x200095ac r14/lr:  0x00019857
[00:00:00.548,156] <err> os:  xpsr:  0x00000000
[00:00:00.553,344] <err> os: Faulting instruction address (r15/pc): 0x00000000
[00:00:00.561,187] <err> os: >>> ZEPHYR FATAL ERROR 38: Unknown error on CPU 0
[00:00:00.569,061] <err> os: Current thread: 0x20008388 (bme280_tid)
[00:00:00.576,019] <err> os: Halting system

Relevant parts for prj.conf

# For NS apps, then this should be the default
CONFIG_TRUSTED_EXECUTION_NONSECURE=y

# I2C driver for nRF5340
CONFIG_I2C=y
CONFIG_I2C_NRFX=y           # Enable I2C driver for nRF5340
CONFIG_NRFX_TWIM1=y         # Enable second I2C instance using TWIM
#CONFIG_I2C_ASYNC=y          # Enable async I2C support
CONFIG_I2C_INIT_PRIORITY=40

# Enable Sensor Drivers 
CONFIG_SENSOR=y
CONFIG_SENSOR_LOG_LEVEL_DBG=y          # Set the logging level for the sensor subsystem

# Enable Adafruit BME280 Pressure Sensor support
CONFIG_BME280=y

Any help is appreciated...

  • Hi,

     

    Which thread is this wrt. your code?

    Current thread: 0x20008388 (bme280_tid)

    Based on the faulting instruction, my first guess would be that you are executing a null pointer, which triggers a secure fault.

    If you use addr2line on the LR content, it can help pin-point where the null pointer was called from.

    Have you ensured that your thread has enough stack allocated?

     

    Kind regards,

    Håkon

  • Hej Håkan,

    The security warning threw me off, I was used to seeing segfaults :)

    I doubled the tread stack, same issue.

    Traced down the function that segfaults

    static inline int z_impl_i2c_transfer(const struct device *dev,
    				      struct i2c_msg *msgs, uint8_t num_msgs,
    				      uint16_t addr)
    {
    	const struct i2c_driver_api *api =
    		(const struct i2c_driver_api *)dev->api;
    
    	if (!num_msgs) {
    		return 0;
    	}
    
    	msgs[num_msgs - 1].flags |= I2C_MSG_STOP;
    
    	int res =  api->transfer(dev, msgs, num_msgs, addr);
    
    	i2c_xfer_stats(dev, msgs, num_msgs);
    
    	if (IS_ENABLED(CONFIG_I2C_DUMP_MESSAGES)) {
    		i2c_dump_msgs_rw(dev, msgs, num_msgs, addr, true);
    	}
    
    	return res;
    }

    and this line causes the crash and the CPU is stuck on "Fatal Errors" loop. 

    	int res =  api->transfer(dev, msgs, num_msgs, addr);

    The thread is run in user mode, and so is the working thread.

    All calling data looks ok, and this line cannot be stepped into, it crashes immediately. To me, this does look like a security issue. The user thread is not able to call this that transfers the data.

    Another clue is this:

    [00:01:56.467,987] <err> os: ***** SECURE FAULT *****
    [00:01:56.467,987] <err> os: Invalid entry point

  • Hi,

     

    Could this be due to the TFM logging being enabled?

     

    Can you try to set the following in your prj.conf:

    CONFIG_TFM_SECURE_UART=n
    CONFIG_TFM_LOG_LEVEL_SILENCE=y

     

    And then re-generate the build folder?

     

    Kind regards,

    Håkon

  • Hej,

    Unfortunately, these were already set to the values above. So no difference, it still crashes.

    The difference between these threads is that the one crashing also uses the sensor API, any chance of a security mismatch?

    /Stefan

  • Hi Stefan,

     

    Is this the line that causes the fault?

    ret = i2c_reg_read_byte(bme280_dev, BME280_I2C_ADDR, BME280_REG_ID, &bme280_chip_id);

     

    The BME280_REG_ID define, which is a constant define, ie. located in flash. DMA requires that buffers are located in RAM, but the driver itself should provide a returned error if that is the case:

    https://github.com/nrfconnect/sdk-zephyr/blob/v3.7.99-ncs2/drivers/i2c/i2c_nrfx_twim.c#L81-L94

     

    Will commenting out the first i2c_read_reg_byte() call will make the subsequent sensor_ API call fault as well?

     

    Kind regards,

    Håkon

Related