RomeoGolf

Ср 23 Август 2017

USB-polygon-12: Имитация файлов

Файлы: какие надо и где их взять

В предыдущем выпуске цикла проект для самодельного устройства был доведен до такого состояния, что ОС ПК определяет его, как двухгигабайтную флэшку, причем, Windows 7 уже не предлагает ее отформатировать. Однако по мнению ОС флэшка пуста. Надо ее «наполнить».

Для начала о том, какие файлы я хотел бы видеть.

Во-первых, текстовый файл с неким идентификатором. Это для того, чтобы собственная программа для ПК, проверив наличие воткнутых в порты флэшек и обнаружив несколько одновременно, могла с легкостью отличить нужную (то есть, данное устройство) по наличию файла с определенным именем и определенным содержимым. Очень хорошо на мой взгляд подойдет для этой цели GUID в текстовом виде.

Во-вторых, некий текстовый файл, скажем, Readme. Для отладки работы с текстами разного размера и содержимого, хранящимися в памяти программ устройства, как с файлами.

В-третьих, «файл», содержимое которого изменялось бы при воздействии на устройство, скажем, с отображением состояния счетчика, который меняется при нажатии на кнопки. Отображать состояние счетчика хорошо бы в текстовом виде, да еще и с каким-нибудь комментарием, чтобы открывать чем попало, хоть в notepad.

В-четвертых, «файл» с размером, заведомо превышающим то, что может храниться в памяти устройства, скажем, счетчик где-нибудь мегабайтного размера, в текстовом виде для удобства просмотра.

Потом, хотелось бы, чтобы можно было управлять количеством файлов и их размером, а также способом подготовки информации при запросе конкретного файла. То есть, должна быть какая-то управляющая структура, перечисляющая имя файла, его размер и соответствующую ему подпрограмму, отвечающую за вывод данных. А наполнение корневого каталога должно формироваться в соответствии с этой структурой, и данные по запросу каждого файла должны готовиться указанными в ней функциями.

Подготовка управляющей структуры

Именно такое решение — с таблицей элементов ФС — описано в статье на Хабрахабре. Эта статья вообще сильно перекликается с тем, что я тут делаю, только для другого процессора. Идея не очень сложная, понятная, только надо ее подогнать под AVR и LUFA.

Для начала объявляю структуру таблицы файлов с коротким именем файла, длиной файла и подпрограммой подготовки его данных. А непосредственно перед ней объявлю и тип указателя на функцию для этой подпрограммы, чтобы она принимала указатель на порцию данных, размер порции и ее смещение в файле. Так как ничего интерфейсного в этих сущностях я пока не вижу, объявлять буду в файле fake_fs.c, сразу после определения констант, таким образом:

typedef uint8_t * (*ProcedureForRead)(uint8_t *data_buf, uint32_t size, uint32_t offset);

typedef struct {
  char name[11];
  uint32_t size;
  ProcedureForRead procedureForRead;
} FileEntry;

Имя предполагается короткое, «8.3», поэтому на поле имени отведено 11 символов. Поле будет копироваться в описатель файла как есть, поэтому надо сразу заполнять его правильно: без точки-разделителя, с дополнением пробелами имени до 8 символов и расширения до 3 символов.

Заготовка для первого файла

Чуть выше расположу объявления для файла, содержащего GUID в текстовом виде:

#define SIZE_OF_KEY 38
static const char guidKey[] PROGMEM = "{7065c23a-3818-43c5-bdf0-55567ade31e7}";
uint8_t * readKey(uint8_t *data_buf, uint32_t size, uint32_t offset);

Чтобы данные файла лежали в памяти программ и не засоряли оперативную, нужен для начала макрос PROGMEM, и потом еще некоторые макросы из модуля pgmspace.h, который следует подключить в начале файла:

#include "avr/pgmspace.h"

И собственно функция, выдающая данные этого «файла»:

uint8_t * readKey(uint8_t *data_buf, uint32_t size, uint32_t offset) {
    if ((offset + 16) > SIZE_OF_KEY) {
        if (offset < SIZE_OF_KEY) memcpy_P(data_buf, (PGM_P)(&(guidKey[0]) + offset), SIZE_OF_KEY - offset);
    } else {
        memcpy_P(data_buf, (PGM_P)(&(guidKey[0]) + offset), 16);
    }
    return data_buf;
}

Суть: если адрес очередного запрошенного куска данных (адрес начала плюс размер передаваемого куска) выходит за границы размера файла, то скопировать в отведенный буфер только помещающийся кусок. Если же запрошенный кусок помещается с запасом — скопировать его весь. А если смещение сразу выходит за границы файла — ничего не делать.

Еще после объявления структуры таблицы файлов надо объявить саму таблицу, содержащую приведенный выше «файл», с просьбой разместить ее в памяти программ и с ограничительной записью в конце:

static const FileEntry fileTable[] PROGMEM =
{
    {"KEY_FILETXT", SIZE_OF_KEY, readKey},
    {{ 0 }, 0, NULL}
};

Изменения в функциях чтения FAT и чтения данных

Функцию чтения данных нужно дописать. Теперь она будет иметь такой вид:

uint8_t * read_data(uint8_t * data_buf, uint32_t BlockAddress, uint8_t BytesInBlockDiv16) {
    memset(data_buf, 0, 16);
    uint32_t size;
    if ((BlockAddress == ROOT_SECTOR) && (BytesInBlockDiv16 == 0)) {
    /* root, chunck 1 */
    data_buf[0] = 'L';
    data_buf[1] = 'U';
    data_buf[2] = 'F';
    data_buf[3] = 'A';

    data_buf[4] = '_';
    data_buf[5] = '1';
    data_buf[6] = ' ';
    data_buf[7] = ' ';

    data_buf[8] = ' ';
    data_buf[9] = ' ';
    data_buf[10] = ' ';
    data_buf[11] = 0x08;
    } else
    if ((BlockAddress == ROOT_SECTOR) && (BytesInBlockDiv16 == 1)) {
    /* root, chunck 2 */
    /* все нули */
    } else
    if ((BlockAddress >= ROOT_SECTOR) && (BlockAddress < ROOT_SECTOR + SECTORS_PER_CLUSTER)) {
        uint16_t readingChunck = (((BlockAddress - ROOT_SECTOR) << BYTES_PER_SECT_SHIFT) + (BytesInBlockDiv16 << 4)) >> 4;
        uint16_t fileNum = (readingChunck >> 1) - 1;
        if (fileNum <= lastFileNo) {
            if ((readingChunck & 1) == 0){
                memcpy_P(data_buf, (PGM_P)(&(fileTable[fileNum].name[0])), 11);

                data_buf[11] = ATTR_ARCHIVE;
                data_buf[12] = 0x00; /* reserv */
                data_buf[13] = 0x04; /* creation minisec */
                data_buf[14] = 0x72; /* creation time */
                data_buf[15] = 0x00; /* creation time */

            } else {
                uint32_t firstCluster = ROOT_CLUSTER + 1;
                uint32_t clustFileSize;
                for (uint16_t i = 0; i < fileNum; i++) {
                    size = pgm_read_dword(&(fileTable[i].size));
                    clustFileSize = (size % BYTES_PER_CLUSTER == 0) ?
                        size >> BYTES_PER_CLUST_SHIFT :
                        (size >> BYTES_PER_CLUST_SHIFT) + 1;
                    firstCluster += clustFileSize;
                }

                data_buf[0] = 0x79; /*  */
                data_buf[1] = 0x40; /*  */
                data_buf[2] = 0x79; /*  */
                data_buf[3] = 0x40; /*  */

                data_buf[4] = (firstCluster >> 16) & 0xFF;
                data_buf[5] = (firstCluster >> 24) & 0xFF;
                data_buf[6] = 0x00; /*  */
                data_buf[7] = 0x00; /*  */

                data_buf[8] = 0x79; /*  */
                data_buf[9] = 0x40; /*  */

                data_buf[10] = firstCluster & 0xFF;
                data_buf[11] = (firstCluster >> 8) & 0xFF;

                /* file size */
                size = pgm_read_dword(&(fileTable[fileNum].size));
                data_buf[12] = size & 0xFF;
                data_buf[13] = (size >> 8) & 0xFF;
                data_buf[14] = (size >> 16) & 0xFF;
                data_buf[15] = (size >> 24) & 0xFF;
            }
        }
    }

    if ((BlockAddress >= ROOT_SECTOR + SECTORS_PER_CLUSTER)) {
        BlockAddress -= BOOT_SECTOR + 1;

        uint32_t readingClustNo = ((BlockAddress << BYTES_PER_SECT_SHIFT) + (BytesInBlockDiv16 << 4)) >> BYTES_PER_CLUST_SHIFT;
        uint32_t clustFileSize;
        uint32_t lastFileEndClust = ROOT_CLUSTER ;
        ProcedureForRead pFunc;

        for (uint16_t i = 0; i <= lastFileNo; i++) {
            size = pgm_read_dword(&(fileTable[i].size));
            clustFileSize = (size % BYTES_PER_CLUSTER == 0) ?
                    size >> BYTES_PER_CLUST_SHIFT :
                    (size >> BYTES_PER_CLUST_SHIFT) + 1;
            if ((readingClustNo > lastFileEndClust) && (readingClustNo <= lastFileEndClust + clustFileSize)) {
                memcpy_P(&pFunc, &(fileTable[i].procedureForRead), sizeof(PGM_VOID_P));
                if (pFunc == NULL) {
                    break;
                } else {
                    uint32_t offset =
                    ((BlockAddress - ((lastFileEndClust + 1) << SECTORS_PER_CLUST_SHIFT)) << BYTES_PER_SECT_SHIFT) +
                    (BytesInBlockDiv16 << 4);
                    if (offset > size) break;
                    return pFunc(data_buf, fileTable[i].size, offset);
                }
                break;
            }
            lastFileEndClust += clustFileSize;
        }
    }
    return data_buf;
}

