Merge https://source.denx.de/u-boot/custodians/u-boot-riscv
[platform/kernel/u-boot.git] / boot / bootmeth_extlinux.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Bootmethod for extlinux boot from a block device
4  *
5  * Copyright 2021 Google LLC
6  * Written by Simon Glass <sjg@chromium.org>
7  */
8
9 #define LOG_CATEGORY UCLASS_BOOTSTD
10
11 #include <common.h>
12 #include <bootdev.h>
13 #include <bootflow.h>
14 #include <bootmeth.h>
15 #include <bootstd.h>
16 #include <command.h>
17 #include <dm.h>
18 #include <extlinux.h>
19 #include <fs.h>
20 #include <malloc.h>
21 #include <mapmem.h>
22 #include <mmc.h>
23 #include <pxe_utils.h>
24
25 static int extlinux_get_state_desc(struct udevice *dev, char *buf, int maxsize)
26 {
27         if (IS_ENABLED(CONFIG_SANDBOX)) {
28                 int len;
29
30                 len = snprintf(buf, maxsize, "OK");
31
32                 return len + 1 < maxsize ? 0 : -ENOSPC;
33         }
34
35         return 0;
36 }
37
38 static int extlinux_getfile(struct pxe_context *ctx, const char *file_path,
39                             char *file_addr, ulong *sizep)
40 {
41         struct extlinux_info *info = ctx->userdata;
42         ulong addr;
43         int ret;
44
45         addr = simple_strtoul(file_addr, NULL, 16);
46
47         /* Allow up to 1GB */
48         *sizep = 1 << 30;
49         ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr,
50                                  sizep);
51         if (ret)
52                 return log_msg_ret("read", ret);
53
54         return 0;
55 }
56
57 static int extlinux_check(struct udevice *dev, struct bootflow_iter *iter)
58 {
59         int ret;
60
61         /* This only works on block devices */
62         ret = bootflow_iter_check_blk(iter);
63         if (ret)
64                 return log_msg_ret("blk", ret);
65
66         return 0;
67 }
68
69 /**
70  * extlinux_fill_info() - Decode the extlinux file to find out its info
71  *
72  * @bflow: Bootflow to process
73  * @return 0 if OK, -ve on error
74  */
75 static int extlinux_fill_info(struct bootflow *bflow)
76 {
77         struct membuff mb;
78         char line[200];
79         char *data;
80         int len;
81
82         log_debug("parsing bflow file size %x\n", bflow->size);
83         membuff_init(&mb, bflow->buf, bflow->size);
84         membuff_putraw(&mb, bflow->size, true, &data);
85         while (len = membuff_readline(&mb, line, sizeof(line) - 1, ' '), len) {
86                 char *tok, *p = line;
87
88                 tok = strsep(&p, " ");
89                 if (p) {
90                         if (!strcmp("label", tok)) {
91                                 bflow->os_name = strdup(p);
92                                 if (!bflow->os_name)
93                                         return log_msg_ret("os", -ENOMEM);
94                         }
95                 }
96         }
97
98         return 0;
99 }
100
101 static int extlinux_read_bootflow(struct udevice *dev, struct bootflow *bflow)
102 {
103         struct blk_desc *desc;
104         const char *const *prefixes;
105         struct udevice *bootstd;
106         const char *prefix;
107         loff_t size;
108         int ret, i;
109
110         ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
111         if (ret)
112                 return log_msg_ret("std", ret);
113
114         /* If a block device, we require a partition table */
115         if (bflow->blk && !bflow->part)
116                 return -ENOENT;
117
118         prefixes = bootstd_get_prefixes(bootstd);
119         i = 0;
120         desc = bflow->blk ? dev_get_uclass_plat(bflow->blk) : NULL;
121         do {
122                 prefix = prefixes ? prefixes[i] : NULL;
123
124                 ret = bootmeth_try_file(bflow, desc, prefix, EXTLINUX_FNAME);
125         } while (ret && prefixes && prefixes[++i]);
126         if (ret)
127                 return log_msg_ret("try", ret);
128         size = bflow->size;
129
130         ret = bootmeth_alloc_file(bflow, 0x10000, 1);
131         if (ret)
132                 return log_msg_ret("read", ret);
133
134         ret = extlinux_fill_info(bflow);
135         if (ret)
136                 return log_msg_ret("inf", ret);
137
138         return 0;
139 }
140
141 static int extlinux_boot(struct udevice *dev, struct bootflow *bflow)
142 {
143         struct cmd_tbl cmdtp = {};      /* dummy */
144         struct pxe_context ctx;
145         struct extlinux_info info;
146         ulong addr;
147         int ret;
148
149         addr = map_to_sysmem(bflow->buf);
150         info.dev = dev;
151         info.bflow = bflow;
152         ret = pxe_setup_ctx(&ctx, &cmdtp, extlinux_getfile, &info, true,
153                             bflow->fname, false);
154         if (ret)
155                 return log_msg_ret("ctx", -EINVAL);
156
157         ret = pxe_process(&ctx, addr, false);
158         if (ret)
159                 return log_msg_ret("bread", -EINVAL);
160
161         return 0;
162 }
163
164 static int extlinux_bootmeth_bind(struct udevice *dev)
165 {
166         struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
167
168         plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
169                 "Extlinux boot from a block device" : "extlinux";
170
171         return 0;
172 }
173
174 static struct bootmeth_ops extlinux_bootmeth_ops = {
175         .get_state_desc = extlinux_get_state_desc,
176         .check          = extlinux_check,
177         .read_bootflow  = extlinux_read_bootflow,
178         .read_file      = bootmeth_common_read_file,
179         .boot           = extlinux_boot,
180 };
181
182 static const struct udevice_id extlinux_bootmeth_ids[] = {
183         { .compatible = "u-boot,extlinux" },
184         { }
185 };
186
187 /* Put an number before 'extlinux' to provide a default ordering */
188 U_BOOT_DRIVER(bootmeth_1extlinux) = {
189         .name           = "bootmeth_extlinux",
190         .id             = UCLASS_BOOTMETH,
191         .of_match       = extlinux_bootmeth_ids,
192         .ops            = &extlinux_bootmeth_ops,
193         .bind           = extlinux_bootmeth_bind,
194 };