diff --git a/connectivity/drivers/nfc/CMakeLists.txt b/connectivity/drivers/nfc/CMakeLists.txt index 214a1e0ba8..aa4d75cfdd 100644 --- a/connectivity/drivers/nfc/CMakeLists.txt +++ b/connectivity/drivers/nfc/CMakeLists.txt @@ -4,3 +4,7 @@ if("PN512" IN_LIST MBED_TARGET_LABELS) add_subdirectory(PN512) endif() + +if("M24SR" IN_LIST MBED_TARGET_LABELS) + add_subdirectory(TARGET_M24SR) +endif() diff --git a/connectivity/drivers/nfc/TARGET_M24SR/CMakeLists.txt b/connectivity/drivers/nfc/TARGET_M24SR/CMakeLists.txt new file mode 100644 index 0000000000..dc2a4d7c28 --- /dev/null +++ b/connectivity/drivers/nfc/TARGET_M24SR/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2020 ARM Limited. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +target_include_directories(mbed-nfc + INTERFACE + include + include/nfc +) + +target_sources(mbed-nfc + INTERFACE + source/m24sr_driver.cpp +) diff --git a/connectivity/drivers/nfc/TARGET_M24SR/include/nfc/m24sr_driver.h b/connectivity/drivers/nfc/TARGET_M24SR/include/nfc/m24sr_driver.h new file mode 100644 index 0000000000..3f7b623344 --- /dev/null +++ b/connectivity/drivers/nfc/TARGET_M24SR/include/nfc/m24sr_driver.h @@ -0,0 +1,1573 @@ +/* mbed Microcontroller Library + * SPDX-License-Identifier: BSD-3-Clause + ****************************************************************************** + * @file m24sr_driver.h + * @author ST Central Labs + * @brief This file provides a set of functions to interface with the M24SR + * device. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2018 STMicroelectronics

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form 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. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. + * + ****************************************************************************** + */ + +/* + Based on: X-CUBE-MEMS1/trunk/Drivers/BSP/Components/m24sr/m24sr.h + Revision: M24SR Driver V1.0.0 + */ + +#ifndef M24SR_H +#define M24SR_H + +#include +#include +#include "I2C.h" +#include "NFCEEPROMDriver.h" +#include "EventQueue.h" + +#if defined TARGET_DISCO_L475VG_IOT01A + +#define M24SR_I2C_SDA_PIN PB_11 +#define M24SR_I2C_SCL_PIN PB_10 +#define M24SR_GPO_PIN PE_4 +#define M24SR_RF_DISABLE_PIN PE_2 + +#elif MBED_CONF_X_NUCLEO_NFC01A1 + +#define M24SR_I2C_SDA_PIN D14 +#define M24SR_I2C_SCL_PIN D15 +#define M24SR_GPO_PIN D12 +#define M24SR_RF_DISABLE_PIN D11 + +#else + +#define M24SR_I2C_SDA_PIN NC +#define M24SR_I2C_SCL_PIN NC +#define M24SR_GPO_PIN NC +#define M24SR_RF_DISABLE_PIN NC + +#endif + +namespace mbed { +namespace nfc { +namespace vendor { +namespace ST { + +#define OPEN_SESSION_RETRIES 5 +#define CC_FILE_LENGTH 15 +#define NDEF_FILE_HEADER_SIZE 2 +#define MAX_NDEF_SIZE 0x1FFF + +/** + * User parameter used to invoke a command, + * it is used to provide the data back with the response + */ +struct CommandData_t { + uint8_t *data; /**< data */ + uint16_t length; /**< number of bytes in the data array */ + uint16_t offset; /**< offset parameter used in the read/write command */ +}; + +/** + * @brief APDU Command structure + */ +class C_APDU { +public: + struct C_APDUHeader_t { + uint8_t CLA; /**< Command class */ + uint8_t INS; /**< Operation code */ + uint8_t P1; /**< Selection Mode */ + uint8_t P2; /**< Selection Option */ + }; + + struct C_APDUBody_t { + uint8_t LC; /**< Data field length */ + const uint8_t *data; /**< Command parameters */ + uint8_t LE; /**< Expected length of data to be returned */ + }; + + C_APDU(uint8_t cla, uint8_t ins, uint16_t p1p2, uint8_t length, const uint8_t *data, uint8_t expected) + { + header.CLA = cla; + header.INS = ins; + header.P1 = (uint8_t)((p1p2 & 0xFF00) >> 8); + header.P2 = (uint8_t)(p1p2 & 0x00FF); + body.LC = length; + body.data = data; + body.LE = expected; + } + + C_APDUHeader_t header; + C_APDUBody_t body; +}; + +/** + * @brief SC response structure + */ +struct R_APDU { + uint8_t *data; /**< Data returned from the card */ // pointer on the transceiver buffer = ReaderRecBuf[CR95HF_DATA_OFFSET ]; + uint8_t SW1; /**< Command Processing status */ + uint8_t SW2; /**< Command Processing qualification */ +}; + +enum M24srError_t : uint16_t { + M24SR_SUCCESS = 0, + M24SR_ERROR = 0x6F00, + M24SR_FILE_OVERFLOW_LE = 0x6280, + M24SR_EOF = 0x6282, + M24SR_PASSWORD_REQUIRED = 0x6300, + M24SR_PASSWORD_INCORRECT = 0x63C0, + M24SR_PASSWORD_INCORRECT1RETRY = 0x63C1, + M24SR_PASSWORD_INCORRECT2RETRY = 0x63C2, + M24SR_WRONG_LENGHT = 0x6700, + M24SR_UNSUCESSFUL_UPDATING = 0x6581, + M24SR_INCOPATIBLE_COMMAND = 0x6981, + M24SR_SECURITY_UNSATISFIED = 0x6982, + M24SR_REFERENCE_DATA_NOT_USABLE = 0x6984, + + M24SR_INCORRECT_PARAMETER = 0x6a80, + M24SR_FILE_NOT_FOUND = 0x6a82, + M24SR_FILE_OVERFLOW_LC = 0x6A84, + + M24SR_INCORRECT_P1_OR_P2 = 0x6A86, + M24SR_RF_SESSION_KILLED = 0x6500, + M24SR_INS_NOT_SUPPORTED = 0x6D00, + M24SR_CLASS_NOT_SUPPORTED = 0x6E00, + + M24SR_IO_ERROR_I2CTIMEOUT = 0x0011, + M24SR_IO_ERROR_CRC = 0x0012, + M24SR_IO_ERROR_NACK = 0x0013, + M24SR_IO_ERROR_PARAMETER = 0x0014, + M24SR_IO_ERROR_NBATEMPT = 0x0015, + M24SR_IO_NOACKNOWLEDGE = 0x0016, + M24SR_IO_PIN_NOT_CONNECTED = 0x0017 +}; + +/** + * @brief GPO state + */ +enum NfcGpoState_t { + HIGH_IMPEDANCE = 0, + SESSION_OPENED = 1, + WIP = 2, + I2C_ANSWER_READY = 3, + INTERRUPT = 4, + STATE_CONTROL = 5 +}; + +/** + * Possible password to set. + */ +enum PasswordType_t { + READ_PASSWORD = 0x01, /**< Password to use before reading the tag */ + WRITE_PASSWORD = 0x02, /**< Password to use before writing the tag */ + I2C_PASSWORD = 0x03, /**< Root password, used only through nfc */ +}; + +/** + * Command that the component can accept + */ +enum Command_t { + NONE, + DESELECT, + SELECT_APPLICATION, + SELECT_CC_FILE, + SELECT_NDEF_FILE, + SELECT_SYSTEM_FILE, + READ, + UPDATE, + VERIFY, + MANAGE_I2C_GPO, + MANAGE_RF_GPO, + CHANGE_REFERENCE_DATA, + ENABLE_VERIFICATION_REQUIREMENT, + DISABLE_VERIFICATION_REQUIREMENT, + ENABLE_PERMANET_STATE, + DISABLE_PERMANET_STATE, +}; + +/** + * Communication mode used by this device + */ +enum Communication_t { + SYNC, /**< SYNC wait the command response before returning */ + ASYNC /**< ASYNC use a callback to notify the end of a command */ +}; + +/** + * Class representing a M24SR component. + * This component has two operation modes, sync or async. + * In sync mode each function call returns only after the command has completed. + * In async mode each function call returns immediately and the answer will be notified + * through a callback. + * The default behaviour is sync mode. + * To enable the async mode ManageI2CGPO(I2C_ANSWER_READY) function must be called. + * When the component notifies an interrupt user must call {@link ManageEvent} function. + * Note that passing a parameter other than I2C_ANSWER_READY to ManageI2CGPO initialize the component in sync mode. + */ +class M24srDriver : public NFCEEPROMDriver { +public: + /** + * Object that contains all the callbacks fired by this class, each command has its own callback. + * The callback default implementation is an empty function. + */ + class Callbacks { + public: + /** called when get_session completes */ + virtual void on_session_open(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when deselect completes */ + virtual void on_deselect(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when select_application completes */ + virtual void on_selected_application(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when select_cc_file completes */ + virtual void on_selected_cc_file(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when select_ndef_file completes */ + virtual void on_selected_ndef_file(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when select_system_file completes */ + virtual void on_selected_system_file(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when read_binary completes */ + virtual void on_read_byte(M24srDriver *nfc, M24srError_t status, uint16_t offset, uint8_t *bytes_read, + uint16_t read_count) + { + (void) nfc; + (void) status; + (void) offset; + (void) bytes_read; + (void) read_count; + } + + /** called when update_binary completes */ + virtual void on_updated_binary(M24srDriver *nfc, M24srError_t status, uint16_t offset, uint8_t *bytes_written, + uint16_t write_count) + { + (void) nfc; + (void) status; + (void) bytes_written; + (void) write_count; + (void) offset; + } + + /** called when verify completes */ + virtual void on_verified(M24srDriver *nfc, M24srError_t status, PasswordType_t password_type, const uint8_t *pwd) + { + (void) nfc; + (void) status; + (void) password_type; + (void) pwd; + } + + /** called when manage_i2c_gpo completes */ + virtual void on_manage_i2c_gpo(M24srDriver *nfc, M24srError_t status, NfcGpoState_t new_status) + { + (void) nfc; + (void) status; + (void) new_status; + } + + /** called when manage_rf_gpo completes */ + virtual void on_manage_rf_gpo(M24srDriver *nfc, M24srError_t status, NfcGpoState_t new_status) + { + (void) nfc; + (void) status; + (void) new_status; + } + + /** called when change_reference_data completes */ + virtual void on_change_reference_data(M24srDriver *nfc, M24srError_t status, PasswordType_t type, + const uint8_t *data) + { + (void) nfc; + (void) status; + (void) type; + (void) data; + } + + /** called when enable_verification_requirement completes */ + virtual void on_enable_verification_requirement(M24srDriver *nfc, M24srError_t status, PasswordType_t type) + { + (void) nfc; + (void) status; + (void) type; + } + + /** called when disable_verification_requirement completes */ + virtual void on_disable_verification_requirement(M24srDriver *nfc, M24srError_t status, PasswordType_t type) + { + (void) nfc; + (void) status; + (void) type; + } + + /** called when enable_permanent_state completes */ + virtual void on_enable_permanent_state(M24srDriver *nfc, M24srError_t status, PasswordType_t type) + { + (void) nfc; + (void) status; + (void) type; + } + + /** called when disable_permanent_state completes */ + virtual void on_disable_permanent_state(M24srDriver *nfc, M24srError_t status, PasswordType_t type) + { + (void) nfc; + (void) status; + (void) type; + } + + /** called when read_id completes */ + virtual void on_read_id(M24srDriver *nfc, M24srError_t status, uint8_t *id) + { + (void) nfc; + (void) status; + (void) id; + } + + /** called when enable_read_password completes */ + virtual void on_enable_read_password(M24srDriver *nfc, M24srError_t status, const uint8_t *new_password) + { + (void) nfc; + (void) status; + (void) new_password; + } + + /** called when oenable_write_password completes */ + virtual void on_enable_write_password(M24srDriver *nfc, M24srError_t status, const uint8_t *new_password) + { + (void) nfc; + (void) status; + (void) new_password; + } + + /** called when disable_read_password completes */ + virtual void on_disable_read_password(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when disable_write_password completes */ + virtual void on_disable_write_password(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when disable_all_password completes */ + virtual void on_disable_all_password(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when enable_read_only completes */ + virtual void on_enable_read_only(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when enable_write_only completes */ + virtual void on_enable_write_only(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when disable_read_only completes */ + virtual void on_disable_read_only(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + /** called when disable_write_only completes */ + virtual void on_disable_write_only(M24srDriver *nfc, M24srError_t status) + { + (void) nfc; + (void) status; + } + + virtual ~Callbacks() { } + }; + +public: + /** Create the driver, default pin names will be used appropriate for the board. + * @param i2c_data_pin I2C data pin name. + * @param i2c_clock_pin I2C clock pin name. + * @param gpo_pin I2C GPO pin name. + * @param rf_disable_pin pin name for breaking the RF connection. + */ + M24srDriver(PinName i2c_data_pin = M24SR_I2C_SDA_PIN, PinName i2c_clock_pin = M24SR_I2C_SCL_PIN, + PinName gpo_pin = M24SR_GPO_PIN, PinName rf_disable_pin = M24SR_RF_DISABLE_PIN); + + virtual ~M24srDriver() { } + + /** @see NFCEEPROMDriver::reset + */ + virtual void reset() + { + set_callback(&_default_cb); + init(); + manage_i2c_gpo(I2C_ANSWER_READY); + } + + /** @see NFCEEPROMDriver::get_max_size + */ + virtual size_t read_max_size() + { + return MAX_NDEF_SIZE; + } + + /** @see NFCEEPROMDriver::start_session + */ + virtual void start_session(bool force = true) + { + if (_is_session_open) { + delegate()->on_session_started(true); + return; + } + + set_callback(&_open_session_cb); + + get_session(force); + } + + /** @see NFCEEPROMDriver::end_session + */ + virtual void end_session() + { + set_callback(&_close_session_cb); + deselect(); + } + + /** @see NFCEEPROMDriver::read_bytes + */ + virtual void read_bytes(uint32_t address, uint8_t *bytes, size_t count) + { + if (!_is_session_open) { + delegate()->on_bytes_read(0); + return; + } + + if (address > _ndef_size) { + delegate()->on_bytes_read(0); + return; + } + + set_callback(&_read_byte_cb); + + if (count > _max_read_bytes) { + count = _max_read_bytes; + } + + if (address + count > _ndef_size) { + count = _ndef_size - address; + } + + if (count == 0) { + delegate()->on_bytes_read(0); + return; + } + + /* offset by ndef file size*/ + address += NDEF_FILE_HEADER_SIZE; + + read_binary((uint16_t) address, (uint8_t) count, bytes); + } + + /** @see NFCEEPROMDriver::write_bytes + */ + virtual void write_bytes(uint32_t address, const uint8_t *bytes, size_t count) + { + if (!_is_session_open) { + delegate()->on_bytes_written(0); + return; + } + + if (address > _ndef_size) { + delegate()->on_bytes_written(0); + return; + } + + if (bytes) { + set_callback(&_write_byte_cb); + } else { + set_callback(&_erase_bytes_cb); + } + + if (count > _max_write_bytes) { + count = _max_write_bytes; + } + + if (address + count > _ndef_size) { + count = _ndef_size - address; + } + + if (count == 0) { + delegate()->on_bytes_written(0); + return; + } + + /* offset by ndef file size*/ + address += NDEF_FILE_HEADER_SIZE; + + update_binary((uint16_t) address, (uint8_t) count, bytes); + } + + /** @see NFCEEPROMDriver::set_size + */ + virtual void write_size(size_t count) + { + if (!_is_session_open) { + delegate()->on_size_read(false, 0); + return; + } + + if (count > MAX_NDEF_SIZE - NDEF_FILE_HEADER_SIZE) { + delegate()->on_size_read(false, 0); + return; + } + + set_callback(&_set_size_cb); + + _ndef_size = (uint16_t)count; + + /* NDEF file size is BE */ + uint8_t *bytes = (uint8_t *)&_ndef_size; + _ndef_size_buffer[0] = bytes[1]; + _ndef_size_buffer[1] = bytes[0]; + + update_binary(0, NDEF_FILE_HEADER_SIZE, (const uint8_t *)&_ndef_size_buffer); + } + + /** @see NFCEEPROMDriver::get_size + */ + virtual void read_size() + { + if (!_is_session_open) { + delegate()->on_size_read(false, 0); + return; + } + + set_callback(&_get_size_cb); + + read_binary(0, NDEF_FILE_HEADER_SIZE, (uint8_t *)&_ndef_size_buffer); + } + + /** @see NFCEEPROMDriver::erase_bytes + */ + virtual void erase_bytes(uint32_t address, size_t size) + { + write_bytes(address, NULL, size); + } + +private: + /** + * Change the function to call when a command ends. + * @param commandCallback Object containing the callback, if NULL it will use empty callback + */ + void set_callback(Callbacks *callback) + { + if (callback) { + _command_cb = callback; + } else { + _command_cb = &_default_cb; + } + } + + /** + * get the callback object to use + * @return callback object to use + */ + Callbacks *get_callback() + { + /* this allows for two levels of operation, the previous command will continue + * when this set of callbacks has finished */ + if (_subcommand_cb) { + return _subcommand_cb; + } + return _command_cb; + } + + void nfc_interrupt_callback() + { + if (_communication_type == ASYNC) { + event_queue()->call(this, &M24srDriver::manage_event); + } + } + + /** + * Enable the request of a password before reading the tag. + * @param current_write_password Current password + * @param new_password Password to request before reading the tag. + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t enable_read_password(const uint8_t *current_write_password, const uint8_t *new_password) + { + _subcommand_cb = &_change_password_request_status_cb; + _change_password_request_status_cb.set_task(READ_PASSWORD, new_password); + + return verify(WRITE_PASSWORD, current_write_password); + } + + /** + * Disable the request of a password before reading the tag. + * @param current_write_password Current password + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t disable_read_password(const uint8_t *current_write_password) + { + _subcommand_cb = &_change_password_request_status_cb; + _change_password_request_status_cb.set_task(READ_PASSWORD, NULL); + + return verify(WRITE_PASSWORD, current_write_password); + } + + /** + * Enable the request of a password before writing to the tag. + * @param current_write_password Current password + * @param new_password Password to request before reading the tag. + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t enable_write_password(const uint8_t *current_write_password, uint8_t *new_password) + { + _subcommand_cb = &_change_password_request_status_cb; + _change_password_request_status_cb.set_task(WRITE_PASSWORD, new_password); + + return verify(WRITE_PASSWORD, current_write_password); + } + + /** + * Disable the request of a password before writing the tag. + * @param current_write_password Current password. + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t disable_write_password(const uint8_t *current_write_password) + { + _subcommand_cb = &_change_password_request_status_cb; + _change_password_request_status_cb.set_task(WRITE_PASSWORD, NULL); + + return verify(WRITE_PASSWORD, current_write_password); + } + + /** + * @brief This function disables both read and write passwords. + * @param super_user_password I2C super user password. + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t disable_all_password(const uint8_t *super_user_password) + { + _subcommand_cb = &_remove_password_cb; + return verify(I2C_PASSWORD, super_user_password); + } + + /** + * @brief This function enables read only mode. + * @param current_write_password Write password is needed to enable read only mode. + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t enable_read_only(const uint8_t *current_write_password) + { + _subcommand_cb = &_change_access_state_cb; + _change_access_state_cb.change_access_state(ChangeAccessStateCallback::WRITE, false); + + return verify(WRITE_PASSWORD, current_write_password); + } + + /** + * @brief This function disables read only mode. + * @param current_write_password Write password is needed to disable read only mode. + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t disable_read_only(const uint8_t *current_write_password) + { + _subcommand_cb = &_change_access_state_cb; + _change_access_state_cb.change_access_state(ChangeAccessStateCallback::WRITE, true); + + return verify(I2C_PASSWORD, current_write_password); + } + + /** + * @brief This function enables write only mode. + * @param current_write_password Write password is needed to enable write only mode. + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t enable_write_only(const uint8_t *current_write_password) + { + _subcommand_cb = &_change_access_state_cb; + _change_access_state_cb.change_access_state(ChangeAccessStateCallback::READ, false); + + return verify(WRITE_PASSWORD, current_write_password); + } + + /** + * @brief This function disables write only mode. + * @param current_write_password Write password is needed to disable write only mode. + * @return return M24SR_SUCCESS if no errors + * @note The password must have a length of 16 chars. + */ + M24srError_t disable_write_only(const uint8_t *current_write_password) + { + _subcommand_cb = &_change_access_state_cb; + _change_access_state_cb.change_access_state(ChangeAccessStateCallback::READ, true); + + return verify(I2C_PASSWORD, current_write_password); + } + +private: + M24srError_t init(); + M24srError_t read_id(uint8_t *nfc_id); + M24srError_t get_session(bool force = false); + + M24srError_t deselect(); + M24srError_t receive_deselect(); + + M24srError_t select_application(); + M24srError_t receive_select_application(); + + M24srError_t select_cc_file(); + M24srError_t receive_select_cc_file(); + + M24srError_t select_ndef_file(uint16_t ndef_file_id); + M24srError_t receive_select_ndef_file(); + + M24srError_t select_system_file(); + M24srError_t receive_select_system_file(); + + M24srError_t read_binary(uint16_t offset, uint8_t length, uint8_t *buffer); + M24srError_t st_read_binary(uint16_t offset, uint8_t length, uint8_t *buffer); + M24srError_t receive_read_binary(); + + M24srError_t update_binary(uint16_t offset, uint8_t length, const uint8_t *data); + M24srError_t receive_update_binary(); + + M24srError_t verify(PasswordType_t password_type, const uint8_t *password); + M24srError_t receive_verify(); + + M24srError_t change_reference_data(PasswordType_t password_type, const uint8_t *password); + M24srError_t receive_change_reference_data(); + + M24srError_t enable_verification_requirement(PasswordType_t password_type); + M24srError_t receive_enable_verification_requirement(); + + M24srError_t disable_verification_requirement(PasswordType_t password_type); + M24srError_t receive_disable_verification_requirement(); + + M24srError_t enable_permanent_state(PasswordType_t password_type); + M24srError_t receive_enable_permanent_state(); + + M24srError_t disable_permanent_state(PasswordType_t password_type); + M24srError_t receive_disable_permanent_state(); + + M24srError_t send_interrupt(); + M24srError_t state_control(bool gpo_reset); + + M24srError_t manage_i2c_gpo(NfcGpoState_t gpo_i2c_config); + M24srError_t manage_rf_gpo(NfcGpoState_t gpo_rf_config); + + M24srError_t rf_config(bool enable); + M24srError_t send_fwt_extension(uint8_t fwt_byte); + + M24srError_t send_receive_i2c(uint16_t length, uint8_t *command); + + /** + * Function to call when the component fire an interrupt. + * @return last operation status + */ + M24srError_t manage_event(); + + /** + * Send a command to the component. + * @param length Length of the command. + * @param command Buffer containing the command. + * @return M24SR_SUCCESS if no errors + */ + M24srError_t io_send_i2c_command(uint8_t length, const uint8_t *command); + + /** + * Read a command response. + * @param length Number of bytes to read. + * @param command Buffer to store the response into. + * @return M24SR_SUCCESS if no errors + */ + M24srError_t io_receive_i2c_response(uint8_t length, uint8_t *command); + + /** + * Do an active polling on the I2C bus until the answer is ready. + * @return M24SR_SUCCESS if no errors + */ + M24srError_t io_poll_i2c(); + + bool manage_sync_communication(M24srError_t *status); + +private: + /** + * @brief This class permits to enable/disable the password request to read/write into the tag + */ + class ChangePasswordRequestStatusCallback : public Callbacks { + public: + /** + * Build the chain of callbacks. + */ + ChangePasswordRequestStatusCallback() + : _new_password(NULL), + _type(I2C_PASSWORD), + _enable(false) { } + + /* This class is equivalent to calling the methods: + * + * To enable the request: + * - Verify + * - change_reference_data + * - enable_permanent_state + * + * To disable the request: + * - verify + * - disable_verification_requirement + */ + + /** + * Set the password to enable/disable. + * @param type Type of password to enable/disable. + * @param new_password Array of 16bytes with the new password, if null the request will be disabled. + */ + void set_task(PasswordType_t type, const uint8_t *new_password) + { + _new_password = new_password; + _type = type; + _enable = (new_password != NULL); + } + + virtual void on_verified(M24srDriver *nfc, M24srError_t status, PasswordType_t, const uint8_t *) + { + if (status != M24SR_SUCCESS) { + return on_finish_command(nfc, status); + } + if (_enable) { + nfc->change_reference_data(_type, _new_password); + } else { + nfc->disable_verification_requirement(_type); + } + } + + virtual void on_disable_verification_requirement(M24srDriver *nfc, M24srError_t status, PasswordType_t) + { + on_finish_command(nfc, status); + } + + virtual void on_change_reference_data(M24srDriver *nfc, M24srError_t status, PasswordType_t type, const uint8_t *) + { + if (status == M24SR_SUCCESS) { + nfc->enable_permanent_state(type); + } else { + on_finish_command(nfc, status); + } + } + + virtual void on_enable_permanent_state(M24srDriver *nfc, M24srError_t status, PasswordType_t) + { + on_finish_command(nfc, status); + } + + private: + /** + * Remove the private callbacks and call the user callback. + * @param nfc Object triggering the command. + * @param status Command status. + */ + void on_finish_command(M24srDriver *nfc, M24srError_t status) + { + nfc->_subcommand_cb = NULL; + + if (_enable) { + if (_type == READ_PASSWORD) { + nfc->get_callback()->on_enable_read_password(nfc, status, _new_password); + } else { + nfc->get_callback()->on_enable_write_password(nfc, status, _new_password); + } + } else { + if (_type == READ_PASSWORD) { + nfc->get_callback()->on_disable_read_password(nfc, status); + } else { + nfc->get_callback()->on_disable_write_password(nfc, status); + } + } + } + + private: + const uint8_t *_new_password; + PasswordType_t _type; + bool _enable; + }; + + /** + * @brief This class permits to disable all the password requests to read/write into the tag. + */ + class RemoveAllPasswordCallback : public Callbacks { + public: + /** + * Build the chain of callbacks. + */ + RemoveAllPasswordCallback() + : _password(NULL) { } + + /* This class is equivalent to calling the methods: + * - verify(i2c) + * - disable_permanent_state(Read) + * - disable_permanent_state(write) + * - disable_verification_requirement(Read) + * - disable_verification_requirement(write) + * - change_reference_data(Read) + * - change_reference_data(write) + */ + + virtual void on_verified(M24srDriver *nfc, M24srError_t status, PasswordType_t, const uint8_t *data) + { + if (status != M24SR_SUCCESS) { + return on_finish_command(nfc, status); + } + _password = data; + nfc->disable_permanent_state(READ_PASSWORD); + } + + virtual void on_disable_permanent_state(M24srDriver *nfc, M24srError_t status, PasswordType_t type) + { + if (status != M24SR_SUCCESS) { + return on_finish_command(nfc, status); + } + if (type == READ_PASSWORD) { + nfc->disable_permanent_state(WRITE_PASSWORD); + } else { + nfc->disable_verification_requirement(READ_PASSWORD); + } + } + + virtual void on_disable_verification_requirement(M24srDriver *nfc, M24srError_t status, PasswordType_t type) + { + if (status != M24SR_SUCCESS) { + return on_finish_command(nfc, status); + } + if (type == READ_PASSWORD) { + nfc->disable_verification_requirement(WRITE_PASSWORD); + } else { + nfc->change_reference_data(READ_PASSWORD, _password); + } + } + + virtual void on_change_reference_data(M24srDriver *nfc, M24srError_t status, PasswordType_t type, + const uint8_t *data) + { + if (status != M24SR_SUCCESS) { + return on_finish_command(nfc, status); + } + if (type == READ_PASSWORD) { + nfc->change_reference_data(WRITE_PASSWORD, data); + } else { + on_finish_command(nfc, status); + } + } + + private: + /** + * Remove the private callback and call the onDisableAllPassword callback. + * @param nfc Object triggering the command. + * @param status Command status. + */ + void on_finish_command(M24srDriver *nfc, M24srError_t status) + { + nfc->_subcommand_cb = NULL; + _password = NULL; + nfc->get_callback()->on_disable_all_password(nfc, status); + } + + private: + /** + * Store the default password used for open a super user session + * it will be set as default read/write password + */ + const uint8_t *_password; + }; + + /** + * @brief This class permits to set the tag as read/write only. + */ + class ChangeAccessStateCallback : public Callbacks { + public: + enum AccessType_t { + WRITE, + READ + }; + + /** + * Build the chain of callbacks. + */ + ChangeAccessStateCallback() + : _type(WRITE), + _enable(false) { } + + /* This class is equivalent to calling the methods: + * - verify(i2c) + * - enable_permanent_state(Read/write) + * or: + * - verify(i2c) + * - disable_permanent_state + * - disable_verification_requirement(Read/write) + */ + + /** + * Set the access to enable/disable an access type. + * @param type Access type. + * @param enable True to enable the state, False to disable it. + */ + void change_access_state(AccessType_t type, bool enable) + { + _type = type; + _enable = enable; + } + + virtual void on_verified(M24srDriver *nfc, M24srError_t status, PasswordType_t, const uint8_t *) + { + if (status != M24SR_SUCCESS) { + return on_finish_command(nfc, status); + } + + if (_enable) { + nfc->disable_permanent_state(_type == WRITE ? WRITE_PASSWORD : READ_PASSWORD); + } else { + nfc->enable_permanent_state(_type == WRITE ? WRITE_PASSWORD : READ_PASSWORD); + } + + } + + virtual void on_disable_permanent_state(M24srDriver *nfc, M24srError_t status, PasswordType_t type) + { + if (status != M24SR_SUCCESS) { + return on_finish_command(nfc, status); + } + + nfc->disable_verification_requirement(type); + } + + virtual void on_disable_verification_requirement(M24srDriver *nfc, M24srError_t status, PasswordType_t) + { + on_finish_command(nfc, status); + } + + virtual void on_enable_permanent_state(M24srDriver *nfc, M24srError_t status, PasswordType_t) + { + on_finish_command(nfc, status); + } + + private: + /** + * Remove the private callback and call the user callback. + * @param nfc Object triggering the command. + * @param status Command status. + */ + void on_finish_command(M24srDriver *nfc, M24srError_t status) + { + nfc->_subcommand_cb = NULL; + if (_enable) { + if (_type == READ) { + //enable read = disable write only + nfc->get_callback()->on_disable_write_only(nfc, status); + } else { + //enable write = disable read only + nfc->get_callback()->on_disable_read_only(nfc, status); + } + } else { + if (_type == WRITE) { + //disable write = enable read only + nfc->get_callback()->on_enable_read_only(nfc, status); + } else { + nfc->get_callback()->on_enable_write_only(nfc, status); + } + } + } + + private: + AccessType_t _type; + bool _enable; + }; + + /** + * @brief Object with the callback used to send a ManageGPO command. + */ + class ManageGPOCallback : public Callbacks { + + public: + /** + * Build the chain of callbacks. + * @param parent Parent component to run the command on. + */ + ManageGPOCallback() + : _new_gpo_config(HIGH_IMPEDANCE), + _read_gpo_config(0), + _change_i2c_gpo(true) { } + + /* This class is equivalent to calling the methods: + * - selected_application + * - select_system_file + * - read_binary + * - verify + * - update_binary + */ + + /** + * Command parameters. + * @param i2cGpo true to change the i2c gpo, false for the rf gpo. + * @param new_config new gpo function. + */ + void set_new_gpo_config(bool i2cGpo, NfcGpoState_t new_config) + { + _new_gpo_config = new_config; + _change_i2c_gpo = i2cGpo; + } + + virtual void on_selected_application(M24srDriver *nfc, M24srError_t status) + { + if (status == M24SR_SUCCESS) { + nfc->select_system_file(); + } else { + on_finish_command(nfc, status); + } + } + + virtual void on_selected_system_file(M24srDriver *nfc, M24srError_t status) + { + if (status == M24SR_SUCCESS) { + nfc->read_binary(0x0004, 0x01, &_read_gpo_config); + } else { + on_finish_command(nfc, status); + } + } + + virtual void on_read_byte(M24srDriver *nfc, M24srError_t status, uint16_t, uint8_t *, uint16_t) + { + if (status == M24SR_SUCCESS) { + nfc->verify(I2C_PASSWORD, default_password); + } else { + on_finish_command(nfc, status); + } + } + + virtual void on_verified(M24srDriver *nfc, M24srError_t status, PasswordType_t, const uint8_t *) + { + if (status != M24SR_SUCCESS) { + return on_finish_command(nfc, status); + } + + if (_change_i2c_gpo) { + _read_gpo_config = (_read_gpo_config & 0xF0) | (uint8_t) _new_gpo_config; + } else { + _read_gpo_config = (_read_gpo_config & 0x0F) | (((uint8_t) _new_gpo_config) << 4); + } + + nfc->update_binary(0x0004, 0x01, &_read_gpo_config); + } + + virtual void on_updated_binary(M24srDriver *nfc, M24srError_t status, uint16_t, uint8_t *, uint16_t) + { + + if (status == M24SR_SUCCESS) { + if (_new_gpo_config == I2C_ANSWER_READY) { + nfc->_communication_type = ASYNC; + } else { + nfc->_communication_type = SYNC; + } + } + on_finish_command(nfc, status); + } + + private: + /** + * Remove the private callback and call the user callback. + * @param nfc Object where the command was send to. + * @param status Command status. + */ + void on_finish_command(M24srDriver *nfc, M24srError_t status) + { + nfc->_subcommand_cb = NULL; + if (_change_i2c_gpo) { + nfc->_command_cb->on_manage_i2c_gpo(nfc, status, _new_gpo_config); + } else { + nfc->_command_cb->on_manage_rf_gpo(nfc, status, _new_gpo_config); + } + } + + private: + /** new gpo function that this class has to write */ + NfcGpoState_t _new_gpo_config; + + /** variable where storeing the read gpo configuration */ + uint8_t _read_gpo_config; + + /** true to change the i2c gpo, false to change the rf gpo */ + bool _change_i2c_gpo; + }; + + /** + * @brief Object with the callback used to read the component ID + */ + class ReadIDCallback : public Callbacks { + public: + /** + * Build the chain of callbacks. + * @param parent object where to send the command to. + */ + ReadIDCallback() : _id(NULL) { } + + /* This class is equivalent to calling the methods: + * - select_application + * - select_system_file + * - read_binary + */ + + /** + * Set the variable containing the result + * @param idPtr + */ + void set_task(uint8_t *id) + { + _id = id; + } + + virtual void on_selected_application(M24srDriver *nfc, M24srError_t status) + { + if (status == M24SR_SUCCESS) { + nfc->select_system_file(); + } else { + on_finish_command(nfc, status); + } + + } + + virtual void on_selected_system_file(M24srDriver *nfc, M24srError_t status) + { + if (status == M24SR_SUCCESS) { + nfc->read_binary(0x0011, 0x01, _id); + } else { + on_finish_command(nfc, status); + } + } + + virtual void on_read_byte(M24srDriver *nfc, M24srError_t status, uint16_t, uint8_t *, uint16_t) + { + on_finish_command(nfc, status); + } + + private: + /** + * Remove the private callback and call the user onReadId function. + * @param nfc Object where the command was send. + * @param status Command status. + */ + void on_finish_command(M24srDriver *nfc, M24srError_t status) + { + nfc->_subcommand_cb = NULL; + nfc->get_callback()->on_read_id(nfc, status, _id); + } + + private: + /** pointer to read id */ + uint8_t *_id; + }; + + /** + * Class containing the callback needed to open a session and read the max + * read/write size + */ + class OpenSessionCallBack : public Callbacks { + public: + OpenSessionCallBack() + : _retries(OPEN_SESSION_RETRIES) { } + + void on_session_open(M24srDriver *nfc, M24srError_t status) + { + if (status == M24SR_SUCCESS) { + nfc->select_application(); + } else { + nfc->delegate()->on_session_started(false); + } + } + + void on_selected_application(M24srDriver *nfc, M24srError_t status) + { + if (status == M24SR_SUCCESS) { + nfc->select_cc_file(); + } else { + if (_retries == 0) { + nfc->delegate()->on_session_started(false); + } else { + _retries--; + nfc->select_application(); + } + } + } + + void on_selected_cc_file(M24srDriver *nfc, M24srError_t status) + { + if (status == M24SR_SUCCESS) { + nfc->read_binary(0x0000, CC_FILE_LENGTH, CCFile); + } else { + nfc->delegate()->on_session_started(false); + } + } + + void on_read_byte(M24srDriver *nfc, M24srError_t status, uint16_t, uint8_t *bytes_read, + uint16_t read_count) + { + if (status != M24SR_SUCCESS || read_count != CC_FILE_LENGTH) { + nfc->delegate()->on_session_started(false); + } + uint16_t ndef_file_id = (uint16_t)((bytes_read[0x09] << 8) | bytes_read[0x0A]); + nfc->_max_read_bytes = (uint16_t)((bytes_read[0x03] << 8) | bytes_read[0x04]); + nfc->_max_write_bytes = (uint16_t)((bytes_read[0x05] << 8) | bytes_read[0x06]); + nfc->select_ndef_file(ndef_file_id); + } + + void on_selected_ndef_file(M24srDriver *nfc, M24srError_t status) + { + nfc->_is_session_open = (status == M24SR_SUCCESS); + nfc->delegate()->on_session_started(nfc->_is_session_open); + } + + private: + /** number of trials done for open the session */ + uint32_t _retries; + + /** buffer where read the CC file */ + uint8_t CCFile[15]; + }; + + /** + * Class containing the callback needed to close a session + */ + class CloseSessionCallBack : public Callbacks { + public: + CloseSessionCallBack() { } + + virtual void on_deselect(M24srDriver *nfc, M24srError_t status) + { + if (status == M24SR_SUCCESS) { + nfc->_is_session_open = false; + nfc->delegate()->on_session_ended(true); + } else { + nfc->delegate()->on_session_ended(false); + } + } + }; + + /** + * Class containing the callback needed to write a buffer + */ + class WriteByteCallback : public Callbacks { + public: + WriteByteCallback() { } + + virtual void on_updated_binary(M24srDriver *nfc, M24srError_t status, uint16_t offset, uint8_t *bytes_written, + uint16_t write_count) + { + if (status != M24SR_SUCCESS) { + nfc->delegate()->on_bytes_written(0); + return; + } + + nfc->delegate()->on_bytes_written(write_count); + } + }; + + /** + * Class containing the callback needed to read a buffer + */ + class ReadByteCallback : public Callbacks { + public: + ReadByteCallback() { } + + virtual void on_read_byte(M24srDriver *nfc, M24srError_t status, uint16_t offset, uint8_t *bytes_read, + uint16_t read_count) + { + if (status != M24SR_SUCCESS) { + nfc->delegate()->on_bytes_read(0); + return; + } + + nfc->delegate()->on_bytes_read(read_count); + } + }; + + class SetSizeCallback : public Callbacks { + public: + SetSizeCallback() { } + + virtual void on_updated_binary(M24srDriver *nfc, M24srError_t status, uint16_t offset, uint8_t *bytes_written, + uint16_t write_count) + { + if (status != M24SR_SUCCESS) { + nfc->delegate()->on_size_written(false); + return; + } + + nfc->delegate()->on_size_written(true); + } + }; + + class GetSizeCallback : public Callbacks { + public: + GetSizeCallback() { } + + virtual void on_read_byte(M24srDriver *nfc, M24srError_t status, uint16_t offset, uint8_t *bytes_read, + uint16_t read_count) + { + if (status != M24SR_SUCCESS) { + nfc->delegate()->on_size_read(false, 0); + return; + } + + /* NDEF file size is BE */ + nfc->_ndef_size = (((uint16_t) nfc->_ndef_size_buffer[0]) << 8 | nfc->_ndef_size_buffer[1]); + + nfc->delegate()->on_size_read(true, nfc->_ndef_size); + } + }; + + class EraseBytesCallback : public Callbacks { + public: + EraseBytesCallback() { } + + virtual void on_updated_binary(M24srDriver *nfc, M24srError_t status, uint16_t offset, uint8_t *bytes_written, + uint16_t write_count) + { + if (status != M24SR_SUCCESS) { + nfc->delegate()->on_bytes_erased(0); + return; + } + + nfc->delegate()->on_bytes_erased(write_count); + } + }; + +private: + /** Default password used to change the write/read permission */ + static const uint8_t default_password[16]; + + I2C _i2c_channel; + + /** Interrupt object fired when the gpo status changes */ + InterruptIn _gpo_event_interrupt; + DigitalIn _gpo_pin; + DigitalOut _rf_disable_pin; + + /** object containing the callbacks to use*/ + Callbacks *_command_cb; + + /** + * Object with private callbacks used to hide high level commands each + * calling multiple low level commands. This callbacks object has + * higher priority comparing to the user callbacks. + */ + Callbacks *_subcommand_cb; + + Callbacks _default_cb; + ManageGPOCallback _manage_gpo_cb; + ReadIDCallback _read_id_cb; + ChangePasswordRequestStatusCallback _change_password_request_status_cb; + RemoveAllPasswordCallback _remove_password_cb; + ChangeAccessStateCallback _change_access_state_cb; + OpenSessionCallBack _open_session_cb; + CloseSessionCallBack _close_session_cb; + WriteByteCallback _write_byte_cb; + ReadByteCallback _read_byte_cb; + SetSizeCallback _set_size_cb; + GetSizeCallback _get_size_cb; + EraseBytesCallback _erase_bytes_cb; + + + uint8_t _buffer[0xFF]; + + /** Type of communication being used (SYNC, ASYNC) */ + Communication_t _communication_type; + + Command_t _last_command; + CommandData_t _last_command_data; + + /** Buffer used to build the command to send to the chip. */ + uint16_t _ndef_size; + uint8_t _ndef_size_buffer[NDEF_FILE_HEADER_SIZE]; + uint8_t _max_read_bytes; + uint8_t _max_write_bytes; + uint8_t _did_byte; + + bool _is_session_open; +}; + +} //ST +} //vendor +} //nfc +} //mbed + +#endif // M24SR_H + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/connectivity/drivers/nfc/TARGET_M24SR/mbed_lib.json b/connectivity/drivers/nfc/TARGET_M24SR/mbed_lib.json new file mode 100644 index 0000000000..e30eb925bb --- /dev/null +++ b/connectivity/drivers/nfc/TARGET_M24SR/mbed_lib.json @@ -0,0 +1,15 @@ +{ + "name": "M24SR-nfc", + "config": { + "X_NUCLEO_NFC01A1": { + "macro_name": "MBED_CONF_X_NUCLEO_NFC01A1", + "value": false, + "help": "Device is using an X-NUCLEO-NFC01A1 shield attached through an arduino header" + }, + "nfceeprom": { + "macro_name": "MBED_CONF_NFCEEPROM", + "value": true, + "help": "Device supports NFC EEPROM" + } + } +} diff --git a/connectivity/drivers/nfc/TARGET_M24SR/source/m24sr_driver.cpp b/connectivity/drivers/nfc/TARGET_M24SR/source/m24sr_driver.cpp new file mode 100644 index 0000000000..f0b06fa377 --- /dev/null +++ b/connectivity/drivers/nfc/TARGET_M24SR/source/m24sr_driver.cpp @@ -0,0 +1,1465 @@ +/* mbed Microcontroller Library + * SPDX-License-Identifier: BSD-3-Clause + ****************************************************************************** + * @file m24sr_driver.cpp + * @author ST Central Labs + * @brief This file provides a set of functions to interface with the M24SR + * device. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2018 STMicroelectronics

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form 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. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. + * + ****************************************************************************** + */ + +#include +#include "Callback.h" + +namespace mbed { +namespace nfc { +namespace vendor { +namespace ST { + +#define MAX_OPERATION_SIZE 246 +#define MAX_PAYLOAD 241 + +/** value returned by the NFC chip when a command is successfully completed */ +static constexpr const uint16_t NFC_COMMAND_SUCCESS = 0x9000; +/** I2C nfc address */ +#define M24SR_ADDR 0xAC + +#define SYSTEM_FILE_ID_BYTES {0xE1,0x01} +#define CC_FILE_ID_BYTES {0xE1,0x03} + +#define UB_STATUS_OFFSET 4 +#define LB_STATUS_OFFSET 3 + +/* APDU command: class list */ +#define C_APDU_CLA_DEFAULT 0x00 +#define C_APDU_CLA_ST 0xA2 + +/* data area management commands */ +#define C_APDU_SELECT_FILE 0xA4 +#define C_APDU_GET_RESPONCE 0xC0 +#define C_APDU_STATUS 0xF2 +#define C_APDU_UPDATE_BINARY 0xD6 +#define C_APDU_READ_BINARY 0xB0 +#define C_APDU_WRITE_BINARY 0xD0 +#define C_APDU_UPDATE_RECORD 0xDC +#define C_APDU_READ_RECORD 0xB2 + +/* safety management commands */ +#define C_APDU_VERIFY 0x20 +#define C_APDU_CHANGE 0x24 +#define C_APDU_DISABLE 0x26 +#define C_APDU_ENABLE 0x28 + +/* GPO management commands */ +#define C_APDU_INTERRUPT 0xD6 + +/* length */ +#define STATUS_LENGTH 2 +#define CRC_LENGTH 2 +#define STATUS_RESPONSE_LENGTH 5 +#define DESELECT_RESPONSE_LENGTH 3 +#define WATING_TIME_EXT_RESPONSE_LENGTH 4 +#define PASSWORD_LENGTH 16 + +#define DESELECT_REQUEST_COMMAND {0xC2,0xE0,0xB4} +#define SELECT_APPLICATION_COMMAND {0xD2,0x76,0x00,0x00,0x85,0x01,0x01} + +/* command structure mask */ +#define CMD_MASK_SELECT_APPLICATION 0x01FF +#define CMD_MASK_SELECT_CC_FILE 0x017F +#define CMD_MASK_SELECT_NDEF_FILE 0x017F +#define CMD_MASK_READ_BINARY 0x019F +#define CMD_MASK_UPDATE_BINARY 0x017F +#define CMD_MASK_VERIFY_BINARY_WO_PWD 0x013F +#define CMD_MASK_VERIFY_BINARY_WITH_PWD 0x017F +#define CMD_MASK_CHANGE_REF_DATA 0x017F +#define CMD_MASK_ENABLE_VERIFREQ 0x011F +#define CMD_MASK_DISABLE_VERIFREQ 0x011F +#define CMD_MASK_SEND_INTERRUPT 0x013F +#define CMD_MASK_GPO_STATE 0x017F + +/* command structure values for the mask */ +#define PCB_NEEDED 0x0001 /* PCB byte present or not */ +#define CLA_NEEDED 0x0002 /* CLA byte present or not */ +#define INS_NEEDED 0x0004 /* Operation code present or not*/ +#define P1_NEEDED 0x0008 /* Selection Mode present or not*/ +#define P2_NEEDED 0x0010 /* Selection Option present or not*/ +#define LC_NEEDED 0x0020 /* Data field length byte present or not */ +#define DATA_NEEDED 0x0040 /* Data present or not */ +#define LE_NEEDED 0x0080 /* Expected length present or not */ +#define CRC_NEEDED 0x0100 /* 2 CRC bytes present or not */ +#define DID_NEEDED 0x08 /* DID byte present or not */ + +/* offset */ +#define OFFSET_PCB 0 +#define OFFSET_CLASS 1 +#define OFFSET_INS 2 +#define OFFSET_P1 3 + +/* mask */ +#define MASK_BLOCK 0xC0 +#define MASK_I_BLOCK 0x00 +#define MASK_R_BLOCK 0x80 +#define MASK_S_BLOCK 0xC0 + +#define GETMSB(val) ((uint8_t) ((val & 0xFF00)>>8)) +#define GETLSB(val) ((uint8_t) (val & 0x00FF)) + +/** default password, also used to enable super user mode through the I2C channel */ +const uint8_t M24srDriver::default_password[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/** + * @brief This function updates the CRC + */ +static uint16_t update_crc(uint8_t ch, uint16_t *lpw_crc) +{ + ch = (ch ^ (uint8_t)((*lpw_crc) & 0x00FF)); + ch = (ch ^ (ch << 4)); + *lpw_crc = (*lpw_crc >> 8) ^ ((uint16_t) ch << 8) ^ ((uint16_t) ch << 3) ^ ((uint16_t) ch >> 4); + + return (*lpw_crc); +} + +/** + * @brief This function returns the CRC 16 + * @param data pointer on the data used to compute the CRC16 + * @param length number of bytes of the data + * @retval CRC16 + */ +static uint16_t compute_crc(uint8_t *data, uint8_t length) +{ + uint8_t block; + uint16_t crc16 = 0x6363; /* ITU-V.41 */ + + do { + block = *data++; + update_crc(block, &crc16); + } while (--length); + + return crc16; +} + +/** + * @brief This function computes the CRC16 residue as defined by CRC ISO/IEC 13239 + * @param data input data + * @param length Number of bits of DataIn + * @retval Status (SW1&SW2) CRC16 residue is correct + * @retval M24SR_ERROR_CRC CRC16 residue is false + */ +static M24srError_t is_correct_crc_residue(uint8_t *data, uint8_t length) +{ + uint16_t res_crc = 0x0000; + uint16_t status; + + /* check the CRC16 Residue */ + if (length != 0) { + res_crc = compute_crc(data, length); + } + + if (res_crc == 0x0000) { + /* Good CRC, but error status from M24SR */ + status = ((data[length - UB_STATUS_OFFSET] << 8) & 0xFF00) + | (data[length - LB_STATUS_OFFSET] & 0x00FF); + } else { + res_crc = 0x0000; + res_crc = compute_crc(data, 5); + if (res_crc != 0x0000) { + /* Bad CRC */ + return M24SR_IO_ERROR_CRC; + } else { + /* Good CRC, but error status from M24SR */ + status = ((data[1] << 8) & 0xFF00) | (data[2] & 0x00FF); + } + } + + if (status == NFC_COMMAND_SUCCESS) { + return M24SR_SUCCESS; + } + + return (M24srError_t)status; +} + +/** + * @brief This functions creates an I block command according to the structures command_mask and Command. + * @param command_mask structure which contains the field of the different parameters + * @param command structure of the command + * @param length number of bytes of the command + * @param command_buffer pointer to the command created + */ +static void build_I_block_command(uint16_t command_mask, C_APDU *command, uint8_t did, uint16_t *length, + uint8_t *command_buffer) +{ + uint16_t crc16; + static uint8_t block_number = 0x01; + + (*length) = 0; + + /* add the PCD byte */ + if ((command_mask & PCB_NEEDED) != 0) { + /* toggle the block number */ + block_number = !block_number; + /* Add the I block byte */ + command_buffer[(*length)++] = 0x02 | block_number; + } + + /* add the DID byte */ + if ((block_number & DID_NEEDED) != 0) { + /* Add the I block byte */ + command_buffer[(*length)++] = did; + } + + /* add the Class byte */ + if ((command_mask & CLA_NEEDED) != 0) { + command_buffer[(*length)++] = command->header.CLA; + } + + /* add the instruction byte byte */ + if ((command_mask & INS_NEEDED) != 0) { + command_buffer[(*length)++] = command->header.INS; + } + + /* add the Selection Mode byte */ + if ((command_mask & P1_NEEDED) != 0) { + command_buffer[(*length)++] = command->header.P1; + } + + /* add the Selection Mode byte */ + if ((command_mask & P2_NEEDED) != 0) { + command_buffer[(*length)++] = command->header.P2; + } + + /* add Data field lengthbyte */ + if ((command_mask & LC_NEEDED) != 0) { + command_buffer[(*length)++] = command->body.LC; + } + + /* add Data field */ + if ((command_mask & DATA_NEEDED) != 0) { + if (command->body.data) { + memcpy(&(command_buffer[(*length)]), command->body.data, command->body.LC); + } else { + memset(&(command_buffer[(*length)]), 0, command->body.LC); + } + (*length) += command->body.LC; + } + + /* add Le field */ + if ((command_mask & LE_NEEDED) != 0) { + command_buffer[(*length)++] = command->body.LE; + } + + /* add CRC field */ + if ((command_mask & CRC_NEEDED) != 0) { + crc16 = compute_crc(command_buffer, (uint8_t)(*length)); + /* append the CRC16 */ + command_buffer[(*length)++] = GETLSB(crc16); + command_buffer[(*length)++] = GETMSB(crc16); + } +} + +/** + * @brief This function returns M24SR_STATUS_SUCCESS if the buffer is an s-block + * @param buffer pointer to the data + * @retval M24SR_SUCCESS the data is a S-Block + * @retval NFC_ERROR the data is not a S-Block + */ +static M24srError_t is_S_block(uint8_t *buffer) +{ + if ((buffer[OFFSET_PCB] & MASK_BLOCK) == MASK_S_BLOCK) { + return M24SR_SUCCESS; + } else { + return M24SR_ERROR; + } +} + +M24srDriver::M24srDriver(PinName i2c_data_pin, PinName i2c_clock_pin, + PinName gpo_pin, PinName rf_disable_pin) + : _i2c_channel(i2c_data_pin, i2c_clock_pin), + _gpo_event_interrupt(gpo_pin), + _gpo_pin(gpo_pin), + _rf_disable_pin(rf_disable_pin), + _command_cb(&_default_cb), + _subcommand_cb(NULL), + _communication_type(SYNC), + _last_command(NONE), + _ndef_size(MAX_NDEF_SIZE), + _max_read_bytes(MAX_PAYLOAD), + _max_write_bytes(MAX_PAYLOAD), + _is_session_open(false) +{ + /* driver requires valid pin names */ + MBED_ASSERT(i2c_data_pin != NC); + MBED_ASSERT(i2c_clock_pin != NC); + MBED_ASSERT(gpo_pin != NC); + MBED_ASSERT(rf_disable_pin != NC); + + memset(_buffer, 0, 0xFF); + _did_byte = 0; + + if (_rf_disable_pin.is_connected() != 0) { + _rf_disable_pin = 0; + } + + if (_gpo_pin.is_connected() != 0) { + _gpo_event_interrupt.fall(mbed::callback(this, &M24srDriver::nfc_interrupt_callback)); + _gpo_event_interrupt.mode(PullUp); + _gpo_event_interrupt.disable_irq(); + } +} + +/** + * @brief This function initialize the M24SR device + * @return M24SR_SUCCESS if no errors + */ +M24srError_t M24srDriver::init() +{ + /* force sync comms to avoid triggering the application with an event */ + _communication_type = SYNC; + + /* force to open a i2c session */ + M24srError_t status = get_session(true); + + if (status != M24SR_SUCCESS) { + return status; + } + + /* leave the gpo always up */ + if (_gpo_pin.is_connected() != 0) { + status = manage_i2c_gpo(HIGH_IMPEDANCE); + if (status != M24SR_SUCCESS) { + return status; + } + } + + if (_rf_disable_pin.is_connected() != 0) { + status = manage_rf_gpo(HIGH_IMPEDANCE); + if (status != M24SR_SUCCESS) { + return status; + } + } + + /* close the session */ + status = deselect(); + + if (status != M24SR_SUCCESS) { + return status; + } + + if (_gpo_pin.is_connected() != 0) { + _gpo_event_interrupt.enable_irq(); + } + + return M24SR_SUCCESS; +} + +/** + * Handle communication if SYNC mode is selected + * @param status the return error + * @return true if communication has been handled successfully (or was not needed) + */ +bool M24srDriver::manage_sync_communication(M24srError_t *status) +{ + if (_communication_type == SYNC) { + *status = io_poll_i2c(); + if (*status == M24SR_SUCCESS) { + *status = manage_event(); + } else { + _last_command = NONE; + return false; + } + } + + return true; +} + +/** + * @brief This function sends the FWT extension command (S-Block format) + * @param fwt_byte FWT value + * @return M24SR_SUCCESS if no errors + */ +M24srError_t M24srDriver::send_fwt_extension(uint8_t fwt_byte) +{ + uint8_t buffer[STATUS_RESPONSE_LENGTH]; + M24srError_t status; + uint8_t length = 0; + uint16_t crc16; + + /* create the response */ + buffer[length++] = 0xF2; + buffer[length++] = fwt_byte; + /* compute the CRC */ + crc16 = compute_crc(buffer, 0x02); + /* append the CRC16 */ + buffer[length++] = GETLSB(crc16); + buffer[length++] = GETMSB(crc16); + + /* send the request */ + status = io_send_i2c_command(length, buffer); + if (status != M24SR_SUCCESS) { + return status; + } + + _last_command = UPDATE; + + if (!manage_sync_communication(&status)) { + get_callback()->on_updated_binary(this, status, _last_command_data.offset, _last_command_data.data, + _last_command_data.length); + } + + return status; +} + +/** + * @brief This function sends the Deselect command (S-Block format) + * @return M24SR_SUCCESS if no errors + */ +M24srError_t M24srDriver::deselect() +{ + uint8_t buffer[] = DESELECT_REQUEST_COMMAND; + M24srError_t status; + + /* send the request */ + status = io_send_i2c_command(sizeof(buffer), buffer); + + if (status != M24SR_SUCCESS) { + get_callback()->on_deselect(this, status); + } + + _last_command = DESELECT; + + if (!manage_sync_communication(&status)) { + get_callback()->on_selected_application(this, status); + } + + return status; +} + +M24srError_t M24srDriver::receive_deselect() +{ + uint8_t buffer[4]; + M24srError_t status; + + status = io_receive_i2c_response(sizeof(buffer), buffer); + get_callback()->on_deselect(this, status); + + return status; +} + +/** + * @brief This function sends the GetSession command to the M24SR device + * @retval M24SR_SUCCESS the function is successful. + * @retval Status (SW1&SW2) if operation does not complete. + */ +M24srError_t M24srDriver::get_session(bool force) +{ + /* special M24SR command */ + const uint8_t M24SR_OPENSESSION_COMMAND = 0x26; + const uint8_t M24SR_KILLSESSION_COMMAND = 0x52; + + M24srError_t status; + + if (force) { + status = io_send_i2c_command(1, &M24SR_OPENSESSION_COMMAND); + } else { + status = io_send_i2c_command(1, &M24SR_KILLSESSION_COMMAND); + } + + if (status != M24SR_SUCCESS) { + get_callback()->on_session_open(this, status); + return status; + } + + /* Insure no access will be done just after open session */ + /* The only way here is to poll I2C to know when M24SR is ready */ + /* GPO can not be use with KillSession command */ + status = io_poll_i2c(); + + get_callback()->on_session_open(this, status); + return status; +} + +/** + * @brief This function sends the SelectApplication command + * @return M24SR_SUCCESS if no errors + */ +M24srError_t M24srDriver::select_application() +{ + M24srError_t status; + uint8_t data_out[] = SELECT_APPLICATION_COMMAND; + uint16_t P1_P2 = 0x0400; + uint16_t length; + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_SELECT_FILE, P1_P2, sizeof(data_out), data_out, 0); + + /* build the I2C command */ + build_I_block_command(CMD_MASK_SELECT_APPLICATION, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_selected_application(this, status); + return status; + } + + _last_command = SELECT_APPLICATION; + + if (!manage_sync_communication(&status)) { + get_callback()->on_selected_application(this, status); + } + + return status; +} + +M24srError_t M24srDriver::receive_select_application() +{ + uint8_t data_in[STATUS_RESPONSE_LENGTH]; + M24srError_t status; + + _last_command = NONE; + + status = io_receive_i2c_response(sizeof(data_in), data_in); + if (status != M24SR_SUCCESS) { + get_callback()->on_selected_application(this, status); + return status; + } + + status = is_correct_crc_residue(data_in, sizeof(data_in)); + get_callback()->on_selected_application(this, status); + + return status; +} + +M24srError_t M24srDriver::read_id(uint8_t *nfc_id) +{ + if (!nfc_id) { + return M24SR_ERROR; + } + + _subcommand_cb = &_read_id_cb; + _read_id_cb.set_task(nfc_id); + + return select_application(); +} + +/** + * @brief This function sends the SelectCCFile command + * @retval M24SR_SUCCESS the function is successful. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + * @retval Status (SW1&SW2) if operation does not complete for another reason. + */ +M24srError_t M24srDriver::select_cc_file() +{ + M24srError_t status; + uint8_t data_out[] = CC_FILE_ID_BYTES; + uint16_t P1_P2 = 0x000C; + uint16_t length; + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_SELECT_FILE, P1_P2, sizeof(data_out), data_out, 0); + + /* build the I2C command */ + build_I_block_command(CMD_MASK_SELECT_CC_FILE, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_selected_cc_file(this, status); + return status; + } + + _last_command = SELECT_CC_FILE; + + if (!manage_sync_communication(&status)) { + get_callback()->on_selected_cc_file(this, status); + } + + return status; +} + +M24srError_t M24srDriver::receive_select_cc_file() +{ + uint8_t data_in[STATUS_RESPONSE_LENGTH]; + M24srError_t status; + + _last_command = NONE; + + status = io_receive_i2c_response(sizeof(data_in), data_in); + + if (status != M24SR_SUCCESS) { + get_callback()->on_selected_cc_file(this, status); + return status; + } + + status = is_correct_crc_residue(data_in, sizeof(data_in)); + get_callback()->on_selected_cc_file(this, status); + + return status; +} + +/** + * @brief This function sends the SelectSystemFile command + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::select_system_file() +{ + uint8_t data_out[] = SYSTEM_FILE_ID_BYTES; + M24srError_t status; + uint16_t P1_P2 = 0x000C; + uint16_t length; + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_SELECT_FILE, P1_P2, sizeof(data_out), data_out, 0); + + /* build the command */ + build_I_block_command(CMD_MASK_SELECT_CC_FILE, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_selected_system_file(this, status); + return status; + } + + _last_command = SELECT_SYSTEM_FILE; + + if (!manage_sync_communication(&status)) { + get_callback()->on_selected_system_file(this, status); + } + + return status; +} + +M24srError_t M24srDriver::receive_select_system_file() +{ + uint8_t data_in[STATUS_RESPONSE_LENGTH]; + M24srError_t status; + + _last_command = NONE; + + status = io_receive_i2c_response(sizeof(data_in), data_in); + + if (status != M24SR_SUCCESS) { + get_callback()->on_selected_system_file(this, status); + return status; + } + + status = is_correct_crc_residue(data_in, sizeof(data_in)); + get_callback()->on_selected_system_file(this, status); + + return status; +} + +/** + * @brief This function sends the SelectNDEFfile command + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::select_ndef_file(uint16_t ndef_file_id) +{ + M24srError_t status; + uint8_t data_out[] = { GETMSB(ndef_file_id), GETLSB(ndef_file_id) }; + uint16_t P1_P2 = 0x000C; + uint16_t length; + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_SELECT_FILE, P1_P2, sizeof(data_out), data_out, 0); + + /* build the I2C command */ + build_I_block_command(CMD_MASK_SELECT_NDEF_FILE, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + return status; + } + + _last_command = SELECT_NDEF_FILE; + + if (!manage_sync_communication(&status)) { + get_callback()->on_selected_ndef_file(this, status); + } + + return status; +} + +M24srError_t M24srDriver::receive_select_ndef_file() +{ + uint8_t data_in[STATUS_RESPONSE_LENGTH]; + M24srError_t status; + + _last_command = NONE; + + status = io_receive_i2c_response(sizeof(data_in), data_in); + + if (status != M24SR_SUCCESS) { + get_callback()->on_selected_ndef_file(this, status); + return status; + } + + status = is_correct_crc_residue(data_in, sizeof(data_in)); + get_callback()->on_selected_ndef_file(this, status); + + return status; +} + +/** + * @brief This function sends a read binary command + * @param offset first byte to read + * @param length number of bytes to read + * @param buffer pointer to the buffer read from the M24SR device + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::read_binary(uint16_t offset, uint8_t length, uint8_t *buffer) +{ + uint16_t command_length; + M24srError_t status; + + if (length > MAX_OPERATION_SIZE) { + length = MAX_OPERATION_SIZE; + } + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_READ_BINARY, offset, 0, NULL, length); + + build_I_block_command(CMD_MASK_READ_BINARY, &command, _did_byte, &command_length, _buffer); + + status = io_send_i2c_command(command_length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_read_byte(this, status, offset, buffer, length); + return status; + } + + _last_command = READ; + _last_command_data.data = buffer; + _last_command_data.length = length; + _last_command_data.offset = offset; + + if (!manage_sync_communication(&status)) { + get_callback()->on_read_byte(this, status, offset, buffer, length); + } + + return status; +} + +M24srError_t M24srDriver::receive_read_binary() +{ + M24srError_t status; + const uint16_t length = _last_command_data.length; + const uint16_t offset = _last_command_data.offset; + uint8_t *data = _last_command_data.data; + + _last_command = NONE; + + status = io_receive_i2c_response(length + STATUS_RESPONSE_LENGTH, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_read_byte(this, status, offset, data, length); + return status; + } + + status = is_correct_crc_residue(_buffer, length + STATUS_RESPONSE_LENGTH); + if (status != M24SR_SUCCESS) { + get_callback()->on_read_byte(this, status, offset, data, length); + } else { + /* retrieve the data without SW1 & SW2 as provided as return value of the function */ + memcpy(_last_command_data.data, &_buffer[1], length); + get_callback()->on_read_byte(this, status, offset, data, length); + } + + return status; +} + +/** + * @brief This function sends a ST read binary command (no error if access is not inside NDEF file) + * @param offset first byte to read + * @param length number of bytes to read + * @param buffer pointer to the buffer read from the M24SR device + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::st_read_binary(uint16_t offset, uint8_t length, uint8_t *buffer) +{ + uint16_t command_length; + M24srError_t status; + + if (length > MAX_OPERATION_SIZE) { + length = MAX_OPERATION_SIZE; + } + + C_APDU command(C_APDU_CLA_ST, C_APDU_READ_BINARY, offset, 0, NULL, length); + + build_I_block_command(CMD_MASK_READ_BINARY, &command, _did_byte, &command_length, _buffer); + + status = io_send_i2c_command(command_length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_read_byte(this, status, offset, buffer, length); + return status; + } + + _last_command = READ; + _last_command_data.data = buffer; + _last_command_data.length = length; + + if (!manage_sync_communication(&status)) { + get_callback()->on_read_byte(this, status, offset, buffer, length); + } + + return status; +} + +/** + * @brief This function sends a Update binary command + * @param offset first byte to read + * @param length number of bytes to write + * @param buffer pointer to the buffer read from the M24SR device + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::update_binary(uint16_t offset, uint8_t length, const uint8_t *data) +{ + M24srError_t status; + uint16_t command_length; + + if (length > MAX_OPERATION_SIZE) { + length = MAX_OPERATION_SIZE; + } + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_UPDATE_BINARY, offset, length, data, 0); + + build_I_block_command(CMD_MASK_UPDATE_BINARY, &command, _did_byte, &command_length, _buffer); + + status = io_send_i2c_command(command_length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_updated_binary(this, status, offset, (uint8_t *) data, length); + return status; + } + + _last_command = UPDATE; + _last_command_data.data = (uint8_t *) data; + _last_command_data.length = length; + _last_command_data.offset = offset; + + if (!manage_sync_communication(&status)) { + get_callback()->on_updated_binary(this, status, offset, (uint8_t *) data, length); + } + + return status; +} + +M24srError_t M24srDriver::receive_update_binary() +{ + uint8_t response[STATUS_RESPONSE_LENGTH]; + M24srError_t status; + const uint16_t length = _last_command_data.length; + uint8_t *data = _last_command_data.data; + const uint16_t offset = _last_command_data.offset; + + _last_command = NONE; + + status = io_receive_i2c_response(sizeof(response), response); + if (status != M24SR_SUCCESS) { + get_callback()->on_updated_binary(this, status, offset, data, length); + return status; + } + + if (is_S_block(response) == M24SR_SUCCESS) { + /* check the CRC */ + status = is_correct_crc_residue(response, WATING_TIME_EXT_RESPONSE_LENGTH); + if (status != M24SR_IO_ERROR_CRC) { + /* send the FrameExension response*/ + status = send_fwt_extension(response[OFFSET_PCB + 1]); + if (status != M24SR_SUCCESS) { + /* abort update */ + get_callback()->on_updated_binary(this, status, offset, data, length); + } + } + } else { + status = is_correct_crc_residue(response, STATUS_RESPONSE_LENGTH); + get_callback()->on_updated_binary(this, status, offset, data, length); + } + + return status; +} + +/** + * @brief This function sends the Verify command + * @param password_type PasswordId ( 0x0001 : Read NDEF pwd or 0x0002 : Write NDEF pwd or 0x0003 : I2C pwd) + * @param password pointer to the password + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::verify(PasswordType_t password_type, const uint8_t *password) +{ + M24srError_t status; + uint16_t length; + + /* check the parameters */ + if (password_type > I2C_PASSWORD) { + get_callback()->on_verified(this, M24SR_IO_ERROR_PARAMETER, password_type, password); + return M24SR_IO_ERROR_PARAMETER; + } + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_VERIFY, password_type, password ? PASSWORD_LENGTH : 0, NULL, 0); + + uint16_t command_mask = CMD_MASK_VERIFY_BINARY_WO_PWD; + + if (password) { + /* copy the password */ + command.body.data = password; + /* build the I2C command */ + command_mask = CMD_MASK_VERIFY_BINARY_WITH_PWD; + } + + /* build the I2C command */ + build_I_block_command(command_mask, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_verified(this, status, password_type, password); + return status; + } + + _last_command = VERIFY; + _last_command_data.data = (uint8_t *) password; + /* use the offset to store the password type */ + _last_command_data.offset = password_type; + + if (!manage_sync_communication(&status)) { + get_callback()->on_verified(this, status, password_type, password); + } + + return status; +} + +M24srError_t M24srDriver::receive_verify() +{ + M24srError_t status; + uint8_t respBuffer[STATUS_RESPONSE_LENGTH]; + _last_command = NONE; + + const uint8_t *data = _last_command_data.data; + const PasswordType_t type = PasswordType_t(_last_command_data.offset); + + status = io_receive_i2c_response(sizeof(respBuffer), respBuffer); + + if (status != M24SR_SUCCESS) { + get_callback()->on_verified(this, status, type, data); + return status; + } + + status = is_correct_crc_residue(respBuffer, STATUS_RESPONSE_LENGTH); + get_callback()->on_verified(this, status, type, data); + return status; +} + +/** + * @brief This function sends the ChangeReferenceData command + * @param password_type PasswordId (0x0001 : Read NDEF pwd or 0x0002 : Write NDEF pwd or 0x0003 : I2C pwd) + * @param password pointer to the passwaord + * @retval Status (SW1&SW2) Satus of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::change_reference_data(PasswordType_t password_type, const uint8_t *password) +{ + M24srError_t status; + uint16_t length; + + /* check the parameters */ + if (password_type > I2C_PASSWORD) { + get_callback()->on_change_reference_data(this, M24SR_IO_ERROR_PARAMETER, password_type, password); + return M24SR_IO_ERROR_PARAMETER; + } + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_CHANGE, password_type, PASSWORD_LENGTH, password, 0); + + /* build the command */ + build_I_block_command(CMD_MASK_CHANGE_REF_DATA, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_change_reference_data(this, status, password_type, password); + return status; + } + + _last_command = CHANGE_REFERENCE_DATA; + _last_command_data.data = (uint8_t *) password; + /* use the offset to store the password type */ + _last_command_data.offset = password_type; + + if (!manage_sync_communication(&status)) { + get_callback()->on_change_reference_data(this, status, password_type, password); + } + + return status; +} + +M24srError_t M24srDriver::receive_change_reference_data() +{ + M24srError_t status; + uint8_t rensponse[STATUS_RESPONSE_LENGTH]; + + PasswordType_t type = PasswordType_t(_last_command_data.offset); + uint8_t *data = _last_command_data.data; + + status = io_receive_i2c_response(STATUS_RESPONSE_LENGTH, rensponse); + if (status != M24SR_SUCCESS) { + get_callback()->on_change_reference_data(this, status, type, data); + return status; + } + + status = is_correct_crc_residue(rensponse, STATUS_RESPONSE_LENGTH); + get_callback()->on_change_reference_data(this, status, type, data); + return status; +} + +/** + * @brief This function sends the EnableVerificationRequirement command + * @param password_type enable the read or write protection ( 0x0001 : Read or 0x0002 : Write ) + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::enable_verification_requirement(PasswordType_t password_type) +{ + M24srError_t status; + uint16_t length; + + /* check the parameters */ + if ((password_type != READ_PASSWORD) && (password_type != WRITE_PASSWORD)) { + get_callback()->on_enable_verification_requirement(this, M24SR_IO_ERROR_PARAMETER, password_type); + return M24SR_IO_ERROR_PARAMETER; + } + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_ENABLE, password_type, 0, NULL, 0); + + /* build the I2C command */ + build_I_block_command(CMD_MASK_ENABLE_VERIFREQ, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_enable_verification_requirement(this, status, password_type); + return status; + } + + _last_command = ENABLE_VERIFICATION_REQUIREMENT; + /* use the offset to store the password type */ + _last_command_data.offset = password_type; + + if (!manage_sync_communication(&status)) { + get_callback()->on_enable_verification_requirement(this, status, password_type); + } + + return status; +} + +M24srError_t M24srDriver::receive_enable_verification_requirement() +{ + M24srError_t status; + uint8_t rensponse[STATUS_RESPONSE_LENGTH]; + + PasswordType_t type = PasswordType_t(_last_command_data.offset); + + status = io_receive_i2c_response(STATUS_RESPONSE_LENGTH, rensponse); + if (status != M24SR_SUCCESS) { + get_callback()->on_enable_verification_requirement(this, status, type); + return status; + } + + status = is_correct_crc_residue(rensponse, STATUS_RESPONSE_LENGTH); + get_callback()->on_enable_verification_requirement(this, status, type); + return status; +} + +/** + * @brief This function sends the DisableVerificationRequirement command + * @param password_type enable the read or write protection ( 0x0001 : Read or 0x0002 : Write ) + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::disable_verification_requirement(PasswordType_t password_type) +{ + M24srError_t status; + uint16_t length; + + /* check the parameters */ + if ((password_type != READ_PASSWORD) && (password_type != WRITE_PASSWORD)) { + get_callback()->on_disable_verification_requirement(this, M24SR_IO_ERROR_PARAMETER, password_type); + return M24SR_IO_ERROR_PARAMETER; + } + + C_APDU command(C_APDU_CLA_DEFAULT, C_APDU_DISABLE, password_type, 0, NULL, 0); + + /* build the command */ + build_I_block_command(CMD_MASK_DISABLE_VERIFREQ, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_disable_verification_requirement(this, status, password_type); + return status; + } + + _last_command = DISABLE_VERIFICATION_REQUIREMENT; + /* use the offset to store the password type */ + _last_command_data.offset = password_type; + + if (!manage_sync_communication(&status)) { + get_callback()->on_disable_verification_requirement(this, status, password_type); + } + + return status; +} + +M24srError_t M24srDriver::receive_disable_verification_requirement() +{ + M24srError_t status; + uint8_t rensponse[STATUS_RESPONSE_LENGTH]; + + PasswordType_t type = PasswordType_t(_last_command_data.offset); + + status = io_receive_i2c_response(STATUS_RESPONSE_LENGTH, rensponse); + if (status != M24SR_SUCCESS) { + get_callback()->on_disable_verification_requirement(this, status, type); + return status; + } + + status = is_correct_crc_residue(rensponse, STATUS_RESPONSE_LENGTH); + get_callback()->on_disable_verification_requirement(this, status, type); + return status; +} + +/** + * @brief This function sends the EnablePermananentState command + * @param password_type enable the read or write protection ( 0x0001 : Read or 0x0002 : Write ) + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::enable_permanent_state(PasswordType_t password_type) +{ + M24srError_t status; + uint16_t length; + + /* check the parameters */ + if ((password_type != READ_PASSWORD) && (password_type != WRITE_PASSWORD)) { + get_callback()->on_enable_permanent_state(this, M24SR_IO_ERROR_PARAMETER, password_type); + return M24SR_IO_ERROR_PARAMETER; + } + + C_APDU command(C_APDU_CLA_ST, C_APDU_ENABLE, password_type, 0, NULL, 0); + + /* build the I2C command */ + build_I_block_command(CMD_MASK_ENABLE_VERIFREQ, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_enable_permanent_state(this, status, password_type); + return status; + } + + _last_command = ENABLE_PERMANET_STATE; + /* use the offset to store the password type */ + _last_command_data.offset = password_type; + + if (!manage_sync_communication(&status)) { + get_callback()->on_enable_permanent_state(this, status, password_type); + } + + return status; +} + +M24srError_t M24srDriver::receive_enable_permanent_state() +{ + M24srError_t status; + uint8_t rensponse[STATUS_RESPONSE_LENGTH]; + + PasswordType_t type = PasswordType_t(_last_command_data.offset); + + status = io_receive_i2c_response(STATUS_RESPONSE_LENGTH, rensponse); + if (status != M24SR_SUCCESS) { + get_callback()->on_enable_permanent_state(this, status, type); + return status; + } + + status = is_correct_crc_residue(rensponse, STATUS_RESPONSE_LENGTH); + get_callback()->on_enable_permanent_state(this, status, type); + return status; +} + +/** + * @brief This function sends the DisablePermanentState command + * @param password_type enable the read or write protection ( 0x0001 : Read or 0x0002 : Write ) + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::disable_permanent_state(PasswordType_t password_type) +{ + M24srError_t status; + uint16_t length; + + /* check the parameters */ + if ((password_type != READ_PASSWORD) && (password_type != WRITE_PASSWORD)) { + get_callback()->on_disable_permanent_state(this, M24SR_IO_ERROR_PARAMETER, password_type); + return M24SR_IO_ERROR_PARAMETER; + } + + C_APDU command(C_APDU_CLA_ST, C_APDU_DISABLE, password_type, 0, NULL, 0); + + /* build the I2C command */ + build_I_block_command(CMD_MASK_DISABLE_VERIFREQ, &command, _did_byte, &length, _buffer); + + /* send the request */ + status = io_send_i2c_command(length, _buffer); + if (status != M24SR_SUCCESS) { + get_callback()->on_enable_permanent_state(this, status, password_type); + return status; + } + + _last_command = DISABLE_PERMANET_STATE; + /* use the offset to store the password type */ + _last_command_data.offset = password_type; + + if (!manage_sync_communication(&status)) { + get_callback()->on_disable_permanent_state(this, status, password_type); + } + + return status; +} + +M24srError_t M24srDriver::receive_disable_permanent_state() +{ + M24srError_t status; + uint8_t rensponse[STATUS_RESPONSE_LENGTH]; + + PasswordType_t type = PasswordType_t(_last_command_data.offset); + + status = io_receive_i2c_response(STATUS_RESPONSE_LENGTH, rensponse); + if (status != M24SR_SUCCESS) { + get_callback()->on_disable_permanent_state(this, status, type); + return status; + } + + status = is_correct_crc_residue(rensponse, STATUS_RESPONSE_LENGTH); + get_callback()->on_disable_permanent_state(this, status, type); + return status; +} + +/** + * @brief This function generates an interrupt on GPO pin + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::send_interrupt() +{ + uint16_t length; + uint16_t P1_P2 = 0x001E; + + M24srError_t status = manage_i2c_gpo(INTERRUPT); + if (status != M24SR_SUCCESS) { + return status; + } + + C_APDU command(C_APDU_CLA_ST, C_APDU_INTERRUPT, P1_P2, 0, NULL, 0); + + /* build the I2C command */ + build_I_block_command(CMD_MASK_SEND_INTERRUPT, &command, _did_byte, &length, _buffer); + + return send_receive_i2c(length, _buffer); +} + +M24srError_t M24srDriver::send_receive_i2c(uint16_t length, uint8_t *buffer) +{ + M24srError_t status; + + /* send the request */ + status = io_send_i2c_command(length, buffer); + if (status != M24SR_SUCCESS) { + return status; + } + + status = io_poll_i2c(); + if (status != M24SR_SUCCESS) { + return status; + } + + /* read the response */ + status = io_receive_i2c_response(STATUS_RESPONSE_LENGTH, buffer); + if (status != M24SR_SUCCESS) { + return status; + } + + return is_correct_crc_residue(buffer, STATUS_RESPONSE_LENGTH); +} + +/** + * @brief This function forces GPO pin to low state or high Z + * @param uSetOrReset select if GPO must be low (reset) or HiZ + * @retval Status (SW1&SW2) Status of the operation to complete. + * @retval M24SR_ERROR_I2CTIMEOUT I2C timeout occurred. + */ +M24srError_t M24srDriver::state_control(bool gpo_reset) +{ + uint16_t length; + uint16_t P1_P2 = 0x001F; + + M24srError_t status = manage_i2c_gpo(STATE_CONTROL); + if (status == M24SR_SUCCESS) { + return status; + } + + uint8_t reset = (uint8_t)gpo_reset; + + C_APDU command(C_APDU_CLA_ST, C_APDU_INTERRUPT, P1_P2, 1, &reset, 0); + + /* build the I2C command */ + build_I_block_command(CMD_MASK_GPO_STATE, &command, _did_byte, &length, _buffer); + + return send_receive_i2c(length, _buffer); +} + +M24srError_t M24srDriver::manage_i2c_gpo(NfcGpoState_t gpo_i2c_config) +{ + if (_gpo_pin.is_connected() == 0) { + return M24SR_IO_PIN_NOT_CONNECTED; + } + + if (gpo_i2c_config > STATE_CONTROL) { + return M24SR_IO_ERROR_PARAMETER; + } + + _subcommand_cb = &_manage_gpo_cb; + _manage_gpo_cb.set_new_gpo_config(true, gpo_i2c_config); + + return select_application(); +} + +M24srError_t M24srDriver::manage_rf_gpo(NfcGpoState_t gpo_rf_config) +{ + if (_rf_disable_pin.is_connected() == 0) { + return M24SR_IO_PIN_NOT_CONNECTED; + } + + if (gpo_rf_config > STATE_CONTROL) { + return M24SR_IO_ERROR_PARAMETER; + } + + _subcommand_cb = &_manage_gpo_cb; + _manage_gpo_cb.set_new_gpo_config(false, gpo_rf_config); + + return select_application(); +} + +M24srError_t M24srDriver::rf_config(bool enable) +{ + if (_rf_disable_pin.is_connected() == 0) { + return M24SR_IO_PIN_NOT_CONNECTED; + } + /* invert since it's a disable pin */ + _rf_disable_pin = !enable; + + return M24SR_SUCCESS; +} + +M24srError_t M24srDriver::io_send_i2c_command(uint8_t length, const uint8_t *buffer) +{ + int ret = _i2c_channel.write(M24SR_ADDR, (const char *) buffer, length); + if (ret == 0) { + return M24SR_SUCCESS; + } + return M24SR_IO_ERROR_I2CTIMEOUT; +} + +M24srError_t M24srDriver::io_receive_i2c_response(uint8_t length, uint8_t *buffer) +{ + int ret = _i2c_channel.read(M24SR_ADDR, (char *) buffer, length); + if (ret == 0) { + return M24SR_SUCCESS; + } + + return M24SR_IO_ERROR_I2CTIMEOUT; +} + +M24srError_t M24srDriver::io_poll_i2c() +{ + int status = 1; + while (status != 0) { + /* send the device address and wait to receive an ack bit */ + status = _i2c_channel.write(M24SR_ADDR, NULL, 0); + } + return M24SR_SUCCESS; +} + +M24srError_t M24srDriver::manage_event() +{ + switch (_last_command) { + case DESELECT: + return receive_deselect(); + case SELECT_APPLICATION: + return receive_select_application(); + case SELECT_CC_FILE: + return receive_select_cc_file(); + case SELECT_NDEF_FILE: + return receive_select_ndef_file(); + case SELECT_SYSTEM_FILE: + return receive_select_system_file(); + case READ: + return receive_read_binary(); + case UPDATE: + return receive_update_binary(); + case VERIFY: + return receive_verify(); + case CHANGE_REFERENCE_DATA: + return receive_change_reference_data(); + case ENABLE_VERIFICATION_REQUIREMENT: + return receive_enable_verification_requirement(); + case DISABLE_VERIFICATION_REQUIREMENT: + return receive_disable_verification_requirement(); + case ENABLE_PERMANET_STATE: + return receive_enable_permanent_state(); + case DISABLE_PERMANET_STATE: + return receive_disable_permanent_state(); + default: + return M24SR_SUCCESS; + } +} + +} //ST +} //vendor +} //nfc +} //mbed + +mbed::nfc::NFCEEPROMDriver *greentea_nfc_EEPROM_driver_get_instance() +{ + static mbed::nfc::vendor::ST::M24srDriver instance; + return &instance; +} + +/******************* (C) COPYRIGHT 2013 STMicroelectronics *****END OF FILE****/