В нее добавились:

  • Проверка попадания адреса в корневой каталог для имитации файловой записи в этом каталоге. В этом случае вычисляется размер файла и адрес его первого кластера, чтобы дописать в корневой каталог в конце того, что там уже записано (на текущий момент там записан только идентификатор тома, но файлов может быть несколько, а функция универсальная)
  • Проверка попадания адреса за пределы корневого каталога для имитации содержимого файла. Тогда файл будет выдаваться 16-байтными кусками, в соответстви с адресом и смещением запрошенного куска.

Функция read_fat также заметно разрослась и усложнилась:

uint8_t * read_fat(uint8_t * data_buf, uint32_t BlockAddress, uint8_t BytesInBlockDiv16) {
    uint32_t nextClaster;
    uint32_t size = pgm_read_dword(&(fileTable[0].size));
    memset(data_buf, 0, 16);
    if ((BlockAddress == 0) && (BytesInBlockDiv16 == 0)) {
        /* first fat */
        data_buf[0] = 0xF8;
        data_buf[1] = 0xFF;
        data_buf[2] = 0xFF;
        data_buf[3] = 0x0F;
        /* second fat */
        data_buf[4] = 0xFF;
        data_buf[5] = 0xFF;
        data_buf[6] = 0xFF;
        data_buf[7] = 0x0F;
        /* root dir */
        data_buf[8] = 0xFF;
        data_buf[9] = 0xFF;
        data_buf[10] = 0xFF;
        data_buf[11] = 0x0F;
        /* file1 */
        if (fileTable == NULL || size == 0) {
          data_buf[12] = 0x00;
          data_buf[13] = 0x00;
          data_buf[14] = 0x00;
          data_buf[15] = 0x00;
        } else 
        if (size <= SECTORS_PER_CLUSTER * BYTES_PER_SECTOR) {
          data_buf[12] = 0xFF;
          data_buf[13] = 0xFF;
          data_buf[14] = 0xFF;
          data_buf[15] = 0x0F;
        } else 
        if (size > SECTORS_PER_CLUSTER * BYTES_PER_SECTOR) {
          nextClaster = (ROOT_CLUSTER + 2);
          data_buf[12] = nextClaster & 0xFF;
          data_buf[13] = (nextClaster >> 8) & 0xFF;
          data_buf[14] = (nextClaster >> 16) & 0xFF;
          data_buf[15] = (nextClaster >> 24) & 0xFF;
        }
    } else {
        uint32_t readingClustNo = ((BlockAddress << BYTES_PER_SECT_SHIFT) + (BytesInBlockDiv16 << 4)) >> 2;
        uint32_t clustFileSize;
        uint32_t lastFileEndClust = ROOT_CLUSTER;
        uint16_t currFileNo = 0;

        for (int j = 0; j < 16; j +=4 ) {
            for (int i = currFileNo; i <= lastFileNo; i++) {
                size = pgm_read_dword(&(fileTable[i].size));
                clustFileSize = (size % BYTES_PER_CLUSTER == 0) ?
                        size >> BYTES_PER_CLUST_SHIFT :
                        (size >> BYTES_PER_CLUST_SHIFT) + 1;
                if (readingClustNo == lastFileEndClust + clustFileSize) {
                    data_buf[j + 0] = 0xFF;
                    data_buf[j + 1] = 0xFF;
                    data_buf[j + 2] = 0xFF;
                    data_buf[j + 3] = 0x0F;
                    break;
                }
                if ((readingClustNo > lastFileEndClust) && (readingClustNo < lastFileEndClust + clustFileSize)) {
                    nextClaster = (readingClustNo + 1);
                    data_buf[j + 0] = (nextClaster >> 0) & 0xFF;
                    data_buf[j + 1] = (nextClaster >> 8) & 0xFF;
                    data_buf[j + 2] = (nextClaster >> 16) & 0xFF;
                    data_buf[j + 3] = (nextClaster >> 24) & 0xFF;
                    break;
                }
                lastFileEndClust += clustFileSize;
                currFileNo++;
            }
            readingClustNo += 1;
        }
    }
    return data_buf;
}

