Commit c30a15e5 authored by Donggeun Kim's avatar Donggeun Kim Committed by Wolfgang Denk
Browse files

FAT: Add FAT write feature



In some cases, saving data in RAM as a file with FAT format is required.
This patch allows the file to be written in FAT formatted partition.

The usage is similar with reading a file.
First, fat_register_device function is called before file_fat_write function
in order to set target partition.
Then, file_fat_write function is invoked with desired file name,
start ram address for writing data, and file size.
Signed-off-by: default avatarDonggeun Kim <dg77.kim@samsung.com>
Signed-off-by: default avatarKyungmin Park <kyungmin.park@samsung.com>
parent eea63e05
......@@ -1175,6 +1175,11 @@ The following options need to be configured:
to disable the command chpart. This is the default when you
have not defined a custom partition
- FAT(File Allocation Table) filesystem write function support:
CONFIG_FAT_WRITE
Support for saving memory data as a file
in FAT formatted partition
- Keyboard Support:
CONFIG_ISA_KEYBOARD
......
......@@ -25,6 +25,7 @@ LIB = $(obj)libfat.o
AOBJS =
COBJS-$(CONFIG_CMD_FAT) := fat.o
COBJS-$(CONFIG_FAT_WRITE):= fat_write.o
ifndef CONFIG_SPL_BUILD
COBJS-$(CONFIG_CMD_FAT) += file.o
......
......@@ -46,6 +46,7 @@ static void downcase (char *str)
static block_dev_desc_t *cur_dev = NULL;
static unsigned long part_offset = 0;
static unsigned long part_size;
static int cur_part = 1;
......@@ -99,6 +100,7 @@ int fat_register_device (block_dev_desc_t * dev_desc, int part_no)
if (!get_partition_info(dev_desc, part_no, &info)) {
part_offset = info.start;
cur_part = part_no;
part_size = info.size;
} else if ((strncmp((char *)&buffer[DOS_FS_TYPE_OFFSET], "FAT", 3) == 0) ||
(strncmp((char *)&buffer[DOS_FS32_TYPE_OFFSET], "FAT32", 5) == 0)) {
/* ok, we assume we are on a PBR only */
......
/*
* fat_write.c
*
* R/W (V)FAT 12/16/32 filesystem implementation by Donggeun Kim
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <common.h>
#include <command.h>
#include <config.h>
#include <fat.h>
#include <asm/byteorder.h>
#include <part.h>
#include "fat.c"
static void uppercase(char *str, int len)
{
int i;
for (i = 0; i < len; i++) {
TOUPPER(*str);
str++;
}
}
static int total_sector;
static int disk_write(__u32 startblock, __u32 getsize, __u8 *bufptr)
{
if (cur_dev == NULL)
return -1;
if (startblock + getsize > total_sector) {
printf("error: overflow occurs\n");
return -1;
}
startblock += part_offset;
if (cur_dev->block_read) {
return cur_dev->block_write(cur_dev->dev, startblock, getsize,
(unsigned long *) bufptr);
}
return -1;
}
/*
* Set short name in directory entry
*/
static void set_name(dir_entry *dirent, const char *filename)
{
char s_name[VFAT_MAXLEN_BYTES];
char *period;
int period_location, len, i, ext_num;
if (filename == NULL)
return;
len = strlen(filename);
if (len == 0)
return;
memcpy(s_name, filename, len);
uppercase(s_name, len);
period = strchr(s_name, '.');
if (period == NULL) {
period_location = len;
ext_num = 0;
} else {
period_location = period - s_name;
ext_num = len - period_location - 1;
}
/* Pad spaces when the length of file name is shorter than eight */
if (period_location < 8) {
memcpy(dirent->name, s_name, period_location);
for (i = period_location; i < 8; i++)
dirent->name[i] = ' ';
} else if (period_location == 8) {
memcpy(dirent->name, s_name, period_location);
} else {
memcpy(dirent->name, s_name, 6);
dirent->name[6] = '~';
dirent->name[7] = '1';
}
if (ext_num < 3) {
memcpy(dirent->ext, s_name + period_location + 1, ext_num);
for (i = ext_num; i < 3; i++)
dirent->ext[i] = ' ';
} else
memcpy(dirent->ext, s_name + period_location + 1, 3);
debug("name : %s\n", dirent->name);
debug("ext : %s\n", dirent->ext);
}
/*
* Write fat buffer into block device
*/
static int flush_fat_buffer(fsdata *mydata)
{
int getsize = FATBUFBLOCKS;
__u32 fatlength = mydata->fatlength;
__u8 *bufptr = mydata->fatbuf;
__u32 startblock = mydata->fatbufnum * FATBUFBLOCKS;
fatlength *= mydata->sect_size;
startblock += mydata->fat_sect;
if (getsize > fatlength)
getsize = fatlength;
/* Write FAT buf */
if (disk_write(startblock, getsize, bufptr) < 0) {
debug("error: writing FAT blocks\n");
return -1;
}
return 0;
}
/*
* Get the entry at index 'entry' in a FAT (12/16/32) table.
* On failure 0x00 is returned.
* When bufnum is changed, write back the previous fatbuf to the disk.
*/
static __u32 get_fatent_value(fsdata *mydata, __u32 entry)
{
__u32 bufnum;
__u32 off16, offset;
__u32 ret = 0x00;
__u16 val1, val2;
switch (mydata->fatsize) {
case 32:
bufnum = entry / FAT32BUFSIZE;
offset = entry - bufnum * FAT32BUFSIZE;
break;
case 16:
bufnum = entry / FAT16BUFSIZE;
offset = entry - bufnum * FAT16BUFSIZE;
break;
case 12:
bufnum = entry / FAT12BUFSIZE;
offset = entry - bufnum * FAT12BUFSIZE;
break;
default:
/* Unsupported FAT size */
return ret;
}
debug("FAT%d: entry: 0x%04x = %d, offset: 0x%04x = %d\n",
mydata->fatsize, entry, entry, offset, offset);
/* Read a new block of FAT entries into the cache. */
if (bufnum != mydata->fatbufnum) {
int getsize = FATBUFBLOCKS;
__u8 *bufptr = mydata->fatbuf;
__u32 fatlength = mydata->fatlength;
__u32 startblock = bufnum * FATBUFBLOCKS;
if (getsize > fatlength)
getsize = fatlength;
fatlength *= mydata->sect_size; /* We want it in bytes now */
startblock += mydata->fat_sect; /* Offset from start of disk */
/* Write back the fatbuf to the disk */
if (mydata->fatbufnum != -1) {
if (flush_fat_buffer(mydata) < 0)
return -1;
}
if (disk_read(startblock, getsize, bufptr) < 0) {
debug("Error reading FAT blocks\n");
return ret;
}
mydata->fatbufnum = bufnum;
}
/* Get the actual entry from the table */
switch (mydata->fatsize) {
case 32:
ret = FAT2CPU32(((__u32 *) mydata->fatbuf)[offset]);
break;
case 16:
ret = FAT2CPU16(((__u16 *) mydata->fatbuf)[offset]);
break;
case 12:
off16 = (offset * 3) / 4;
switch (offset & 0x3) {
case 0:
ret = FAT2CPU16(((__u16 *) mydata->fatbuf)[off16]);
ret &= 0xfff;
break;
case 1:
val1 = FAT2CPU16(((__u16 *)mydata->fatbuf)[off16]);
val1 &= 0xf000;
val2 = FAT2CPU16(((__u16 *)mydata->fatbuf)[off16 + 1]);
val2 &= 0x00ff;
ret = (val2 << 4) | (val1 >> 12);
break;
case 2:
val1 = FAT2CPU16(((__u16 *)mydata->fatbuf)[off16]);
val1 &= 0xff00;
val2 = FAT2CPU16(((__u16 *)mydata->fatbuf)[off16 + 1]);
val2 &= 0x000f;
ret = (val2 << 8) | (val1 >> 8);
break;
case 3:
ret = FAT2CPU16(((__u16 *)mydata->fatbuf)[off16]);
ret = (ret & 0xfff0) >> 4;
break;
default:
break;
}
break;
}
debug("FAT%d: ret: %08x, entry: %08x, offset: %04x\n",
mydata->fatsize, ret, entry, offset);
return ret;
}
#ifdef CONFIG_SUPPORT_VFAT
/*
* Set the file name information from 'name' into 'slotptr',
*/
static int str2slot(dir_slot *slotptr, const char *name, int *idx)
{
int j, end_idx = 0;
for (j = 0; j <= 8; j += 2) {
if (name[*idx] == 0x00) {
slotptr->name0_4[j] = 0;
slotptr->name0_4[j + 1] = 0;
end_idx++;
goto name0_4;
}
slotptr->name0_4[j] = name[*idx];
(*idx)++;
end_idx++;
}
for (j = 0; j <= 10; j += 2) {
if (name[*idx] == 0x00) {
slotptr->name5_10[j] = 0;
slotptr->name5_10[j + 1] = 0;
end_idx++;
goto name5_10;
}
slotptr->name5_10[j] = name[*idx];
(*idx)++;
end_idx++;
}
for (j = 0; j <= 2; j += 2) {
if (name[*idx] == 0x00) {
slotptr->name11_12[j] = 0;
slotptr->name11_12[j + 1] = 0;
end_idx++;
goto name11_12;
}
slotptr->name11_12[j] = name[*idx];
(*idx)++;
end_idx++;
}
if (name[*idx] == 0x00)
return 1;
return 0;
/* Not used characters are filled with 0xff 0xff */
name0_4:
for (; end_idx < 5; end_idx++) {
slotptr->name0_4[end_idx * 2] = 0xff;
slotptr->name0_4[end_idx * 2 + 1] = 0xff;
}
end_idx = 5;
name5_10:
end_idx -= 5;
for (; end_idx < 6; end_idx++) {
slotptr->name5_10[end_idx * 2] = 0xff;
slotptr->name5_10[end_idx * 2 + 1] = 0xff;
}
end_idx = 11;
name11_12:
end_idx -= 11;
for (; end_idx < 2; end_idx++) {
slotptr->name11_12[end_idx * 2] = 0xff;
slotptr->name11_12[end_idx * 2 + 1] = 0xff;
}
return 1;
}
static int is_next_clust(fsdata *mydata, dir_entry *dentptr);
static void flush_dir_table(fsdata *mydata, dir_entry **dentptr);
/*
* Fill dir_slot entries with appropriate name, id, and attr
* The real directory entry is returned by 'dentptr'
*/
static void
fill_dir_slot(fsdata *mydata, dir_entry **dentptr, const char *l_name)
{
dir_slot *slotptr = (dir_slot *)get_vfatname_block;
__u8 counter, checksum;
int idx = 0, ret;
char s_name[16];
/* Get short file name and checksum value */
strncpy(s_name, (*dentptr)->name, 16);
checksum = mkcksum(s_name);
do {
memset(slotptr, 0x00, sizeof(dir_slot));
ret = str2slot(slotptr, l_name, &idx);
slotptr->id = ++counter;
slotptr->attr = ATTR_VFAT;
slotptr->alias_checksum = checksum;
slotptr++;
} while (ret == 0);
slotptr--;
slotptr->id |= LAST_LONG_ENTRY_MASK;
while (counter >= 1) {
if (is_next_clust(mydata, *dentptr)) {
/* A new cluster is allocated for directory table */
flush_dir_table(mydata, dentptr);
}
memcpy(*dentptr, slotptr, sizeof(dir_slot));
(*dentptr)++;
slotptr--;
counter--;
}
if (is_next_clust(mydata, *dentptr)) {
/* A new cluster is allocated for directory table */
flush_dir_table(mydata, dentptr);
}
}
static __u32 dir_curclust;
/*
* Extract the full long filename starting at 'retdent' (which is really
* a slot) into 'l_name'. If successful also copy the real directory entry
* into 'retdent'
* If additional adjacent cluster for directory entries is read into memory,
* then 'get_vfatname_block' is copied into 'get_dentfromdir_block' and
* the location of the real directory entry is returned by 'retdent'
* Return 0 on success, -1 otherwise.
*/
static int
get_long_file_name(fsdata *mydata, int curclust, __u8 *cluster,
dir_entry **retdent, char *l_name)
{
dir_entry *realdent;
dir_slot *slotptr = (dir_slot *)(*retdent);
dir_slot *slotptr2 = NULL;
__u8 *buflimit = cluster + mydata->sect_size * ((curclust == 0) ?
PREFETCH_BLOCKS :
mydata->clust_size);
__u8 counter = (slotptr->id & ~LAST_LONG_ENTRY_MASK) & 0xff;
int idx = 0, cur_position = 0;
if (counter > VFAT_MAXSEQ) {
debug("Error: VFAT name is too long\n");
return -1;
}
while ((__u8 *)slotptr < buflimit) {
if (counter == 0)
break;
if (((slotptr->id & ~LAST_LONG_ENTRY_MASK) & 0xff) != counter)
return -1;
slotptr++;
counter--;
}
if ((__u8 *)slotptr >= buflimit) {
if (curclust == 0)
return -1;
curclust = get_fatent_value(mydata, dir_curclust);
if (CHECK_CLUST(curclust, mydata->fatsize)) {
debug("curclust: 0x%x\n", curclust);
printf("Invalid FAT entry\n");
return -1;
}
dir_curclust = curclust;
if (get_cluster(mydata, curclust, get_vfatname_block,
mydata->clust_size * mydata->sect_size) != 0) {
debug("Error: reading directory block\n");
return -1;
}
slotptr2 = (dir_slot *)get_vfatname_block;
while (counter > 0) {
if (((slotptr2->id & ~LAST_LONG_ENTRY_MASK)
& 0xff) != counter)
return -1;
slotptr2++;
counter--;
}
/* Save the real directory entry */
realdent = (dir_entry *)slotptr2;
while ((__u8 *)slotptr2 > get_vfatname_block) {
slotptr2--;
slot2str(slotptr2, l_name, &idx);
}
} else {
/* Save the real directory entry */
realdent = (dir_entry *)slotptr;
}
do {
slotptr--;
if (slot2str(slotptr, l_name, &idx))
break;
} while (!(slotptr->id & LAST_LONG_ENTRY_MASK));
l_name[idx] = '\0';
if (*l_name == DELETED_FLAG)
*l_name = '\0';
else if (*l_name == aRING)
*l_name = DELETED_FLAG;
downcase(l_name);
/* Return the real directory entry */
*retdent = realdent;
if (slotptr2) {
memcpy(get_dentfromdir_block, get_vfatname_block,
mydata->clust_size * mydata->sect_size);
cur_position = (__u8 *)realdent - get_vfatname_block;
*retdent = (dir_entry *) &get_dentfromdir_block[cur_position];
}
return 0;
}
#endif
/*
* Set the entry at index 'entry' in a FAT (16/32) table.
*/
static int set_fatent_value(fsdata *mydata, __u32 entry, __u32 entry_value)
{
__u32 bufnum, offset;
switch (mydata->fatsize) {
case 32:
bufnum = entry / FAT32BUFSIZE;
offset = entry - bufnum * FAT32BUFSIZE;
break;
case 16:
bufnum = entry / FAT16BUFSIZE;
offset = entry - bufnum * FAT16BUFSIZE;
break;
default:
/* Unsupported FAT size */
return -1;
}
/* Read a new block of FAT entries into the cache. */
if (bufnum != mydata->fatbufnum) {
int getsize = FATBUFBLOCKS;
__u8 *bufptr = mydata->fatbuf;
__u32 fatlength = mydata->fatlength;
__u32 startblock = bufnum * FATBUFBLOCKS;
fatlength *= mydata->sect_size;
startblock += mydata->fat_sect;
if (getsize > fatlength)
getsize = fatlength;
if (mydata->fatbufnum != -1) {
if (flush_fat_buffer(mydata) < 0)
return -1;
}
if (disk_read(startblock, getsize, bufptr) < 0) {
debug("Error reading FAT blocks\n");
return -1;
}
mydata->fatbufnum = bufnum;
}
/* Set the actual entry */
switch (mydata->fatsize) {
case 32:
((__u32 *) mydata->fatbuf)[offset] = cpu_to_le32(entry_value);
break;
case 16:
((__u16 *) mydata->fatbuf)[offset] = cpu_to_le16(entry_value);
break;
default:
return -1;
}
return 0;
}
/*
* Determine the entry value at index 'entry' in a FAT (16/32) table
*/
static __u32 determine_fatent(fsdata *mydata, __u32 entry)
{
__u32 next_fat, next_entry = entry + 1;
while (1) {
next_fat = get_fatent_value(mydata, next_entry);
if (next_fat == 0) {
set_fatent_value(mydata, entry, next_entry);
break;
}
next_entry++;
}
debug("FAT%d: entry: %08x, entry_value: %04x\n",
mydata->fatsize, entry, next_entry);
return next_entry;
}
/*
* Write at most 'size' bytes from 'buffer' into the specified cluster.
* Return 0 on success, -1 otherwise.
*/
static int
set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer,
unsigned long size)
{
int idx = 0;
__u32 startsect;
if (clustnum > 0)
startsect = mydata->data_begin +
clustnum * mydata->clust_size;
else
startsect = mydata->rootdir_sect;
debug("clustnum: %d, startsect: %d\n", clustnum, startsect);
if (disk_write(startsect, size / mydata->sect_size, buffer) < 0) {
debug("Error writing data\n");
return -1;
}
if (size % mydata->sect_size) {
__u8 tmpbuf[mydata->sect_size];
idx = size / mydata->sect_size;
buffer += idx * mydata->sect_size;
memcpy(tmpbuf, buffer, size % mydata->sect_size);
if (disk_write(startsect + idx, 1, tmpbuf) < 0) {
debug("Error writing data\n");
return -1;
}
return 0;
}
return 0;