Merge remote-tracking branch 'u-boot-imx/master'
[platform/kernel/u-boot.git] / fs / cbfs / cbfs.c
1 /*
2  * Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3  *
4  * See file CREDITS for list of people who contributed to this
5  * project.
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 <cbfs.h>
24 #include <malloc.h>
25 #include <asm/byteorder.h>
26
27 enum cbfs_result file_cbfs_result;
28
29 const char *file_cbfs_error(void)
30 {
31         switch (file_cbfs_result) {
32         case CBFS_SUCCESS:
33                 return "Success";
34         case CBFS_NOT_INITIALIZED:
35                 return "CBFS not initialized";
36         case CBFS_BAD_HEADER:
37                 return "Bad CBFS header";
38         case CBFS_BAD_FILE:
39                 return "Bad CBFS file";
40         case CBFS_FILE_NOT_FOUND:
41                 return "File not found";
42         default:
43                 return "Unknown";
44         }
45 }
46
47
48 static const u32 good_magic = 0x4f524243;
49 static const u8 good_file_magic[] = "LARCHIVE";
50
51
52 static int initialized;
53 static struct cbfs_header cbfs_header;
54 static struct cbfs_cachenode *file_cache;
55
56 /* Do endian conversion on the CBFS header structure. */
57 static void swap_header(struct cbfs_header *dest, struct cbfs_header *src)
58 {
59         dest->magic = be32_to_cpu(src->magic);
60         dest->version = be32_to_cpu(src->version);
61         dest->rom_size = be32_to_cpu(src->rom_size);
62         dest->boot_block_size = be32_to_cpu(src->boot_block_size);
63         dest->align = be32_to_cpu(src->align);
64         dest->offset = be32_to_cpu(src->offset);
65 }
66
67 /* Do endian conversion on a CBFS file header. */
68 static void swap_file_header(struct cbfs_fileheader *dest,
69                              const struct cbfs_fileheader *src)
70 {
71         memcpy(&dest->magic, &src->magic, sizeof(dest->magic));
72         dest->len = be32_to_cpu(src->len);
73         dest->type = be32_to_cpu(src->type);
74         dest->checksum = be32_to_cpu(src->checksum);
75         dest->offset = be32_to_cpu(src->offset);
76 }
77
78 /*
79  * Given a starting position in memory, scan forward, bounded by a size, and
80  * find the next valid CBFS file. No memory is allocated by this function. The
81  * caller is responsible for allocating space for the new file structure.
82  *
83  * @param start         The location in memory to start from.
84  * @param size          The size of the memory region to search.
85  * @param align         The alignment boundaries to check on.
86  * @param newNode       A pointer to the file structure to load.
87  * @param used          A pointer to the count of of bytes scanned through,
88  *                      including the file if one is found.
89  *
90  * @return 1 if a file is found, 0 if one isn't.
91  */
92 static int file_cbfs_next_file(u8 *start, u32 size, u32 align,
93                                struct cbfs_cachenode *newNode, u32 *used)
94 {
95         struct cbfs_fileheader header;
96
97         *used = 0;
98
99         while (size >= align) {
100                 const struct cbfs_fileheader *fileHeader =
101                         (const struct cbfs_fileheader *)start;
102                 u32 name_len;
103                 u32 step;
104
105                 /* Check if there's a file here. */
106                 if (memcmp(good_file_magic, &(fileHeader->magic),
107                                 sizeof(fileHeader->magic))) {
108                         *used += align;
109                         size -= align;
110                         start += align;
111                         continue;
112                 }
113
114                 swap_file_header(&header, fileHeader);
115                 if (header.offset < sizeof(const struct cbfs_cachenode *) ||
116                                 header.offset > header.len) {
117                         file_cbfs_result = CBFS_BAD_FILE;
118                         return -1;
119                 }
120                 newNode->next = NULL;
121                 newNode->type = header.type;
122                 newNode->data = start + header.offset;
123                 newNode->data_length = header.len;
124                 name_len = header.offset - sizeof(struct cbfs_cachenode *);
125                 newNode->name = (char *)fileHeader +
126                                 sizeof(struct cbfs_cachenode *);
127                 newNode->name_length = name_len;
128                 newNode->checksum = header.checksum;
129
130                 step = header.len;
131                 if (step % align)
132                         step = step + align - step % align;
133
134                 *used += step;
135                 return 1;
136         }
137         return 0;
138 }
139
140 /* Look through a CBFS instance and copy file metadata into regular memory. */
141 static void file_cbfs_fill_cache(u8 *start, u32 size, u32 align)
142 {
143         struct cbfs_cachenode *cache_node;
144         struct cbfs_cachenode *newNode;
145         struct cbfs_cachenode **cache_tail = &file_cache;
146
147         /* Clear out old information. */
148         cache_node = file_cache;
149         while (cache_node) {
150                 struct cbfs_cachenode *oldNode = cache_node;
151                 cache_node = cache_node->next;
152                 free(oldNode);
153         }
154         file_cache = NULL;
155
156         while (size >= align) {
157                 int result;
158                 u32 used;
159
160                 newNode = (struct cbfs_cachenode *)
161                                 malloc(sizeof(struct cbfs_cachenode));
162                 result = file_cbfs_next_file(start, size, align,
163                         newNode, &used);
164
165                 if (result < 0) {
166                         free(newNode);
167                         return;
168                 } else if (result == 0) {
169                         free(newNode);
170                         break;
171                 }
172                 *cache_tail = newNode;
173                 cache_tail = &newNode->next;
174
175                 size -= used;
176                 start += used;
177         }
178         file_cbfs_result = CBFS_SUCCESS;
179 }
180
181 /* Get the CBFS header out of the ROM and do endian conversion. */
182 static int file_cbfs_load_header(uintptr_t end_of_rom,
183                                  struct cbfs_header *header)
184 {
185         struct cbfs_header *header_in_rom;
186
187         header_in_rom = (struct cbfs_header *)(uintptr_t)
188                         *(u32 *)(end_of_rom - 3);
189         swap_header(header, header_in_rom);
190
191         if (header->magic != good_magic || header->offset >
192                         header->rom_size - header->boot_block_size) {
193                 file_cbfs_result = CBFS_BAD_HEADER;
194                 return 1;
195         }
196         return 0;
197 }
198
199 void file_cbfs_init(uintptr_t end_of_rom)
200 {
201         u8 *start_of_rom;
202         initialized = 0;
203
204         if (file_cbfs_load_header(end_of_rom, &cbfs_header))
205                 return;
206
207         start_of_rom = (u8 *)(end_of_rom + 1 - cbfs_header.rom_size);
208
209         file_cbfs_fill_cache(start_of_rom + cbfs_header.offset,
210                              cbfs_header.rom_size, cbfs_header.align);
211         if (file_cbfs_result == CBFS_SUCCESS)
212                 initialized = 1;
213 }
214
215 const struct cbfs_header *file_cbfs_get_header(void)
216 {
217         if (initialized) {
218                 file_cbfs_result = CBFS_SUCCESS;
219                 return &cbfs_header;
220         } else {
221                 file_cbfs_result = CBFS_NOT_INITIALIZED;
222                 return NULL;
223         }
224 }
225
226 const struct cbfs_cachenode *file_cbfs_get_first(void)
227 {
228         if (!initialized) {
229                 file_cbfs_result = CBFS_NOT_INITIALIZED;
230                 return NULL;
231         } else {
232                 file_cbfs_result = CBFS_SUCCESS;
233                 return file_cache;
234         }
235 }
236
237 void file_cbfs_get_next(const struct cbfs_cachenode **file)
238 {
239         if (!initialized) {
240                 file_cbfs_result = CBFS_NOT_INITIALIZED;
241                 file = NULL;
242                 return;
243         }
244
245         if (*file)
246                 *file = (*file)->next;
247         file_cbfs_result = CBFS_SUCCESS;
248 }
249
250 const struct cbfs_cachenode *file_cbfs_find(const char *name)
251 {
252         struct cbfs_cachenode *cache_node = file_cache;
253
254         if (!initialized) {
255                 file_cbfs_result = CBFS_NOT_INITIALIZED;
256                 return NULL;
257         }
258
259         while (cache_node) {
260                 if (!strcmp(name, cache_node->name))
261                         break;
262                 cache_node = cache_node->next;
263         }
264         if (!cache_node)
265                 file_cbfs_result = CBFS_FILE_NOT_FOUND;
266         else
267                 file_cbfs_result = CBFS_SUCCESS;
268
269         return cache_node;
270 }
271
272 const struct cbfs_cachenode *file_cbfs_find_uncached(uintptr_t end_of_rom,
273                                                      const char *name)
274 {
275         u8 *start;
276         u32 size;
277         u32 align;
278         static struct cbfs_cachenode node;
279
280         if (file_cbfs_load_header(end_of_rom, &cbfs_header))
281                 return NULL;
282
283         start = (u8 *)(end_of_rom + 1 - cbfs_header.rom_size);
284         size = cbfs_header.rom_size;
285         align = cbfs_header.align;
286
287         while (size >= align) {
288                 int result;
289                 u32 used;
290
291                 result = file_cbfs_next_file(start, size, align, &node, &used);
292
293                 if (result < 0)
294                         return NULL;
295                 else if (result == 0)
296                         break;
297
298                 if (!strcmp(name, node.name))
299                         return &node;
300
301                 size -= used;
302                 start += used;
303         }
304         file_cbfs_result = CBFS_FILE_NOT_FOUND;
305         return NULL;
306 }
307
308 const char *file_cbfs_name(const struct cbfs_cachenode *file)
309 {
310         file_cbfs_result = CBFS_SUCCESS;
311         return file->name;
312 }
313
314 u32 file_cbfs_size(const struct cbfs_cachenode *file)
315 {
316         file_cbfs_result = CBFS_SUCCESS;
317         return file->data_length;
318 }
319
320 u32 file_cbfs_type(const struct cbfs_cachenode *file)
321 {
322         file_cbfs_result = CBFS_SUCCESS;
323         return file->type;
324 }
325
326 long file_cbfs_read(const struct cbfs_cachenode *file, void *buffer,
327                     unsigned long maxsize)
328 {
329         u32 size;
330
331         size = file->data_length;
332         if (maxsize && size > maxsize)
333                 size = maxsize;
334
335         memcpy(buffer, file->data, size);
336
337         file_cbfs_result = CBFS_SUCCESS;
338         return size;
339 }