В нее добавились операции по вычислению положения файла в области данных для заполнения соответствующих элементов таблицы FAT.

Чтобы эти изменения работали, требуется добавить глобальные переменные, необходимые для вычисления принадлежности запрашиваемых адресов тому или иному файлу. Это количество кластеров, занятых файлами, и номер последнего файла:

uint32_t nClusters;
uint32_t lastFileNo;

Их необходимо инициализировать, для чего потребуется функция, определяющая по таблице файлов соответствующие параметры:

void fakeFsInit() {
    nClusters = ROOT_CLUSTER;
    uint32_t clustFileSize;
    uint32_t size;
    int i = 0;
    for (i = 0; pgm_read_byte(&(fileTable[i].name[0])) != 0; i++) {
        size = pgm_read_dword(&(fileTable[i].size));
        clustFileSize = (size % BYTES_PER_CLUSTER == 0) ?
                size >> BYTES_PER_CLUST_SHIFT :
                (size >> BYTES_PER_CLUST_SHIFT) + 1;
        nClusters += clustFileSize;
    }
    lastFileNo = i - 1;
}

Эту функцию необходимо вызвать в файле MassStorage.c в функции SetupHardware() сразу после вызова USB_Init(), для чего надо не забыть включить #include "Lib/fake_fs.h" в MassStorage.

Для ускорения работы можно было бы вообще все вычисления, выполняемые при подготовке данных FAT и корневого каталога, сделать в функции инициализации, а не на лету при получении запроса на чтение файла. Однако тогда надо хранить параметры каждого файла: кластер начала, количество кластеров, размер в байтах, смещение для элемента-описателя в каталоге и для элементов списка в FAT. Но тогда при увеличении числа имитируемых файлов будет разрастаться занимаемая оперативная память, а ее очень-очень мало. А вот код, вычисляющий эти данные на лету, от количества файлов не зависит.

Если уж на то пошло, то быстрее всего не вычислять ничего, а сложить образ файловой системы где-то в памяти. Получаем в результате обыкновенную флэшку и необходимость иметь солидный объем памяти (по сравнению с имеющейся сейчас). Поэтому жертвую вычислениями в пользу памяти.

Тем более, результат имеется: теперь устройство, подключенное к ПК, можно открыть, как флэшку, на которой в корневом каталоге записан файл KEY_FILE.TXT, и этот файл можно открыть блокнотом (который notepad.exe) и прочитать GUID. Готов очередной GIT-коммит.

Остальные файлы

Имеется заготовка для добавления прочих файлов, перечисленных в начале выпуска. Дальше должно пойти проще: надо вставить в таблицу файлов новое имя файла и имя функции получения данных этого файла, а также ввести его размер. Если это текст — посчитать вручную, а если нет (например, результат каких-то действий или передачи данных извне) — задать желаемую величину.

Файл ReadMe

Добавляю текстовый файл размером чуть побольше. Для этого объявляю соответствующие строковую переменную, константу-размер строки и функцию чтения:

#define SIZE_OF_README  (92 + 53)
static const char readme[] PROGMEM = {
    "This is a test device whithout any flash-memory.\r\n"
    "This device have a fake file system FAT32.\r\n"
    "All files in the device are generated by a program."};
uint8_t * readMe(uint8_t *data_buf, uint32_t size, uint32_t offset);

В таблицу файлов добавляю вторую строку (между строкой файла KEY_FILETXT и ограничительной строкой):

    {"README  TXT", SIZE_OF_README, readMe},

Ну и определяю функцию чтения этого файла, очень похожую на чтение KEY_FILE.TXT. Настолько похожую, что можно бы написать общую унифицированную, но не буду, поскольку текстовых файлов из строковой константы мне больше не надо. А функция такая:

uint8_t * readMe(uint8_t *data_buf, uint32_t size, uint32_t offset) {
    if ((offset + 16) > SIZE_OF_README) {
        memset(data_buf, 0, 16);
    if (offset < SIZE_OF_README) memcpy_P(data_buf, (PGM_P)(&(readme[0]) + offset), SIZE_OF_README - offset);
    } else {
        memcpy_P(data_buf, (PGM_P)(&(readme[0]) + offset), 16);
    }
    return data_buf;
}

Теперь «флэшка» содержит два текстовых файла. Второй — из трех строчек. Английский в этих строчках, вероятно, хромает, но не в нем дело, «в музыканта не стрелять, играет, как умеет».

Сохраняю коммит и продолжаю.

Большой файл

Теперь добавлю файл, который не читает данные из памяти программ, а собирает их на лету, в процессе запросов на чтение. Для простоты пусть это будет набор возрастающих чисел, 32-разрядный счетчик. И лучше в текстовом виде, чтобы не заморачиваться с его просмотром, а открыть тем же блокнотом. И пусть он будет, скажем, мегабайтным.

Объявляю константу-размер и функцию чтения:

#define SIZE_OF_TEST    0x100000
uint8_t * readTest(uint8_t *data_buf, uint32_t size, uint32_t offset);

Вставляю третью строчку в таблицу файлов:

    {"TESTFILETXT", SIZE_OF_TEST, readTest},

И определяю объявленную функцию:

uint8_t * readTest(uint8_t *data_buf, uint32_t size, uint32_t offset) {
    char nibble;
    if ((offset + 16) > SIZE_OF_TEST) {
    memset(data_buf, 0, 16);
    }
    offset = offset >> 3;
    nibble = (offset >> (5 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[0] = nibble;
    nibble = (offset >> (4 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[1] = nibble;
    nibble = (offset >> (3 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[2] = nibble;
    nibble = (offset >> (2 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[3] = nibble;
    nibble = (offset >> (1 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[4] = nibble;
    nibble = offset & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[5] = nibble;
    data_buf[6] = 0x20;
    data_buf[7] = 0x20;

    offset += 1;
    nibble = (offset >> (5 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[8] = nibble;
    nibble = (offset >> (4 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[9] = nibble;
    nibble = (offset >> (3 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[10] = nibble;
    nibble = (offset >> (2 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[11] = nibble;
    nibble = (offset >> (1 * 4)) & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[12] = nibble;
    nibble = offset & 0x0F;
    nibble = nibble <= 9 ? 48 + nibble : 55 + nibble;
    data_buf[13] = nibble;
    data_buf[14] = 0x20;
    data_buf[15] = 0x20;

    return data_buf;
}

Здесь наполнение файла формируется из смещения. В 16-байтный кусок можно вместить в текстовом виде 2 числа по 6 цифр (для наглядности и простоты формирования — шестнадцатиричных) с двумя пробелами для разделения.

Смещение (offset) 16-байтовых кусков изменяется на 16. Если его разделить на 8 (путем сдвига вправо на 3 разряда), то получим величину, изменяющуюся на 2. Внутри процедуры формирования числа дополнительно увеличиваем на 1. Получаем последовательно изменяющееся число, и при достижении файлом мегабайтного размера получаем набор чисел от 000000 до 01FFFF.

После компиляции в корневом каталоге «флэшки» появляется еще один файл. Размер его гораздо больше, чем память программ и оперативная память устройства, вместе взятые (16 КБ + 512 Б). Первый раз он открывается блокнотом довольно долго. Оно и понятно: процедура чтения включает в себя всякого рода вычисления. Зато второй и последующие разы файл открывается моментально, потому, что ОС уже сохранила состояние ФС устройства и прочитанные с него файлы в своем кэше и берет данные из кэша, а не с устройства. Это может вызвать некоторые проблемы при попытке чтения обновляющихся данных на следующем этапе.

Файл, изменяющийся вручную

Для оценки поведения устройства в связке с ОС, а также для отладки получения изменяющихся данных нужна имитация файла, содержимое которого зависит от действий пользователя, скажем, нажатия на кнопки. Такой счетчик нажатий в программе уже объявлен, это data_device в MassStorage.c. Надо перенести объявление ее (а заодно и data_PC) в интерфейсную часть — в заголовочный файл, а он уже подключен где надо.

Опять же, добавляю размер файла и функцию подготовки его данных:

#define SIZE_OF_USERDATA    0x10
uint8_t * readUserData(uint8_t *data_buf, uint32_t size, uint32_t offset);

Добавляю четвертую строчку таблицы:

    {"USERDATATXT", SIZE_OF_USERDATA, readUserData},

и определяю функцию:

uint8_t * readUserData(uint8_t *data_buf, uint32_t size, uint32_t offset) {
    const char *prefix = "Counter = ";
    memset(data_buf, 0x20, 16);
    memcpy(data_buf, prefix, 10);
    data_buf[10] = ((data_device >> 4) & 0xF) <= 9 ? 48 + ((data_device >> 4) & 0xf) : 55 + ((data_device >> 4) &0xF);
    data_buf[11] = (data_device & 0xF) <= 9 ? 48 + (data_device & 0xf) : 55 + (data_device &0xF);
    data_buf[12] = 'h';
    return data_buf;
}

Здесь формируется шестнадцатиричное текстовое представление переменной data_device, которое вставляется в сопроводительную подпись. Вся собранная строка добита пробелами до конца, до 16 символов. Точнее говоря, собирается на 16-символьной заготовке, изначально заполненной пробелами.

Теперь появился еще один текстовый файл. Если его открыть блокнотом, можно увидеть строку

Counter = 00h   

Можно понажимать на кнопки устройства, на верхнюю и вторую сверху. Ничего в файле не изменится. Более того, можно закрыть файл, снова понажимать на кнопки и опять открыть файл, а там по-прежнему будет 00h. А вот если нажать кнопку «Reset» на устройстве («флэшка» на компьютере определится заново), потом нажать разика три на верхнюю кнопку, и уже потом открыть файл, можно увидеть

Counter = 03h   

И эти показания уже не изменятся до очередного сброса или перезапуска питания. Получаем очередное наглядное подтверждение того, что ОС читает носитель в кэш и потом использует данные кэша.

Сохраняю коммит в GIT. Эта версия уже заслуживает номера «1.0».

Низкоуровневое чтение

Напоследок попробую считать данные из разных областей устройства командой dd и убедиться в том, что «флэшка» отвечает на запросы в точности так, как ожидается.

Для начала MBR:

dd if=/dev/sdg1 of=/home/mbr.img bs=512 count=1 skip=0
00000000: eb 58 90 4d 53 44 4f 53 35 2e 30 00 02 08 01 00  .X.MSDOS5.0.....
00000010: 02 00 00 00 00 f8 00 00 3f 00 ff 00 3e 00 00 00  ........?...>...
00000020: 00 10 00 00 08 00 00 00 00 00 00 00 02 00 00 00  ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040: 80 00 29 94 0e 0d 08 4e 4f 20 4e 41 4d 45 20 20  ..)....NO NAME
00000050: 20 20 46 41 54 33 32 20 20 20 00 00 00 00 00 00    FAT32   ......
00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000001a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000001b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000001c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000001d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000001e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000001f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa  ..............U.

Потом FAT1:

dd if=/dev/sdg1 of=/home/mbr.img bs=512 count=3 skip=1
00000000: f8 ff ff 0f ff ff ff 0f ff ff ff 0f ff ff ff 0f  ................
00000010: ff ff ff 0f 06 00 00 00 07 00 00 00 08 00 00 00  ................
00000020: 09 00 00 00 0a 00 00 00 0b 00 00 00 0c 00 00 00  ................
00000030: 0d 00 00 00 0e 00 00 00 0f 00 00 00 10 00 00 00  ................
00000040: 11 00 00 00 12 00 00 00 13 00 00 00 14 00 00 00  ................
00000050: 15 00 00 00 16 00 00 00 17 00 00 00 18 00 00 00  ................
00000060: 19 00 00 00 1a 00 00 00 1b 00 00 00 1c 00 00 00  ................
00000070: 1d 00 00 00 1e 00 00 00 1f 00 00 00 20 00 00 00  ............ ...
00000080: 21 00 00 00 22 00 00 00 23 00 00 00 24 00 00 00  !..."...#...$...
00000090: 25 00 00 00 26 00 00 00 27 00 00 00 28 00 00 00  %...&...'...(...
000000a0: 29 00 00 00 2a 00 00 00 2b 00 00 00 2c 00 00 00  )...*...+...,...
000000b0: 2d 00 00 00 2e 00 00 00 2f 00 00 00 30 00 00 00  -......./...0...
000000c0: 31 00 00 00 32 00 00 00 33 00 00 00 34 00 00 00  1...2...3...4...
000000d0: 35 00 00 00 36 00 00 00 37 00 00 00 38 00 00 00  5...6...7...8...
000000e0: 39 00 00 00 3a 00 00 00 3b 00 00 00 3c 00 00 00  9...:...;...<...
000000f0: 3d 00 00 00 3e 00 00 00 3f 00 00 00 40 00 00 00  =...>...?...@...
00000100: 41 00 00 00 42 00 00 00 43 00 00 00 44 00 00 00  A...B...C...D...
00000110: 45 00 00 00 46 00 00 00 47 00 00 00 48 00 00 00  E...F...G...H...
00000120: 49 00 00 00 4a 00 00 00 4b 00 00 00 4c 00 00 00  I...J...K...L...
00000130: 4d 00 00 00 4e 00 00 00 4f 00 00 00 50 00 00 00  M...N...O...P...
00000140: 51 00 00 00 52 00 00 00 53 00 00 00 54 00 00 00  Q...R...S...T...
00000150: 55 00 00 00 56 00 00 00 57 00 00 00 58 00 00 00  U...V...W...X...
00000160: 59 00 00 00 5a 00 00 00 5b 00 00 00 5c 00 00 00  Y...Z...[...\...
00000170: 5d 00 00 00 5e 00 00 00 5f 00 00 00 60 00 00 00  ]...^..._...`...
00000180: 61 00 00 00 62 00 00 00 63 00 00 00 64 00 00 00  a...b...c...d...
00000190: 65 00 00 00 66 00 00 00 67 00 00 00 68 00 00 00  e...f...g...h...
000001a0: 69 00 00 00 6a 00 00 00 6b 00 00 00 6c 00 00 00  i...j...k...l...
000001b0: 6d 00 00 00 6e 00 00 00 6f 00 00 00 70 00 00 00  m...n...o...p...
000001c0: 71 00 00 00 72 00 00 00 73 00 00 00 74 00 00 00  q...r...s...t...
000001d0: 75 00 00 00 76 00 00 00 77 00 00 00 78 00 00 00  u...v...w...x...
000001e0: 79 00 00 00 7a 00 00 00 7b 00 00 00 7c 00 00 00  y...z...{...|...
000001f0: 7d 00 00 00 7e 00 00 00 7f 00 00 00 80 00 00 00  }...~...........
00000200: 81 00 00 00 82 00 00 00 83 00 00 00 84 00 00 00  ................
00000210: 85 00 00 00 86 00 00 00 87 00 00 00 88 00 00 00  ................
00000220: 89 00 00 00 8a 00 00 00 8b 00 00 00 8c 00 00 00  ................
00000230: 8d 00 00 00 8e 00 00 00 8f 00 00 00 90 00 00 00  ................
00000240: 91 00 00 00 92 00 00 00 93 00 00 00 94 00 00 00  ................
00000250: 95 00 00 00 96 00 00 00 97 00 00 00 98 00 00 00  ................
00000260: 99 00 00 00 9a 00 00 00 9b 00 00 00 9c 00 00 00  ................
00000270: 9d 00 00 00 9e 00 00 00 9f 00 00 00 a0 00 00 00  ................
00000280: a1 00 00 00 a2 00 00 00 a3 00 00 00 a4 00 00 00  ................
00000290: a5 00 00 00 a6 00 00 00 a7 00 00 00 a8 00 00 00  ................
000002a0: a9 00 00 00 aa 00 00 00 ab 00 00 00 ac 00 00 00  ................
000002b0: ad 00 00 00 ae 00 00 00 af 00 00 00 b0 00 00 00  ................
000002c0: b1 00 00 00 b2 00 00 00 b3 00 00 00 b4 00 00 00  ................
000002d0: b5 00 00 00 b6 00 00 00 b7 00 00 00 b8 00 00 00  ................
000002e0: b9 00 00 00 ba 00 00 00 bb 00 00 00 bc 00 00 00  ................
000002f0: bd 00 00 00 be 00 00 00 bf 00 00 00 c0 00 00 00  ................
00000300: c1 00 00 00 c2 00 00 00 c3 00 00 00 c4 00 00 00  ................
00000310: c5 00 00 00 c6 00 00 00 c7 00 00 00 c8 00 00 00  ................
00000320: c9 00 00 00 ca 00 00 00 cb 00 00 00 cc 00 00 00  ................
00000330: cd 00 00 00 ce 00 00 00 cf 00 00 00 d0 00 00 00  ................
00000340: d1 00 00 00 d2 00 00 00 d3 00 00 00 d4 00 00 00  ................
00000350: d5 00 00 00 d6 00 00 00 d7 00 00 00 d8 00 00 00  ................
00000360: d9 00 00 00 da 00 00 00 db 00 00 00 dc 00 00 00  ................
00000370: dd 00 00 00 de 00 00 00 df 00 00 00 e0 00 00 00  ................
00000380: e1 00 00 00 e2 00 00 00 e3 00 00 00 e4 00 00 00  ................
00000390: e5 00 00 00 e6 00 00 00 e7 00 00 00 e8 00 00 00  ................
000003a0: e9 00 00 00 ea 00 00 00 eb 00 00 00 ec 00 00 00  ................
000003b0: ed 00 00 00 ee 00 00 00 ef 00 00 00 f0 00 00 00  ................
000003c0: f1 00 00 00 f2 00 00 00 f3 00 00 00 f4 00 00 00  ................
000003d0: f5 00 00 00 f6 00 00 00 f7 00 00 00 f8 00 00 00  ................
000003e0: f9 00 00 00 fa 00 00 00 fb 00 00 00 fc 00 00 00  ................
000003f0: fd 00 00 00 fe 00 00 00 ff 00 00 00 00 01 00 00  ................
00000400: 01 01 00 00 02 01 00 00 03 01 00 00 04 01 00 00  ................
00000410: ff ff ff 0f ff ff ff 0f ff ff ff 0f 00 00 00 00  ................

Дальше до конца кластера — нули. С параметром skip=9 читается FAT2 с тем же самым результатом.

Корневой каталог:

dd if=/dev/sdg1 of=/home/mbr.img bs=512 count=3 skip=17
00000000: 4c 55 46 41 5f 31 20 20 20 20 20 08 00 00 00 00  LUFA_1     .....
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000020: 4b 45 59 5f 46 49 4c 45 54 58 54 20 00 04 72 00  KEY_FILETXT ..r.
00000030: 79 40 79 40 00 00 00 00 79 40 03 00 26 00 00 00  y@y@....y@..&...
00000040: 52 45 41 44 4d 45 20 20 54 58 54 20 00 04 72 00  README  TXT ..r.
00000050: 79 40 79 40 00 00 00 00 79 40 04 00 91 00 00 00  y@y@....y@......
00000060: 54 45 53 54 46 49 4c 45 54 58 54 20 00 04 72 00  TESTFILETXT ..r.
00000070: 79 40 79 40 00 00 00 00 79 40 05 00 00 00 10 00  y@y@....y@......
00000080: 55 53 45 52 44 41 54 41 54 58 54 20 00 04 72 00  USERDATATXT ..r.
00000090: 79 40 79 40 00 00 00 00 79 40 05 01 10 00 00 00  y@y@....y@......
000000a0: 44 41 54 41 20 20 20 20 42 49 4e 20 00 04 72 00  DATA    BIN ..r.
000000b0: 79 40 79 40 00 00 00 00 79 40 06 01 80 00 00 00  y@y@....y@......

Также показана только непустая часть кластера. И, наконец, для примера один из файлов — KEY_FILE.TXT:

dd if=/dev/sdg1 of=/home/mbr.img bs=512 count=1 skip=25
00000000: 7b 37 30 36 35 63 32 33 61 2d 33 38 31 38 2d 34  {7065c23a-3818-4
00000010: 33 63 35 2d 62 64 66 30 2d 35 35 35 36 37 61 64  3c5-bdf0-55567ad
00000020: 65 33 31 65 37 7d 00 00 00 00 00 00 00 00 00 00  e31e7}..........

Итог

В результате получено устройство, которое успешно делает вид, что оно — флэшка с файловой системой FAT32 и набором файлов, данные для которых можно брать из памяти устройства, можно формировать на лету путем вычислений, можно получать из каких-то внешних источников, хотя бы и от кнопок.

Правда, с получением изменяющихся данных есть некоторые сложности из-за кэширования их средствами ОС. Эту тему рассмотрю чуть позже.

Имитируемые файлы пока что только читаются. Хотя можно в текстовом редакторе их поправить, дать команду записи и никаких ошибок при этом не получить. Ибо изменения также будут делаться в кэше, а при попытке ОС сбросить изменения в носитель она получит ответ, что «все в порядке, принято». Тему записи в устройство и использования записанных данных откладываю пока на неопределенный срок


Теги: