Tizen 2.0 Release
[platform/kernel/u-boot.git] / common / cmd_onenand_ext2.c
1 /*
2  * ext2 filesystem for onenand device.
3  *
4  * Copyright(C) 2009 Samsung Electronics
5  * InKi Dae <inki.dae@samsung.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20  * MA 02111-1307 USA
21  */
22
23 #include <config.h>
24 #include <common.h>
25 #include <version.h>
26 #include <stdarg.h>
27 #include <malloc.h>
28 #include <lcd.h>
29 #include <linux/types.h>
30 #include <asm/io.h>
31 #include <asm/global_data.h>
32
33 #include <onenand_ext2.h>
34
35 #define IMAGE_BASE              (0x380000)
36 #define IMAGE_SIZE              (8 * 1024 * 1024)
37 #define ONENAND_READ_SIZE       (4 * 1024)
38
39 #define BOOT_BLOCK_SIZE         (1024)
40 #define DATA_BLOCK_UNIT         (512)
41 #define INODE_TABLE_ENTRY_SIZE  (128)
42
43 extern int onenand_read(ulong off, char *buf, unsigned int *out_size);
44
45 /* it indicates block size of ext2 filesystem formatted. */
46 unsigned int block_size_of_fs = 0;
47
48 /* it indicates inode block number for root directory and current directory. */
49 unsigned int root_inode, current_inode;
50
51 /* it indicates inode block size for finding the end of directory entry. */
52 unsigned int inode_block_size = 0;
53
54 /* it indicates the location of inode table in group structure. */
55 unsigned int inode_table_location;
56
57 /* it is used to store contents of block data and table entry. */
58 char data_buf[ONENAND_READ_SIZE], table_buf[ONENAND_READ_SIZE];
59
60 /*
61  * read a block from onenand.
62  *
63  * @offset : onenand offset for reading a block.
64  * @blk_count : block count to be read.
65  *
66  * return value is the size read from onenand.
67  */
68 static unsigned int read_blk_from_onenand(unsigned int offset, unsigned int blk_count, char *buf)
69 {
70         unsigned int size, i, out_size = 0;
71
72         for (i = 0; i < blk_count; i++) {
73                 offset += (i * ONENAND_READ_SIZE);
74                 onenand_read(IMAGE_BASE + offset, buf, &size);
75                 out_size += size;
76                 buf += size;
77         }
78
79         return out_size;
80 }
81
82 static int get_sblock(struct ext2_sblock *sblock)
83 {
84         char *pbuf = NULL;
85
86         pbuf = (char *) table_buf;
87
88         /* first, read 4K block from onenand. */
89         read_blk_from_onenand(0, 1, pbuf);
90
91         /* sblock places in next to 1k, boot sector so read from there. */
92         memcpy(sblock, pbuf + BOOT_BLOCK_SIZE, sizeof(struct ext2_sblock));
93
94         dprint("total_inodes = %d, block_size = %d\n", sblock->total_inodes,
95                 sblock->log2_block_size);
96
97         return 0;
98 }
99
100 static int get_group_dec(struct ext2_block_group *group)
101 {
102         char *pbuf = NULL;
103         unsigned int offset = 0;
104
105         pbuf = (char *) table_buf;
106
107         /* get group descriptor. */
108         if (block_size_of_fs == 1024)
109                 /* boot block : 1k, super block : 1k. */
110                 offset = block_size_of_fs * 2;
111         else if (block_size_of_fs == 4096)
112                 /* boot block : 1k, super block : 3k. */
113                 offset = block_size_of_fs;
114
115         read_blk_from_onenand(offset, 1, pbuf);
116
117         memcpy(group, pbuf, sizeof(struct ext2_block_group));
118
119         dprint("block_id = %d, inode_id = %d, inode_table_id = %d\n",
120                 group->block_id, group->inode_id, group->inode_table_id);
121
122         return 0;
123 }
124
125 static int get_root_inode_entry(struct ext2_inode *inode,
126         struct ext2_block_group *group)
127 {
128         char *pbuf = NULL;
129
130         pbuf = (char *) table_buf;
131
132         if (group == NULL) {
133                 dprint("group is NULL.\n");
134                 return -1;
135         }
136
137         /* get location of inode table. */
138         inode_table_location = group->inode_table_id * block_size_of_fs;
139
140         dprint("inode table location = %d\n", inode_table_location);
141
142         read_blk_from_onenand(inode_table_location, 1, pbuf);
143
144         /* 
145          * inode table has 128byte per entry and
146          * root inode places in second entry.
147          */
148         memcpy(inode, pbuf + INODE_TABLE_ENTRY_SIZE,
149                 sizeof(struct ext2_inode));
150
151         /* get inode block size and used to find the end of directory entry. */
152         inode_block_size = inode->blockcnt * DATA_BLOCK_UNIT;
153
154         dprint("dir_blocks = 0x%8x, size = %d, blockcnt = %d\n", inode->b.blocks.dir_blocks[0],
155                 inode->size, inode->blockcnt);
156
157         return 0;
158 }
159
160 /* 
161  * get root directory entry.
162  *
163  * @dirent : directory entry structure for storing directory entry value.
164  * @inode : instance pointer of inode entry.
165  *
166  * the offset of root directory entry is returned.
167  */
168
169 static unsigned int get_root_dir_entry(struct ext2_dirent *dirent, struct ext2_inode *inode)
170 {
171         char *pbuf = NULL;
172         unsigned int offset = 0;
173
174         pbuf = (char *) table_buf;
175
176         if (inode == NULL) {
177                 dprint("inode is NULL.\n");
178                 return 0;
179         }
180
181         /* get block number stored with data of inode. */
182         offset = inode->b.blocks.dir_blocks[0] * block_size_of_fs;
183
184         read_blk_from_onenand(offset, 1, pbuf);
185
186         memcpy(dirent, pbuf, sizeof(struct ext2_dirent));
187
188         dprint("first entry name of root directory is name = %s\n",
189                 dirent->name);
190
191         return offset;
192 }
193
194 /* initialize ext2 filesystem and return pointer of root directory entry. */
195 unsigned int mount_ext2fs(void)
196 {
197         struct ext2_sblock sblock;
198         struct ext2_block_group group;
199         struct ext2_inode inode;
200         struct ext2_dirent dirent;
201
202         unsigned int need_block = 0, ret = 0, offset = 0;
203
204         ret = get_sblock(&sblock);
205         if (ret < 0) {
206                 dprint("sblock is NULL.\n");
207                 return -1;
208         }
209
210         /* 
211          * in case that log2_block_size is 0, block_size is 1024,
212          * 2048 for 1 and 4096 for 2. 
213          */
214         switch (sblock.log2_block_size + 1) {
215         case 1:
216                 block_size_of_fs = 1024;
217                 break;
218         case 2:
219                 block_size_of_fs = 2048;
220                 break;
221         case 3:
222                 block_size_of_fs = 4096;
223                 break;
224         default:
225                 block_size_of_fs = 4096;
226                 break;
227         }
228
229         dprint("block size of filesystem = %d\n", block_size_of_fs);
230
231         /* get block size for inode table. */
232         need_block = sblock.total_inodes * INODE_TABLE_ENTRY_SIZE /
233                 block_size_of_fs;
234
235         dprint("need_block for inode table = %d\n", need_block);
236
237         ret = get_group_dec(&group);
238         if (ret < 0) {
239                 dprint("group_dec is NULL.\n");
240                 return -1;
241         }
242
243         ret = get_root_inode_entry(&inode, &group);
244         if (ret < 0) {
245                 dprint("root_inode_entry is NULL.\n");
246                 return -1;
247         }
248
249         offset = get_root_dir_entry(&dirent, &inode);
250         if (offset == 0) {
251                 dprint("root_dir_entry is NULL.\n");
252                 return -1;
253         }
254
255         return offset;
256 }
257
258 /* 
259  * get inode to file.
260  *
261  * @inode_table : the offset of inode table to file.
262  * @inode : inode structure for storing inode entry value.
263  */
264 static int get_inode(unsigned int inode_table, struct ext2_inode *inode)
265 {
266         unsigned int offset, extra_block_count, need_block = 0;
267         char *pbuf = NULL;
268
269         pbuf = (char *) table_buf;
270
271         /* get block count. */
272         need_block = (inode_table / block_size_of_fs);
273
274         /* 
275          * get extra block count for loading from onenand device
276          * considering 1k and 2k block size for filesystem.
277          */
278         extra_block_count = (block_size_of_fs * need_block) / ONENAND_READ_SIZE;
279
280         offset = inode_table_location + (ONENAND_READ_SIZE * extra_block_count);
281
282         read_blk_from_onenand(offset, 1, pbuf);
283
284         /* also subtract as increased offset. */
285         inode_table -= (ONENAND_READ_SIZE * extra_block_count);
286
287         /* get inode entry to file. */
288         memcpy(inode, (char *) pbuf + inode_table, sizeof(struct ext2_inode));
289
290         return 0;
291 }
292
293 /*
294  * get directory entry to file.
295  *
296  * @in_buf : pointer of system memory to directory entry.
297  * @dirent : directory entry structure for storing directory entry value.
298  */
299 static int get_dir_entry(char *in_buf, struct ext2_dirent *dirent)
300 {
301         memcpy(dirent, (char *) in_buf, sizeof(struct ext2_dirent));
302
303         dirent->name[dirent->namelen] = '\0';
304
305         return 0;
306 }
307 /*
308  * get inode offset.
309  * 
310  * @dirent : instance pointer to directory entry structure.
311  *
312  * return value is the offset to inode table.
313  */
314 static int get_inode_offset(struct ext2_dirent *dirent)
315 {
316         int inode_table;
317
318         if (dirent == NULL) {
319                 printf("dirent is NULL.\n");
320                 return -1;
321         }
322
323         inode_table = (dirent->inode - 1) * INODE_TABLE_ENTRY_SIZE;
324
325         return inode_table;
326 }
327
328 /* 
329  * move in_inode which is directory entry pointer to next directory entry.
330  *
331  * @dirent : pointer of ext2_dirent structure.
332  */
333 static unsigned int next_dir_entry(struct ext2_dirent *dirent)
334 {
335         static unsigned int sum_point = 0;
336
337         /* 
338          * it finds the end of directory entry.
339          * 
340          * directroy entries are stored in data block.
341          * (inode_block_size = data block count * data block size.)
342          */
343         sum_point += dirent->direntlen;
344         if ( sum_point >= inode_block_size) {
345                 sum_point = 0;
346
347                 return 0;
348         }
349
350         return dirent->direntlen;
351 }
352
353 /*
354  * find file matched with filename.
355  *
356  * @in_inode : the offset of current directory entry.
357  * @filename : the file name for finding.
358  * @dirent : the pointer of directory entry structure found.
359  *
360  * return value : inode number of directory entry.
361  */
362 int find_file_ext2(unsigned int in_inode, const char *filename,
363         struct ext2_dirent *dirent)
364 {
365         unsigned int next_point = 0;
366         char *pbuf = NULL;
367
368         pbuf = (char *) table_buf;
369
370         read_blk_from_onenand(in_inode, 1, pbuf);
371
372         get_dir_entry(pbuf, dirent);
373
374         if ((strcmp(dirent->name, filename)) == 0) {
375                 return dirent->inode;
376         }
377
378         next_point = next_dir_entry(dirent);
379
380         do {
381                 get_dir_entry(pbuf, dirent);
382
383                 dprint("soure file = %s, dst file = %s, len = %d\n", filename,
384                         dirent->name, dirent->namelen);
385
386                 if ((strcmp(dirent->name, filename)) == 0) {
387                         return dirent->inode;
388                 }
389
390                 next_point = next_dir_entry(dirent);
391                 pbuf += next_point;
392
393         } while (next_point > 0);
394
395         printf("failed to find file.\n");
396
397         return -1;
398 }
399
400 /*
401  * get the location of inode to file.
402  *
403  * @in_inode : the offset to current directory entry.
404  * @name : file name.
405  *
406  * return value is the offset of inode table to file.
407  */
408 int open_file_ext2(unsigned int in_inode, const char *name)
409 {
410         unsigned int directory_inode, inode_table;
411         struct ext2_dirent dirent;
412
413         directory_inode = find_file_ext2(in_inode, name, &dirent);
414
415         /* 
416          * the location of file block =
417          * inode table base + (directory inode number  - 1) * inode table entry size.
418          */
419         inode_table = (directory_inode - 1) * INODE_TABLE_ENTRY_SIZE;
420
421         dprint("directory_inode = %d, inode_table = %d\n", directory_inode, inode_table);
422
423         return inode_table;
424 }
425
426 /*
427  * get file size.
428  *
429  * @inode_table : offset of inode table to file.
430  * @dirent : pointer of ext2_dirent structure.
431  */
432 int get_filesize_ext2(unsigned int inode_table)
433 {
434         struct ext2_inode inode;
435
436         get_inode(inode_table, &inode);
437
438         return inode.size;
439 }
440
441 /* read data block to file.
442  *
443  * @inode_table : offset of inode table to file.
444  * @in_buf : memory buffer for storing contents of data block.
445  * @size : file size.
446  */
447 int read_file_ext2(unsigned int inode_table, char *in_buf, unsigned int size)
448 {
449         struct ext2_datablock d_block;
450         struct ext2_inode inode;
451         unsigned int d_block_addr, id_block_addr, read_size = 0, id_block_num;
452         char *ptable = NULL, *pdata = NULL;
453         int i;
454
455         ptable = (char *) table_buf;
456         pdata = (char *) data_buf;
457
458         get_inode(inode_table, &inode);
459
460         /* get data block information for file. */
461         memcpy(&d_block, (char *) &inode.b.blocks, sizeof(struct ext2_datablock));
462
463         /* get real data from direct blocks. */
464         for (i = 0; i < INDIRECT_BLOCKS; i++) {
465                 if (d_block.dir_blocks[i] > 0) {
466
467                         dprint("direct block[%d] num = %d\n", i, d_block.dir_blocks[i]);
468
469                         /* calculate real data address. */
470                         d_block_addr = (unsigned int) d_block.dir_blocks[i] *
471                                 block_size_of_fs;
472                         if (size > block_size_of_fs) {
473                                 read_blk_from_onenand(d_block_addr, 1, pdata);
474                                 memcpy(in_buf, (char *) pdata, block_size_of_fs);
475                                 size -= block_size_of_fs;
476                                 in_buf += block_size_of_fs;
477                                 read_size += block_size_of_fs;
478
479                                 if (size <= 0)
480                                         return read_size;
481                         } else {
482                                 read_blk_from_onenand(d_block_addr, 1, pdata);
483                                 memcpy(in_buf, (char *) pdata, size);
484                                 read_size += size;
485
486                                 return read_size;
487                         }
488                 }
489         }
490
491         /* 
492          * get real data from 1-dim indirect blocks only in case that
493          * 2-dim indirect block has no block num. 
494          */
495         if (d_block.indir_block > 0 && d_block.double_indir_block <= 0) {
496                 d_block_addr = (unsigned int) d_block.indir_block *
497                         block_size_of_fs;
498                 dprint("1-dim indirect block address = 0x%x\n", d_block_addr);
499                 read_blk_from_onenand(d_block_addr, 1, ptable);
500
501                 /* 1-dim indirect block has 1k block and the size per entry is 4byte. */
502                 for (i = 0; i < block_size_of_fs; i+=4) {
503                         /* get indirect block number. */
504                         memcpy(&id_block_num, (char *) ptable + i, 4);
505
506                         dprint("1-dim indirect block[%d] num = %d\n", i, id_block_num);
507
508                         /* get real data from indirect block in case of hole. */
509                         if (id_block_num > 0) {
510                                 /* calculate block number having real data. */
511                                 id_block_addr = (unsigned int) id_block_num *
512                                         block_size_of_fs;
513
514                                 if (size > block_size_of_fs) {
515                                         read_blk_from_onenand(id_block_addr, 1, pdata);
516                                         memcpy(in_buf, (char *) pdata, block_size_of_fs);
517                                         size -= block_size_of_fs;
518                                         in_buf += block_size_of_fs;
519                                         read_size += block_size_of_fs;
520
521                                         if (size <= 0)
522                                                 return read_size;
523                                 } else {
524                                         read_blk_from_onenand(id_block_addr, 1, pdata);
525                                         memcpy(in_buf, pdata, size);
526                                         read_size += size;
527
528                                         return read_size;
529                                 }
530                         }
531                 }
532         }
533
534          /* read operation for 2-dim and 3-dim indirect blocks should be added. */
535
536         return read_size;
537 }
538
539 /* 
540  * list files in directory.
541  *
542  * @in_inode : the offset of current directory entry.
543  * @cmd : command indicating file attributes for listing.
544  */
545 void ls_ext2(unsigned int in_inode, const int cmd)
546 {
547         struct ext2_dirent dirent;
548         struct ext2_inode inode;
549         unsigned int next_point = 0;
550         int ret, inode_table = 0;
551         char *buf = NULL;
552
553         buf = malloc(ONENAND_READ_SIZE);
554         if (buf == NULL) {
555                 dprint("failed to allocate memory.\n");
556                 return;
557         }
558
559         read_blk_from_onenand(in_inode, 1, buf);
560
561         switch (cmd) {
562         case EXT2_LS_ONLY_FILE:
563         case EXT2_LS_ALL_ENTRY:
564         case EXT2_LS_ALL:
565                 do {
566                         ret = get_dir_entry(buf, &dirent);
567                         if (ret < 0) {
568                                 dprint("failed to get directory entry.\n");
569                                 return;
570                         }
571
572                         inode_table = get_inode_offset(&dirent);
573                         if (inode_table < 0) {
574                                 dprint("failed to get inode table offset.\n");
575                                 return;
576                         }
577
578                         ret = get_inode(inode_table, &inode);
579                         if (ret < 0) {
580                                 dprint("failed to get inode entry.\n");
581                                 return;
582                         }
583
584                         if (cmd == EXT2_LS_ONLY_FILE) {
585                                 if (dirent.filetype == EXT2_FT_REG_FILE ||
586                                         dirent.filetype == EXT2_FT_DIR) {
587                                         if (dirent.filetype == EXT2_FT_DIR)
588                                                 printf("[ %s ]\n", dirent.name);
589                                         else
590                                                 printf("%s\n", dirent.name);
591                                 }
592                         } else if (cmd == EXT2_LS_ALL_ENTRY) {
593                                 if (dirent.filetype == EXT2_FT_DIR)
594                                         printf("[ %s ]\n", dirent.name);
595                                 else
596                                         printf("%s\n", dirent.name);
597                         } else if (cmd == EXT2_LS_ALL) {
598                                 if (dirent.filetype == EXT2_FT_DIR)
599                                         printf("%x      %d      [ %s ]\n",
600                                                 inode.mode, inode.size, dirent.name);
601                                 else
602                                         printf("%x      %d      %s\n",
603                                                 inode.mode, inode.size, dirent.name);
604                         }
605
606                         next_point = next_dir_entry(&dirent);
607                         buf += next_point;
608                 } while (next_point != 0);
609
610                 return;
611         default:
612                 printf("it's wrong command.\n");
613                 return;
614         };
615
616         free(buf);
617 }
618
619 /*
620  * change current directory.
621  *
622  * @in_inode : the offset of current directory entry.
623  * @name : directory name.
624  */
625 void cd_ext2(unsigned int in_inode, const char *name)
626 {
627         unsigned int d_inode, out_inode, need_block = 0;
628         struct ext2_inode inode;
629         struct ext2_dirent dirent;
630         char *buf = NULL;
631
632         /* return inode of directory entry. */
633         out_inode = find_file_ext2(in_inode, name, &dirent);
634         if (out_inode < 0) {
635                 printf("failed to find file.\n");
636                 return;
637         }
638
639         /* check whether entry is director or not. */
640         if (dirent.filetype != EXT2_FT_DIR) {
641                 printf("%s is not directory.\n", name);
642                 return;
643         }
644
645         d_inode = (dirent.inode - 1) * INODE_TABLE_ENTRY_SIZE;
646
647         /* 
648          * get block size for loading from onenand.
649          * if d_inode is more then 4k then need_block should be more then 2.
650          */
651         need_block = (d_inode / block_size_of_fs) + 1;
652
653         buf = malloc(ONENAND_READ_SIZE * need_block);
654         if (buf == NULL) {
655                 printf("failed to allocate memory.\n");
656                 return;
657         }
658
659         read_blk_from_onenand(inode_table_location, need_block, buf);
660
661         /* get inode entry to file. */
662         memcpy(&inode, (char *) buf + d_inode, sizeof(struct ext2_inode));
663
664         current_inode = (inode.b.blocks.dir_blocks[0] * block_size_of_fs);
665
666         free(buf);
667 }
668 /*
669  * dump file.
670  *
671  * @in_inode : the offset of current directory entry.
672  * @name : file name.
673  */
674 void fd_ext2(unsigned int in_inode, const char *name)
675 {
676         unsigned int in_size, inode_table, out_size;
677         char *buf = NULL;
678
679         inode_table = open_file_ext2(in_inode, name);
680         in_size = get_filesize_ext2(inode_table);
681         if (in_size < 0) {
682                 printf("failed to get file size.\n");
683                 return;
684         }
685
686         dprint("name = %s, in size = %d\n", name, in_size);
687
688         buf = malloc(in_size);
689
690         out_size = read_file_ext2(inode_table, buf, in_size);
691         dprint("out size = %d, data address = 0x%8x\n", out_size, (unsigned int) buf);
692 }
693
694 void init_onenand_ext2(void)
695 {
696         root_inode = (unsigned int) mount_ext2fs();
697         if (root_inode < 0) {
698                 printf("failed to mount.\n");
699                 return;
700         }
701
702         dprint("ext2fs has been mounted.\n");
703
704         current_inode = root_inode;
705 }
706
707 int do_ls_ext2(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
708 {
709         int cmd = -1;
710
711         if (argc == 1 && argv[1] == NULL) {
712                 cmd = EXT2_LS_ONLY_FILE;
713         } else if (argc == 2 && (strcmp(argv[1], "-a") == 0)) {
714                 cmd = EXT2_LS_ALL_ENTRY;
715         } else if (argc == 2 && (strcmp(argv[1], "-al") == 0)) {
716                 cmd = EXT2_LS_ALL;
717         }
718
719         ls_ext2(current_inode, cmd);
720
721         return 0;
722 }
723
724 int do_cd_ext2(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
725 {
726         if (argc == 2 && argv[1] != NULL)
727             cd_ext2(current_inode, argv[1]);
728
729         return 0;
730 }
731
732 int do_fd_ext2(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
733 {
734         if (argc == 2 && argv[1] != NULL)
735             fd_ext2(current_inode, argv[1]);
736
737         return 0;
738 }
739
740 U_BOOT_CMD(ls_ext2, 3, 1, do_ls_ext2,
741         "list files from ext2 filesystem.\n",
742         "ls_ext2 [-al] [direct_name]\n"
743 );
744
745 U_BOOT_CMD(cd_ext2, 3, 1, do_cd_ext2,
746         "change directory.\n",
747         "cd_ext2 [direct_name]\n"
748 );
749
750 U_BOOT_CMD(fd_ext2, 3, 1, do_fd_ext2,
751         "dump file.\n",
752         "fd_ext2 [file_name]\n"
753 );