firmwareRegistry.js

/* Copyright (c) 2010 - 2017, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Use in source and binary forms, redistribution in binary form only, with
 * or without modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 2. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 3. This software, with or without modification, must only be used with a Nordic
 *    Semiconductor ASA integrated circuit.
 *
 * 4. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

const fs = require('fs');
const path = require('path');
const arrayToInt = require('./util/intArrayConv').arrayToInt;

const currentDir = require.resolve('./firmwareRegistry');
const hexDir = path.join(currentDir, '..', '..', 'pc-ble-driver', 'hex');
const sdV2Dir = path.join(hexDir, 'sd_api_v2');
const sdV3Dir = path.join(hexDir, 'sd_api_v3');

const VERSION_INFO_MAGIC = 0x46D8A517;
const VERSION_INFO_START = 0x20000;
const VERSION_INFO_LENGTH = 24;

/*
 * Holds connectivity firmware information for all supported devices.
 *
 * Devices that have a J-Link debug probe are programmed using nrfjprog, and
 * have only one firmware hex file. Devices that use the Nordic USB stack
 * are programmed using serial DFU. In this case, we need two hex files: One
 * for the softdevice and one for the connectivity application.
 *
 * MacOS does not support opening serial ports using baud rates higher than
 * 115200, while Windows and Linux supports 1m. This requires separate
 * connectivity firmwares and baud rate settings for the different OS'es.
 */
function getFirmwareMap(platform) {
    return {
        jlink: {
            nrf51: {
                file: platform === 'darwin' ?
                    path.join(sdV2Dir, 'connectivity_1.2.3_115k2_with_s130_2.0.1.hex') :
                    path.join(sdV2Dir, 'connectivity_1.2.3_1m_with_s130_2.0.1.hex'),
                version: '1.2.3',
                baudRate: platform === 'darwin' ? 115200 : 1000000,
                sdBleApiVersion: 2,
            },
            nrf52: {
                file: platform === 'darwin' ?
                    path.join(sdV3Dir, 'connectivity_1.2.3_115k2_with_s132_3.1.hex') :
                    path.join(sdV3Dir, 'connectivity_1.2.3_1m_with_s132_3.1.hex'),
                version: '1.2.3',
                baudRate: platform === 'darwin' ? 115200 : 1000000,
                sdBleApiVersion: 3,
            },
        },
        nordicUsb: {
            pca10059: {
                files: {
                    application: path.join(sdV3Dir, 'connectivity_1.2.3_usb_for_s132_3.hex'),
                    softdevice: path.join(sdV3Dir, 's132_nrf52_3.1.0_softdevice.hex'),
                },
                version: 'ble-connectivity 0.1.0+Aug-14-2018-15-12-51',
                baudRate: platform === 'darwin' ? 115200 : 1000000,
                sdBleApiVersion: 3,
                sdId: 0x91, // SoftDevice FWID, s132_nrf52_3.1.0 === 0x91
            },
        },
    };
}

/**
 * Exposes connectivity firmware information to the consumer of pc-ble-driver-js.
 * Implemented as a class with static functions in order to stay consistent with
 * the rest of the pc-ble-driver-js API.
 */
class FirmwareRegistry {

    /**
     * Get connectivity firmware information for the given device family.
     * Returns an object on the form:
     * {
     *   file: '/path/to/firmware.hex',
     *   version: '1.2.2',
     *   baudRate: 115200,
     *   sdBleApiVersion: 2,
     * }
     *
     * @param {String} family The device family. One of 'nrf51' or 'nrf52'.
     * @param {String} [platform] Optional value that can be one of 'win32',
     *     'linux', 'darwin'. Will use the detected platform if not provided.
     * @returns {Object} Firmware info object as described above.
     */
    static getJlinkConnectivityFirmware(family, platform) {
        const firmwareMap = getFirmwareMap(platform || process.platform);
        if (!firmwareMap.jlink[family]) {
            throw new Error(`Unsupported family: ${family}. ` +
                `Expected one of ${JSON.stringify(Object.keys(firmwareMap.jlink))}`);
        }
        return firmwareMap.jlink[family];
    }

    /**
     * Get connectivity firmware information for Nordic USB devices.
     * Returns an object on the form:
     * {
     *   files: {
     *     application: '/path/to/application.hex',
     *     softdevice: '/path/to/softdevice.hex',
     *   },
     *   version: 'connectivity 1.2.2+dfuMar-27-2018-12-41-04',
     *   baudRate: 115200,
     *   sdBleApiVersion: 3,
     *   sdId: 0x8C,
     * }
     *
     * The `sdId` is ID (also called FWID) of the SoftDevice that is required by
     * the connectivity. See https://github.com/NordicSemiconductor/pc-nrfutil
     * for a list of such IDs.
     *
     * @param {String} [platform] Optional value that can be one of 'win32',
     *     'linux', 'darwin'. Will use the detected platform if not provided.
     * @returns {Object} Firmware info object as described above.
     */
    static getNordicUsbConnectivityFirmware(platform) {
        const firmwareMap = getFirmwareMap(platform || process.platform);
        return firmwareMap.nordicUsb.pca10059;
    }

    /**
     * Returns an object that can be passed to the nrf-device-setup npm library
     * for setting up the device. See https://www.npmjs.com/package/nrf-device-setup
     * for a description of the returned object format.
     *
     * @param {String} [platform] Optional value that can be one of 'win32',
     *     'linux', 'darwin'. Will use the detected platform if not provided.
     * @returns {Object} Device setup object.
     */
    static getDeviceSetup(platform) {
        const firmwareMap = getFirmwareMap(platform || process.platform);

        const config = {
            jprog: {},
            dfu: {},
            needSerialport: true,
        };

        Object.keys(firmwareMap.jlink).forEach(family => {
            const deviceConfig = firmwareMap.jlink[family];
            const buffer = fs.readFileSync(deviceConfig.file);
            Object.assign(config.jprog, {
                [family]: {
                    fw: buffer,
                    fwVersion: {
                        length: VERSION_INFO_LENGTH,
                        validator: data => {
                            const parsedData = FirmwareRegistry.parseVersionStruct(data);
                            return parsedData.version === deviceConfig.version &&
                                parsedData.sdBleApiVersion === deviceConfig.sdBleApiVersion &&
                                parsedData.baudRate === deviceConfig.baudRate;
                        },
                    },
                    fwIdAddress: VERSION_INFO_START,
                },
            });
        });

        Object.keys(firmwareMap.nordicUsb).forEach(deviceType => {
            const deviceConfig = firmwareMap.nordicUsb[deviceType];
            const applicationBuffer = fs.readFileSync(deviceConfig.files.application);
            const softdeviceBuffer = fs.readFileSync(deviceConfig.files.softdevice);
            Object.assign(config.dfu, {
                [deviceType]: {
                    application: applicationBuffer,
                    softdevice: softdeviceBuffer,
                    semver: deviceConfig.version,
                    params: {
                        sdId: [deviceConfig.sdId],
                    },
                },
            });
        });

        return config;
    }

    /**
     * Parse the version info struct that can be found in the connectivity
     * firmware. See the connectivity firmware patch in pc-ble-driver/hex/sd_api_v*
     * for details.
     *
     * @param {Number[]} versionStruct Array of integers from the firmware.
     * @returns {Object} Parsed version info struct as an object.
     */
    static parseVersionStruct(versionStruct) {
        const magic = arrayToInt(versionStruct.slice(0, 4));
        const isValid = versionStruct.length === VERSION_INFO_LENGTH
            && magic === VERSION_INFO_MAGIC;
        if (!isValid) {
            return {};
        }
        const major = versionStruct[12];
        const minor = versionStruct[13];
        const patch = versionStruct[14];
        const version = `${major}.${minor}.${patch}`;
        const sdBleApiVersion = versionStruct[16];
        const transportType = versionStruct[17];
        const baudRate = arrayToInt(versionStruct.slice(20, 24));
        return {
            version,
            sdBleApiVersion,
            transportType,
            baudRate,
        };
    }
}

module.exports = FirmwareRegistry;