Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change DCT read access to lock()/unlock() or read-with-copy #1307

Closed

Conversation

avtolstoy
Copy link
Member

@avtolstoy avtolstoy commented Apr 23, 2017

Do not merge. Review only. Superseded by #1320

Problem

When using dct_read_app_data the returned pointer might become invalid in case a different thread writes into DCT causing a DCT swap.

Photon/P1 should have a critical section around flash operations and around DCT operations.

Solution

This branched is based on #1295 and then rebased on #1289.

  • Changes DCT read access to lock()/unlock() or read-with-copy. New methods introduced: dct_read_app_data_lock(), dct_read_app_data_unlock(), dct_read_app_data_copy().
  • Adds atomic flag mutex for DCT read/write operations on Photon/P1 (see PR#8 in WICED repo)
  • Adds atomic flag mutex for flash operations on Photon/P1 (see PR#8 in WICED repo)

NOTE: Photon/P1 bootloader builds should overflow. It's intended, as we are removing DCT implementation out of it and importing it from system-part2 instead in https://github.com/spark/firmware/tree/feature/bootloader_dct

Steps to Test

N/A

Example App

N/A

References


Completeness

  • User is totes amazing for contributing!
  • Contributor has signed CLA (Info here)
  • Problem and Solution clearly stated
  • Run unit/integration/application tests on device
  • Added documentation
  • Added to CHANGELOG.md after merging (add links to docs and issues)

Enhancement

  • [PR #1307] [Photon/P1] New version of WICED adds CRC checking to the DCT implementation so that write errors are detected. Added a critical section around flash operations and around DCT operations to make them thread safe.

@avtolstoy avtolstoy requested a review from sergeuz April 23, 2017 19:04
@avtolstoy avtolstoy changed the base branch from develop to feature/photon/wiced-3.7.0-7 April 23, 2017 19:19
@avtolstoy avtolstoy force-pushed the feature/dct_crc-wiced-3.7 branch from 9521db6 to 9ed574c Compare April 27, 2017 15:37
}

const void* dct_read_app_data(uint32_t offset) {
return ((const void*)((uint32_t)wiced_dct_get_current_address(DCT_APP_SECTION) + offset));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's simplify this:

return ((const char*)wiced_dct_get_current_address(DCT_APP_SECTION) + offset);

I know this function is going to be deprecated, but still, how about adding a critical section here, just to avoid concurrent initialization/swapping of the DCTs?

@@ -524,7 +525,9 @@ int decrypt(char* plaintext, int max_plaintext_len, char* hex_encoded_ciphertext
hex_decode(buf, len, hex_encoded_ciphertext);

// reuse the hex encoded buffer
int plaintext_len = decrypt_rsa(buf, fetch_device_private_key(), (uint8_t*)plaintext, max_plaintext_len);
const uint8_t *key = fetch_device_private_key(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would split this into two functions or at least add a comment to improve readability

fetch_device_private_key(1); // Lock key data
fetch_device_private_key(0); // Unlock key data

const eap_config_t* eap_conf = (eap_config_t*)dct_read_app_data(DCT_EAP_CONFIG_OFFSET);
uint8_t eap_type = WLAN_EAP_TYPE_NONE;

const eap_config_t* eap_conf = (const eap_config_t*)dct_read_app_data_lock(DCT_EAP_CONFIG_OFFSET);
if (!is_eap_configuration_valid(eap_conf)) {
return 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dct_read_app_data_unlock() is missing here. I think we need to implement a RAII-based locker for DCT and avoid using dct_read_app_data_lock/unlock() in C++ code whenever possible


wiced_country_code_t result =
wiced_country_code_t(MK_CNTRY(code[0], code[1], hex_nibble(code[2])));

dct_read_app_data_unlock(DCT_COUNTRY_CODE_OFFSET);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code is dereferenced after unlocking DCT, later in this function:

if (code[0] == 0xFF || code[0] == 0)
...

@@ -441,6 +452,8 @@ int wlan_supplicant_start()
LOG(TRACE, "Supplicant started %d", (int)result);
}

dct_read_app_data_unlock(DCT_EAP_CONFIG_OFFSET);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe that a lot of WICED's stuff gets called while DCT is locked?

static_ip_config_t config;
memcpy(&config, pconfig, sizeof(config));
memcpy(&config, &pconfig, sizeof(config));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary copying

}

wiced_result_t wiced_dct_lock()
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add HAL_IsISR() check here?

@@ -284,7 +284,8 @@ void HAL_OTA_Flashed_ResetStatus(void)

void HAL_FLASH_Read_ServerPublicKey(uint8_t *keyBuffer)
{
fetch_device_public_key();
fetch_device_public_key(1);
fetch_device_public_key(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding a separate function, e.g. init_device_public_key(), and making fetch_device_public_key() to call it internally? These calls look weird :)

void unlock() {
flag.clear(std::memory_order_release);
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see this AtomicFlagMutex implementation supporting recursive locking, possibly in next versions of the firmware. For example:

void lock() {
    acquire_flag();
    if (!mutex_is_initialized && mutex_can_be_initialized) { // Heap is initialized, etc.
        init_recursive_mutex();
        mutex_is_initialized = true;
    }
    if (mutex_is_initialized) {
        lock_mutex();
        release_flag();
    }
}

void unlock() {
    if (mutex_is_initialized) {
        unlock_mutex();
    } else {
        release_flag();
    }
}

Copy link
Member Author

@avtolstoy avtolstoy Apr 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about something like this?

template <typename R, R(*spin)()>
class AtomicRecursiveMutex {
    std::atomic<std::thread::id> owner;
    std::atomic_int refcount{0};
public:
    int lock() {
        SPARK_ASSERT(!HAL_IsISR());
        std::thread::id no_id = std::thread::id();
        std::thread::id self = std::this_thread::get_id();
        while ((no_id != std::thread::id() && no_id != self) || !owner.compare_exchange_weak(no_id, self)) {
            if (no_id == self) {
                // To avoid spinning
                break;
            }
            else if (no_id != std::thread::id()) {
                no_id = std::thread::id();
            }
            spin();
        }
        return ++refcount;
    }

    int unlock() {
        SPARK_ASSERT(!HAL_IsISR());
        SPARK_ASSERT(owner.load() == std::this_thread::get_id());
        int c = --refcount;
        SPARK_ASSERT(c >= 0);
        if (c == 0) {
            // Unlock
            owner.store(std::thread::id());
        }
        return c;
    }
};

We can also add additional assertions to wiced_dct_lock/wiced_dct_unlock that will help us track issues with such an implementation.

wiced_result_t wiced_dct_lock(int exclusive)
{
    SPARK_ASSERT(!HAL_IsISR());
    int refcount = dctLock.lock();
    SPARK_ASSERT(!exclusive || refcount == 1);
    return WICED_SUCCESS;
}

wiced_result_t wiced_dct_unlock(int exclusive)
{
    SPARK_ASSERT(!HAL_IsISR());
    int refcount = dctLock.unlock();
    SPARK_ASSERT(!exclusive || refcount == 0);
    return WICED_SUCCESS;
}

exclusive would be set to 1 when writing to dct, as we cannot guarantee data validity after a dct sector swap.

result = wiced_dct_write(&expected, DCT_WIFI_CONFIG_SECTION, OFFSETOF(platform_dct_wifi_config_t, soft_ap_settings), sizeof(wiced_config_soft_ap_t));
} else {
wiced_dct_read_unlock( soft_ap, WICED_FALSE );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 👍 I've just checked other places where we use non-copying variant of wiced_dct_read_lock(), and haven't found anything suspicious.

@technobly
Copy link
Member

Closing due to changes being in released/v0.7.0-rc.1

@technobly technobly closed this Jul 12, 2017
@technobly technobly deleted the feature/dct_crc-wiced-3.7 branch July 12, 2017 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants