Newer
Older
mbed-os / drivers / usb / include / usb / msd / USBMSD.h
/*
 * Copyright (c) 2018-2019, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef USBMSD_H
#define USBMSD_H

/* These headers are included for child class. */
#include "USBDescriptor.h"
#include "USBDevice_Types.h"
#include "platform/Callback.h"
#include "usb/internal/PolledQueue.h"
#include "usb/internal/Task.h"
#include "BlockDevice.h"
#include "rtos/Mutex.h"

#include "USBDevice.h"

/**
 * \defgroup drivers_USBMSD USBMSD class
 * \ingroup drivers-public-api-usb
 * @{
 */

/**
 * USBMSD class: generic class in order to use all kinds of blocks storage chip
 *
 * Introduction
 *
 * USBMSD implements the MSD protocol. It permits to access a block device (flash, SD Card,...)
 * from a computer over USB.
 *
 * @code
 * #include "mbed.h"
 * #include "SDBlockDevice.h"
 * #include "USBMSD.h"
 *
 * SDBlockDevice sd(PTE3, PTE1, PTE2, PTE4);
 * USBMSD usb(&sd);
 *
 * int main() {
 *
 *     while(true) {
 *         usb.process();
 *     }
 *
 *     return 0;
 * }
 * @endcode
 */
class USBMSD: public USBDevice {
public:

    /**
    * Constructor
    *
    * This creates a new USBMSD object with the given block device. Connect must be called
    * for the block device to connect.
    *
    * @param bd BlockDevice to mount as a USB drive
    * @param connect_blocking true to perform a blocking connect, false to start in a disconnected state
    * @param vendor_id Your vendor_id
    * @param product_id Your product_id
    * @param product_release Your preoduct_release
    */
    USBMSD(mbed::BlockDevice *bd, bool connect_blocking = true, uint16_t vendor_id = 0x0703, uint16_t product_id = 0x0104, uint16_t product_release = 0x0001);

    /**
    * Fully featured constructor
    *
    * Construct this object with the supplied USBPhy and parameters. The user
    * this object is responsible for calling connect() or init().
    *
    * @note Derived classes must use this constructor and call init() or
    * connect() themselves. Derived classes should also call deinit() in
    * their destructor. This ensures that no interrupts can occur when the
    * object is partially constructed or destroyed.
    *
    * @param phy USB phy to use
    * @param bd BlockDevice to mount as a USB drive
    * @param vendor_id Your vendor_id
    * @param product_id Your product_id
    * @param product_release Your preoduct_release
    */
    USBMSD(USBPhy *phy, mbed::BlockDevice *bd, uint16_t vendor_id, uint16_t product_id, uint16_t product_release);

    /**
     * Destroy this object
     *
     * Any classes which inherit from this class must call disconnect
     * before this destructor runs.
     */
    virtual ~USBMSD();

    /**
    * Connect the USB MSD device.
    *
    * @returns true if successful
    */
    bool connect();

    /**
    * Disconnect the USB MSD device.
    */
    void disconnect();

    /**
    * Perform USB processing
    */
    void process();

    /**
     * Called when USBMSD needs to perform processing
     *
     * @param cb Callback called when USBMSD needs process() to be called
     */
    void attach(mbed::Callback<void()> cb);

    /**
    * Check if MSD device was removed/unmounted on the host side.
    *
    * @returns true if device was removed/unmounted on the host side
    */
    bool media_removed();

protected:

    /*
    * read one or more blocks on a storage chip
    *
    * @param data pointer where will be stored read data
    * @param block starting block number
    * @param count number of blocks to read
    * @returns 0 if successful
    */
    virtual int disk_read(uint8_t *data, uint64_t block, uint8_t count);

    /*
    * write one or more blocks on a storage chip
    *
    * @param data data to write
    * @param block starting block number
    * @param count number of blocks to write
    * @returns 0 if successful
    */
    virtual int disk_write(const uint8_t *data, uint64_t block, uint8_t count);

    /*
    * Disk initilization
    */
    virtual int disk_initialize();

    /*
    * Return the number of blocks
    *
    * @returns number of blocks
    */
    virtual uint64_t disk_sectors();

    /*
    * Return memory size
    *
    * @returns memory size
    */
    virtual uint64_t disk_size();

    /*
    * To check the status of the storage chip
    *
    * @returns status: 0: OK, 1: disk not initialized, 2: no medium in the drive, 4: write protected
    */
    virtual int disk_status();

private:

    // MSC Bulk-only Stage
    enum Stage {
        READ_CBW,     // wait a CBW
        ERROR,        // error
        PROCESS_CBW,  // process a CBW request
        SEND_CSW,     // send a CSW
    };

    // Bulk-only CBW
    typedef MBED_PACKED(struct)
    {
        uint32_t Signature;
        uint32_t Tag;
        uint32_t DataLength;
        uint8_t  Flags;
        uint8_t  LUN;
        uint8_t  CBLength;
        uint8_t  CB[16];
    } CBW;

    // Bulk-only CSW
    typedef MBED_PACKED(struct)
    {
        uint32_t Signature;
        uint32_t Tag;
        uint32_t DataResidue;
        uint8_t  Status;
    } CSW;

    // If this class has been initialized
    bool _initialized;

    // If msd device has been unmounted by host
    volatile bool _media_removed;

    //state of the bulk-only state machine
    Stage _stage;

    // current CBW
    CBW _cbw;

    // CSW which will be sent
    CSW _csw;

    // addr where will be read or written data
    uint32_t _addr;

    // length of a reading or writing
    uint32_t _length;

    // memory OK (after a memoryVerify)
    bool _mem_ok;

    // cache in RAM before writing in memory. Useful also to read a block.
    uint8_t *_page;

    int _block_size;
    uint64_t _memory_size;
    uint64_t _block_count;

    // endpoints
    usb_ep_t _bulk_in;
    usb_ep_t _bulk_out;
    uint8_t _bulk_in_buf[512];
    uint8_t _bulk_out_buf[512];
    bool _out_ready;
    bool _in_ready;
    uint32_t _bulk_out_size;

    // Interrupt to thread deferral
    events::PolledQueue _queue;
    events::Task<void()> _in_task;
    events::Task<void()> _out_task;
    events::Task<void()> _reset_task;
    events::Task<void(const setup_packet_t *)> _control_task;
    events::Task<void()> _configure_task;

    mbed::BlockDevice *_bd;
    rtos::Mutex _mutex_init;
    rtos::Mutex _mutex;

    // space for config descriptor
    uint8_t _configuration_descriptor[32];

    virtual const uint8_t *string_iproduct_desc();
    virtual const uint8_t *string_iinterface_desc();
    virtual const uint8_t *configuration_desc(uint8_t index);
    virtual void callback_set_configuration(uint8_t configuration);
    virtual void callback_set_interface(uint16_t interface, uint8_t alternate);
    virtual void callback_state_change(DeviceState new_state);
    virtual void callback_request(const setup_packet_t *setup);
    virtual void callback_request_xfer_done(const setup_packet_t *setup, bool aborted);

    void _isr_out();
    void _isr_in();

    void _out();
    void _in();
    void _reset();
    void _control(const setup_packet_t *request);
    void _configure();

    void _init();
    void _process();
    void _write_next(uint8_t *data, uint32_t size);
    void _read_next();

    void CBWDecode(uint8_t *buf, uint16_t size);
    void sendCSW(void);
    bool inquiryRequest(void);
    bool write(uint8_t *buf, uint16_t size);
    bool readFormatCapacity();
    bool readCapacity(void);
    bool infoTransfer(void);
    void memoryRead(void);
    bool modeSense6(void);
    bool modeSense10(void);
    void testUnitReady(void);
    bool requestSense(void);
    void memoryVerify(uint8_t *buf, uint16_t size);
    void memoryWrite(uint8_t *buf, uint16_t size);
    void msd_reset();
    void fail();
};

/** @}*/

#endif