aboutsummaryrefslogtreecommitdiff
path: root/common/initrd.c
blob: 835ccf111853cdbab9a233f9c2d650424c1be58a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */

/**
 * @file initrd.c
 * Handle initrd, implement cpio with newc format.
 */

#include <apos/initrd.h>
#include <apos/vmem.h>
#include <apos/string.h>
#include <apos/utils.h>
#include <apos/attrs.h>
#include <libfdt.h>

/** GNU cpio, POSIX 'newc' format header. */
struct __packed cpio_header {
	/** Magic bytes. */
	char c_magic[6];

	/** File inode. */
	char c_ino[8];

	/** File type and permissions. */
	char c_mode[8];

	/** User ID. */
	char c_uid[8];

	/** Group ID. */
	char c_gid[8];

	/** Number of links to this file. */
	char c_nlink[8];

	/** Modification time. */
	char c_mtime[8];

	/** Size of file. */
	char c_filesize[8];

	/** Device major. */
	char c_devmajor[8];

	/** Device minor. */
	char c_devminor[8];

	/** Block device major. */
	char c_rdevmajor[8];

	/** Block device minor. */
	char c_rdevminor[8];

	/** Length of filename. */
	char c_namesize[8];

	/** CRC check. */
	char c_check[8];
};

/**
 * Get next file in archive.
 * Does not check for out of bounds.
 *
 * @param cp Pointer to current file header.
 * @return Pointer to next file header.
 */
static struct cpio_header *__next_entry(struct cpio_header *cp)
{
	size_t blen = align_up(
		sizeof(struct cpio_header) + convnum(cp->c_namesize, 8, 16), 4);
	size_t tlen = align_up(convnum(cp->c_filesize, 8, 16), 4);

	return (struct cpio_header *)(((char *)cp) + blen + tlen);
}

/**
 * Get file with name in archive.
 *
 * @param c Pointer to initrd.
 * @param fname Filename to look for.
 * @param fname_len Length of filename.
 * @return Pointer to corresponding file header if found, \c NULL otherwise.
 */
static struct cpio_header *__find_file(const char *c, const char *fname,
                                       size_t fname_len)
{
	struct cpio_header *cp = (struct cpio_header *)c;
	for (; cp; cp = __next_entry(cp)) {
		size_t namelen = convnum(cp->c_namesize, 8, 16);
		if (namelen == 0)
			return NULL;

		if (namelen < fname_len)
			continue;

		char *name = (char *)(cp + 1);
		if (fname[0] != '/')
			name += namelen - (fname_len + 1); /* match ending */

		if (strncmp(name, fname, fname_len) == 0)
			return cp;
	}

	return NULL;
}

/** Name of \c init program. */
static char init_n[] = "init";

/** Length of \c init name. */
static size_t init_nlen = ARRAY_SIZE(init_n) - 1; /* ignore trailing NULL */

pm_t get_initrdtop(const void *fdt)
{
	int chosen_offset = fdt_path_offset(fdt, "/chosen");
	struct cell_info ci = get_cellinfo(fdt, chosen_offset);

	void *initrd_end_ptr = (void *)fdt_getprop(fdt, chosen_offset,
	                                           "linux,initrd-end", NULL);

	/* fdt is only aware of physical memory pointers */
	return (pm_t)__va(fdt_load_int_ptr(ci.addr_cells, initrd_end_ptr));
}

pm_t get_initrdbase(const void *fdt)
{
	const int chosen_offset = fdt_path_offset(fdt, "/chosen");
	const struct cell_info ci = get_cellinfo(fdt, chosen_offset);

	void *initrd_base_ptr = (void *)fdt_getprop(fdt, chosen_offset,
	                                            "linux,initrd-start", NULL);

	return (pm_t)__va(fdt_load_int_ptr(ci.addr_cells, initrd_base_ptr));
}


size_t get_init_size(const void *fdt)
{
	char *c = (char *)get_initrdbase(fdt);
	struct cpio_header *cp = __find_file(c, init_n, init_nlen);
	return convnum(cp->c_filesize, 8, 16);
}

vm_t get_init_base(const void *fdt)
{
	char *c = (char *)get_initrdbase(fdt);
	struct cpio_header *cp = __find_file(c, init_n, init_nlen);
	size_t name_len = convnum(cp->c_namesize, 8, 16);
	return ((vm_t)cp) + align_up(sizeof(struct cpio_header) + name_len, 4);
}

stat_t move_init(const void *fdt, void *target)
{
	const char *c = (const char *)get_initrdbase(fdt);

	const struct cpio_header *cp = __find_file(c, init_n, init_nlen);
	size_t name_len = convnum(cp->c_namesize, 8, 16);
	size_t file_len = convnum(cp->c_filesize, 8, 16);

	char *fp = (char *)cp;
	fp += align_up(sizeof(struct cpio_header) + name_len, 4);

	memmove(target, fp, file_len);
	return OK;
}