1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
// Copyright 2021 Locha Mesh Developers <contact@locha.io>
//
// Based on the previous work of cc2538-bsl and Texas Instruments sblAppEx
// 1.03.00.00 (swra466c.zip).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # Utilities
//!
//! These are convenience functions to read common parameters of the devices,
//! such as the IEEE 802.15.5g address, BLE MAC address, the flash size in
//! bytes, etc.

use std::{convert::TryInto, io};

use crate::{
    constants::{
        COMMAND_RET_FLASH_FAIL, COMMAND_RET_INVALID_ADR,
        COMMAND_RET_INVALID_CMD, COMMAND_RET_SUCCESS, COMMAND_RET_UNKNOWN_CMD,
        MAX_BYTES_PER_TRANSFER,
    },
    Device, Family,
};

/// CC26xx/CC13xx CCFG size in bytes.
pub const CCFG_SIZE: usize = 88;
/// The value of an invalid IEEE/BLE address in the CCFG.
pub const INVALID_ADDR: [u8; 8] =
    [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];

const REG32_SIZE: usize = 4;
/// FLASH.FLASH_SIZE register on CC13xx/CC26xx
const CC26XX_FLASH_O_FLASH_SIZE: u32 = 0x4003002C;
const CC26XX_FCFG1_O_MAC_15_4_0: u32 = 0x000002F0;
/// FLASH_CTRL.DIECFG0 register on CC2538
const CC2538_FLASH_CTRL_O_DIECFG0: u32 = 0x400D3014;

/// Erase a flash range.
pub fn erase_flash_range<P, F>(
    device: &mut Device<P>,
    start_address: u32,
    byte_count: u32,
    mut progress: F,
) -> io::Result<()>
where
    P: serial::SerialPort,
    F: FnMut(f32, u32),
{
    let family = device.family();
    if family.supports_erase() {
        progress(0.0, 0);
        device.erase(start_address, byte_count)?;
        progress(100.0, start_address + byte_count);
    } else if family.supports_sector_erase() {
        let sector_size = family.sector_size();
        let sector_count = if (byte_count % sector_size) != 0 {
            (byte_count / sector_size) + 1
        } else {
            byte_count / sector_size
        };

        for i in 0..sector_count {
            let sector_address = start_address + (i * sector_size);
            log::info!("Erasing sector #{}, address: {:#X}", i, sector_address);

            progress((100.0 * i as f32) / sector_count as f32, sector_address);

            device.sector_erase(sector_address)?;

            let ret = device.get_status()?;
            if ret != COMMAND_RET_SUCCESS {
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    format!(
                        "CMD_SECTOR_ERASE failed: `{}` ({:#X})",
                        status_code_to_str(ret),
                        ret
                    ),
                ));
            }
        }
    } else {
        unreachable!();
    }

    Ok(())
}

/// A binary data transfer
#[derive(Debug)]
pub struct Transfer<'a> {
    /// The data to write on the device's flash.
    pub data: &'a [u8],
    /// The start address in flash of this data.
    pub start_address: u32,
    /// Whether we expect an ACK in return.
    pub expect_ack: bool,
}

