Move CBFS header to a safer place.
[coreboot.git] / util / cbfstool / fs.c
1 /*
2  * cbfstool
3  *
4  * Copyright (C) 2008 Jordan Crouse <jordan@cosmicpenguin.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
18  */
19
20 #include <string.h>
21 #include <stdlib.h>
22 #include "cbfstool.h"
23
24 int namelen(const char *name)
25 {
26         return ALIGN(strlen(name) + 1, 16);
27 }
28
29 /**
30  * Given a name, return the header size for that name. 
31  * @param name The name
32  * @returns The header size given that name
33  */
34 int headersize(const char *name)
35 {
36         return sizeof(struct cbfs_file) + namelen(name);
37 }
38
39 /**
40  * Given a name, set it into the header in a standard way
41  * @param file the cbfs file
42  * @param name The name
43  */
44 void setname(struct cbfs_file *file, const char *name)
45 {
46         memset(CBFS_NAME(file), 0, namelen(name));
47         strcpy((char *)CBFS_NAME(file), name);
48 }
49
50 /**
51  * Given a name, size, and type, set them into the header in a standard way. 
52  * Special case of size of -1: set the size to all of ROM
53  * @param rom The rom
54  * @param c The cbfs file
55  * @param name The name
56  * @param size The size
57  * @param type The type
58  * @returns Always 0 for now
59  */
60 int rom_set_header(struct rom *rom, struct cbfs_file *c, const char *name, int size, int type)
61 {
62         unsigned int csize;
63         csize = headersize(name);
64
65         strcpy(c->magic, COMPONENT_MAGIC);
66
67         /* special case -- if size is -1, means "as much as you can"
68          * it's usually only used in init. 
69          */
70         if (size < 0)
71                 size = rom->fssize - csize;
72         c->len = htonl(size);
73         c->offset = htonl(csize);
74         c->type = htonl(type);
75
76         setname(c, name);
77         return 0;
78 }
79
80 int nextfile(struct rom *rom, struct cbfs_file *c, int offset)
81 {
82         return ALIGN(offset + ntohl(c->len),
83                                         ntohl(rom->header->align));
84 }
85
86
87 /* split
88  * split is a basic primitive in cbfs. Over time, it should be the main operator
89  * used to allocate space. For now for testing we are only using it in the 
90  * fixed-address allocation. 
91  * Split takes a cbfs_file and splits it into two pieces, as determined 
92  * by the size of the file desired. Split only makes sense on CBFS_COMPONENT_NULL
93  * files -- splitting real files is an error, but no checking is done. 
94  * @param file cbfs_file to split
95  * @param size Size of the file desired. 
96  * @returns pointer to a cbfs_file stuct. 
97  */
98 static struct cbfs_file *split(struct rom *rom, struct cbfs_file *file, int size)
99 {
100         struct cbfs_file *newfile = NULL;
101         unsigned long align = ntohl(rom->header->align);
102         unsigned long nextoffset, truncoffset;
103         unsigned long offset = ROM_OFFSET(rom, file);
104         /* figure out the real end of this file, and hence the size */
105         /* compute where the next file is */
106         nextoffset = ALIGN(offset + ntohl(file->len) + headersize(""), align);
107         /* compute where the end of this new file might be */
108         truncoffset = ALIGN(offset + size + headersize(""), align);
109         /* If there is more than align bytes difference, create a new empty file */
110         /* later, we can add code to merge all empty files. */
111         if (nextoffset - truncoffset > align) {
112                 unsigned int csize;
113                 csize = headersize("");
114                 newfile = (struct cbfs_file *)ROM_PTR(rom, truncoffset);
115                 rom_set_header(rom, newfile, "", 
116                         nextoffset - truncoffset - csize, CBFS_COMPONENT_NULL);
117                 file->len = htonl(size);
118         }
119         return newfile;
120 }
121
122
123 /**
124  * rom_alloc_fixed
125  * Given a rom, walk the headers and find the first header of type 
126  * CBFS_COMPONENT_NULL that is >= the desired size and 
127  * contains the (address, length) desired. 
128  * If the CBFS_COMPONENT_NULL is 'align' bytes > size, 
129  * create a new header of CBFS_COMPONENT_NULL following the file. 
130  * The 'len' structure member of the desired file is initialized, but 
131  * nothing else is. 
132  * Simple algorithm: walk until we find an empty file that contains our area, 
133  * and then allocate out of it. 
134  * @param rom The rom
135  * @param size the size of the file needed
136  * @returns pointer to a cbfs_file struct. 
137  */
138 struct cbfs_file * rom_alloc_fixed(struct rom *rom, const char *name, unsigned long start, unsigned long size, int type)
139 {
140         /* walk the rom and find an empty file with a base > base, 
141          * and a large enough size
142          */
143         unsigned long base, end, alen, baseoff;
144         unsigned int offset = ntohl(rom->header->offset);
145         int ret = -1;
146         struct cbfs_file *c = NULL;
147         unsigned long align = ntohl(rom->header->align);
148
149         /* compute a base that is aligned to align */
150         base = TRUNCATE(start, align);
151         /* have to leave room for a header! */
152         base -= headersize(name);
153         /* get an offset for that base */
154         baseoff = base - rom->rombase;
155         end = ALIGN(start + size, align);
156         alen = end - base;
157         while (offset < rom->fssize) {
158
159                 c = (struct cbfs_file *)ROM_PTR(rom, offset);
160
161                 if (!strcmp(c->magic, COMPONENT_MAGIC)) {
162                         if (c->type != CBFS_COMPONENT_NULL) {
163                                 offset += ALIGN(ntohl(c->offset) + ntohl(c->len),
164                                         align);
165                                 continue;
166                         }
167                         /* could turn this into a function. */
168                         /* is the start of this file < our desired start? */
169                         if (offset > baseoff)
170                                 break;
171                         /* Is this file big enough for our needs? */
172                         if (ntohl(c->len) >= alen){
173                                 ret = offset;
174                                 break;
175                         }
176                         offset += ALIGN(ntohl(c->offset) + ntohl(c->len),
177                                         align);
178                 } else {
179                         fprintf(stderr, "Corrupt rom -- found no header at %d\n", offset);
180                         exit(1);
181                 }
182         }
183
184         if (ret < 0)
185                 return NULL;
186
187         /* we have the base offset of our location, and we have the offset for the file we are going to 
188          * split. Split it. 
189          */
190         if (baseoff > offset)
191                 c = split(rom, c, baseoff - offset - headersize(""));
192         /* split off anything left at the end that we don't need */
193         split(rom, c, size);
194
195         c->len = htonl(size);
196
197         strcpy(c->magic, COMPONENT_MAGIC);
198
199         c->offset = htonl(headersize(name));
200
201         c->type = htonl(type);
202
203         setname(c, name);
204
205         return ((struct cbfs_file *)ROM_PTR(rom, ret));
206 }
207
208
209 /**
210  * rom_alloc
211  * Given a rom, walk the headers and find the first header of type 
212  * CBFS_COMPONENT_NULL that is >= the desired size. 
213  * If the CBFS_COMPONENT_NULL is 'align' bytes > size, 
214  * create a new header of CBFS_COMPONENT_NULL following the file. 
215  * The 'len' structure member of the desired file is initialized, but 
216  * nothing else is. 
217  * @param rom The rom
218  * @param size the size of the file needed
219  * @returns pointer to a cbfs_file struct. 
220  */
221 struct cbfs_file * rom_alloc(struct rom *rom, const char *name, unsigned long size, int type)
222 {
223         /* walk the rom and find an empty file with a base > base, and a large enough size */
224         unsigned int offset = ntohl(rom->header->offset);
225         int ret = -1;
226         struct cbfs_file *c = NULL;
227         unsigned long nextoffset, truncoffset;
228         struct cbfs_file *newfile = NULL;
229
230         while ((offset + size) < rom->fssize) {
231
232                 c = (struct cbfs_file *)ROM_PTR(rom, offset);
233
234                 if (!strcmp(c->magic, COMPONENT_MAGIC)) {
235                         if (c->type != CBFS_COMPONENT_NULL) {
236                                 offset += ALIGN(ntohl(c->offset) + ntohl(c->len),
237                                         ntohl(rom->header->align));
238                                 continue;
239                         }
240                         /* Is this file big enough for our needs? */
241                         if (ntohl(c->len) >= size){
242                                 ret = offset;
243                                 break;
244                         }
245                         offset += ALIGN(ntohl(c->offset) + ntohl(c->len),
246                                         ntohl(rom->header->align));
247                 } else {
248                         fprintf(stderr, "Corrupt rom -- found no header at %d\n", offset);
249                         exit(1);
250                 }
251         }
252
253         if (ret < 0)
254                 return NULL;
255
256         /* figure out the real end of this file, and hence the size */
257         /* compute where the next file is */
258         nextoffset = ALIGN(ret + ntohl(c->len) + headersize(name),
259                                 ntohl(rom->header->align));
260         /* compute where the end of this new file might be */
261         truncoffset = ALIGN(ret + size + headersize(name),
262                                 ntohl(rom->header->align));
263         /* If there is more than align bytes difference, create a new empty file */
264         /* later, we can add code to merge all empty files. */
265         if (nextoffset - truncoffset > ntohl(rom->header->align)) {
266                 unsigned int csize;
267                 csize = headersize("");
268                 newfile = (struct cbfs_file *)ROM_PTR(rom, truncoffset);
269                 rom_set_header(rom, newfile, "", 
270                         nextoffset - truncoffset - csize, CBFS_COMPONENT_NULL);
271         } else truncoffset = nextoffset;
272
273         c->len = htonl(size);
274
275         strcpy(c->magic, COMPONENT_MAGIC);
276
277         c->offset = htonl(headersize(name));
278
279         c->type = htonl(type);
280
281         setname(c, name);
282
283         return ((struct cbfs_file *)ROM_PTR(rom, ret));
284 }
285
286 struct cbfs_file *rom_find(struct rom *rom, int offset)
287 {
288         while (offset < rom->fssize) {
289                 struct cbfs_file *c =
290                     (struct cbfs_file *)ROM_PTR(rom, offset);
291
292                 if (!strcmp(c->magic, COMPONENT_MAGIC))
293                         return c;
294
295                 offset += ntohl(rom->header->align);
296         }
297
298         return NULL;
299 }
300
301 struct cbfs_file *rom_find_first(struct rom *rom)
302 {
303         return rom_find(rom, ntohl(rom->header->offset));
304 }
305
306 struct cbfs_file *rom_find_next(struct rom *rom, struct cbfs_file *prev)
307 {
308         unsigned int offset = ROM_OFFSET(rom, prev);
309
310         return rom_find(rom, offset +
311                         ALIGN(ntohl(prev->offset) + ntohl(prev->len),
312                               ntohl(rom->header->align)));
313 }
314
315 struct cbfs_file *rom_find_empty(struct rom *rom)
316 {
317         unsigned int offset = ntohl(rom->header->offset);
318         unsigned int ret = ntohl(rom->header->offset);
319
320         while (offset < rom->fssize) {
321
322                 struct cbfs_file *c =
323                     (struct cbfs_file *)ROM_PTR(rom, offset);
324
325                 if (!strcmp(c->magic, COMPONENT_MAGIC)) {
326                         offset += ALIGN(ntohl(c->offset) + ntohl(c->len),
327                                         ntohl(rom->header->align));
328
329                         ret = offset;
330                 } else
331                         offset += ntohl(rom->header->align);
332         }
333
334         return (ret < rom->fssize) ?
335             (struct cbfs_file *)ROM_PTR(rom, ret) : NULL;
336 }
337
338 struct cbfs_file *rom_find_by_name(struct rom *rom, const char *name)
339 {
340         struct cbfs_file *c = rom_find_first(rom);
341
342         while (c) {
343                 if (!strcmp((char *)CBFS_NAME(c), name))
344                         return c;
345
346                 c = rom_find_next(rom, c);
347         }
348
349         return NULL;
350 }
351
352 int rom_used_space(struct rom *rom)
353 {
354         struct cbfs_file *c = rom_find_first(rom);
355         unsigned int ret = 0;
356
357         while (c) {
358                 int type;
359                 type = ntohl(c->type);
360                 if ((c->type == CBFS_COMPONENT_DELETED) ||
361                         (c->type == CBFS_COMPONENT_NULL))
362                         continue;
363                 ret += ROM_OFFSET(rom, c) + ntohl(c->offset) + ntohl(c->len);
364                 c = rom_find_next(rom, c);
365         }
366
367         return ret;
368 }
369
370 /** 
371  * delete an item. This is a flash-friendly version -- it just blows the 
372  * type to 0. Nothing else is changed. 
373  * N.B. We no longer shuffle contents of ROM. That will come later. 
374  * @param rom The rom
375  * @param name Name of file to remove. 
376  * @return -1 on error, 0 if a file was set to deleted. 
377  */
378 int rom_remove(struct rom *rom, const char *name)
379 {
380         struct cbfs_file *c = rom_find_by_name(rom, name);
381
382         if (c == NULL) {
383                 ERROR("Component %s does not exist\n", name);
384                 return -1;
385         }
386
387         c->type = CBFS_COMPONENT_DELETED; 
388
389         void *n = rom_find_next(rom, c);
390         int clear;
391         
392         if (n != NULL) {
393                 memcpy(c, n, rom->fssize - ROM_OFFSET(rom, n));
394                 clear = ROM_OFFSET(rom, n) - ROM_OFFSET(rom, c);
395         }
396         else { /* No component after this one. */
397                 unsigned int csize;
398                 csize = sizeof(struct cbfs_file) + ALIGN(strlen(name) + 1, 16);
399                 clear = ntohl(c->len) + csize;
400                 memcpy(c, ((void*)c) + clear, 
401                        rom->fssize - (ROM_OFFSET(rom, c)+clear));
402         }
403
404         /* Zero the new space, which is always at the end. */
405         memset(ROM_PTR(rom, rom->fssize - clear), 0, clear);
406
407         return 0;
408 }
409
410 int rom_extract(struct rom *rom, const char *name, void** buf, int *size )
411 {
412         struct cbfs_file *c = rom_find_by_name(rom, name);
413
414         if (c == NULL) {
415                 ERROR("Component %s does not exist\n", name);
416                 return -1;
417         }
418
419         *size = ntohl(c->len);
420         *buf = ((unsigned char *)c) + headersize(name);
421         return 0;
422 }
423
424 /**
425  * Add a new file named 'name', of type 'type', size 'size'. Initialize that file
426  * with the contents of 'buffer'. 
427  * @param rom The rom
428  * @param name file name
429  * @param buffer file data
430  * @param address base address. 0 means 'whereever it fits'
431  * @param size Amount of data
432  * @param type File type
433  * @returns -1 on failure, 0 on success
434  */
435 int rom_add(struct rom *rom, const char *name, void *buffer, unsigned long address, int size, int type)
436 {
437         struct cbfs_file *c;
438         int csize;
439
440         if (rom_find_by_name(rom, name)) {
441                 ERROR("Component %s already exists in this rom\n", name);
442                 return -1;
443         }
444
445         if (address)
446                 c = rom_alloc_fixed(rom, name, address, size, type);
447         else
448                 c = rom_alloc(rom, name, size, type);
449
450         if (c == NULL) {
451                 ERROR("There is not enough room in this ROM\n");
452                 return -1;
453         }
454
455         csize = sizeof(struct cbfs_file) + ALIGN(strlen(name) + 1, 16);
456
457         int offset = ROM_OFFSET(rom, c);
458
459         if (offset + csize + size > rom->fssize) {
460                 ERROR("There is not enough room in this ROM for this\n");
461                 ERROR("component. I need %d bytes, only have %d bytes avail\n",
462                       csize + size, rom->fssize - offset);
463
464                 return -1;
465         }
466
467         strcpy(c->magic, COMPONENT_MAGIC);
468
469         c->len = htonl(size);
470         c->offset = htonl(csize);
471         c->type = htonl(type);
472
473         memset(CBFS_NAME(c), 0, ALIGN(strlen(name) + 1, 16));
474         strcpy((char *)CBFS_NAME(c), name);
475
476         memcpy(((unsigned char *)c) + csize, buffer, size);
477         return 0;
478 }
479