/// Write the flash.
pub fn write_flash_range<'a, P, F>(
    device: &mut Device<P>,
    transfers: &[Transfer<'a>],
    mut progress: F,
) -> io::Result<()>
where
    P: serial::SerialPort,
    F: FnMut(usize, f32, u32, u32),
{
    let family = device.family();

    log::info!("{} transfers", transfers.len());

    for (txfer_index, transfer) in transfers.iter().enumerate() {
        let chunks = transfer.data.len() / MAX_BYTES_PER_TRANSFER;
        log::info!("Chunks for transfer #{}: {}", txfer_index, chunks);

        // Prepare device for flash download.
        device.download(
            transfer.start_address,
            transfer.data.len().try_into().unwrap(),
        )?;

        // Each download command requires to check the latest
        // status to verify it worked.
        let ret = device.get_status()?;

        if ret != COMMAND_RET_SUCCESS {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                format!(
                    "CMD_DOWNLOAD failed: `{}` ({:#X})",
                    status_code_to_str(ret),
                    ret
                ),
            ));
        }

        let mut bytes_left = transfer.data.len();
        let mut data_offset = 0;
        let mut chunk_index = 0;

        while bytes_left > 0 {
            let bytes_in_transfer = MAX_BYTES_PER_TRANSFER.min(bytes_left);
            let chunk = &transfer.data[data_offset..];
            let chunk = &chunk[..bytes_in_transfer];

            let chunk_addr = transfer.start_address + data_offset as u32;
            log::info!(
                "Writing chunk #{} ({} B) at address {:#X}",
                chunk_index,
                chunk.len(),
                chunk_addr
            );

            progress(
                txfer_index,
                (100.0 * chunk_index as f32) / chunks as f32,
                chunk_index,
                chunk_addr,
            );

            let ack = device.send_data(&chunk)?;
            if transfer.expect_ack {
                if !ack {
                    return Err(io::Error::new(
                        io::ErrorKind::Other,
                        format!(
                            "Chunk #{} of size {} not acknowledged at address {:#X} (page: {}) at transfer #{}",
                            chunk_index, chunk.len(), chunk_addr, family.address_to_page(chunk_addr),
                            txfer_index,
                        )
                    ));
                }

                let ret = device.get_status()?;
                if ret != COMMAND_RET_SUCCESS {
                    return Err(io::Error::new(
                        io::ErrorKind::Other,
                        format!(
                            "CMD_SEND_DATA failed: `{}` ({:#X})",
                            status_code_to_str(ret),
                            ret
                        ),
                    ));
                }
            }

            bytes_left -= bytes_in_transfer;
            data_offset += bytes_in_transfer;
            chunk_index += 1;
        }
    }

    Ok(())
}

/// Reads the flash size from the memory.
pub fn read_flash_size<P>(device: &mut Device<P>) -> io::Result<u32>
where
    P: serial::SerialPort,
{
    let addr = match device.family() {
        Family::CC2538 => CC2538_FLASH_CTRL_O_DIECFG0,
        Family::CC26X0 | Family::CC26X2 => CC26XX_FLASH_O_FLASH_SIZE,
    };

    let mut reg = [0u8; REG32_SIZE];
    device.memory_read_32(addr, &mut reg)?;

    match device.family() {
        Family::CC2538 => {
            let flash_ctrl = u32::from_le_bytes(reg);
            let flash_size = (flash_ctrl >> 4) & 0x07;
            match flash_size {
                1 => Ok(0x20000), // 128 KB
                2 => Ok(0x40000), // 256 KB
                3 => Ok(0x60000), // 384 KB
                4 => Ok(0x80000), // 512 KB
                0 => Ok(0x10000), //  64 KB
                _ => Ok(0x10000), // All invalid values are interpreted as 64 KB
            }
        }
        Family::CC26X0 | Family::CC26X2 => {
            let mut flash_size = u32::from_le_bytes(reg);
            flash_size &= 0xFF;

            Ok(flash_size * device.family().sector_size())
        }
    }
}

/// Read IEEE 802.15.4g MAC address.
pub fn read_ieee_address<P>(
    device: &mut Device<P>,
) -> io::Result<([u8; 8], [u8; 8])>
where
    P: serial::SerialPort,
{
    let primary_addr_offset = match device.family() {
        Family::CC2538 => 0x00280028,
        Family::CC26X0 | Family::CC26X2 => CC26XX_FCFG1_O_MAC_15_4_0,
    };

    let secondary_addr_offset = match device.family() {
        Family::CC2538 => 0x0027ffcc,
        Family::CC26X0 | Family::CC26X2 => {
            let ccfg_offset = read_flash_size(device)? - CCFG_SIZE as u32;

            ccfg_offset + 0x20
        }
    };

    let mut primary = [0u8; 8];
    device.memory_read_32(primary_addr_offset, &mut primary)?;

    let mut secondary = [0u8; 8];
    device.memory_read_32(secondary_addr_offset, &mut secondary)?;

    Ok((primary, secondary))
}

pub fn status_code_to_str(ret: u8) -> &'static str {
    match ret {
        COMMAND_RET_SUCCESS => "COMMAND_RET_SUCCESS",
        COMMAND_RET_UNKNOWN_CMD => "COMMAND_RET_UNKNOWN_CMD",
        COMMAND_RET_INVALID_CMD => "COMMAND_RET_INVALID_CMD",
        COMMAND_RET_INVALID_ADR => "COMMAND_RET_INVALID_ADR",
        COMMAND_RET_FLASH_FAIL => "COMMAND_RET_FLASH_FAIL",
        _ => "Unknown",
    }
}