From 18436c74dce29a74e996d5932ba01e0cd3481326 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:30:55 -0600 Subject: test: Add tests for trailing_strtol() This function currently has no tests. Add some. Signed-off-by: Simon Glass --- include/vsprintf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/vsprintf.h b/include/vsprintf.h index 532ef3650bd..3d1f968df44 100644 --- a/include/vsprintf.h +++ b/include/vsprintf.h @@ -99,7 +99,7 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base); * For example, "abc123" would return 123. * * @str: String to exxamine - * Return: training number if found, else -1 + * Return: trailing number if found, else -1 */ long trailing_strtol(const char *str); @@ -114,7 +114,7 @@ long trailing_strtol(const char *str); * @str: String to exxamine * @end: Pointer to end of string to examine, or NULL to use the * whole string - * Return: training number if found, else -1 + * Return: trailing number if found, else -1 */ long trailing_strtoln(const char *str, const char *end); -- cgit v1.3.1 From 4e64cae0a060c579078de299c6a118aa6f6d7b5b Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:30:56 -0600 Subject: lib: Correct comment formatting to avoid sphinx problems Tweak a few comments to kep sphinx happy, in case we want to include this file one day. Also fix the 'exxamine' typo. Patch-notes: This uses: sed -i 's/@param \(\S*\)\s*/@\1: /' include/vsprintf.h to convert the @param to the new format. Signed-off-by: Simon Glass --- include/vsprintf.h | 92 +++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) (limited to 'include') diff --git a/include/vsprintf.h b/include/vsprintf.h index 3d1f968df44..d4bf3211da4 100644 --- a/include/vsprintf.h +++ b/include/vsprintf.h @@ -13,9 +13,9 @@ /** * simple_strtoul - convert a string to an unsigned long * - * @param cp The string to be converted - * @param endp Updated to point to the first character not converted - * @param base The number base to use (0 for the default) + * @cp: The string to be converted + * @endp: Updated to point to the first character not converted + * @base: The number base to use (0 for the default) * Return: value decoded from string (0 if invalid) * * Converts a string to an unsigned long. If there are invalid characters at @@ -34,8 +34,8 @@ ulong simple_strtoul(const char *cp, char **endp, unsigned int base); /** * hex_strtoul - convert a string in hex to an unsigned long * - * @param cp The string to be converted - * @param endp Updated to point to the first character not converted + * @cp: The string to be converted + * @endp: Updated to point to the first character not converted * Return: value decoded from string (0 if invalid) * * Converts a hex string to an unsigned long. If there are invalid characters at @@ -47,8 +47,8 @@ unsigned long hextoul(const char *cp, char **endp); /** * dec_strtoul - convert a string in decimal to an unsigned long * - * @param cp The string to be converted - * @param endp Updated to point to the first character not converted + * @cp: The string to be converted + * @endp: Updated to point to the first character not converted * Return: value decoded from string (0 if invalid) * * Converts a decimal string to an unsigned long. If there are invalid @@ -59,11 +59,11 @@ unsigned long dectoul(const char *cp, char **endp); /** * strict_strtoul - convert a string to an unsigned long strictly - * @param cp The string to be converted - * @param base The number base to use (0 for the default) - * @param res The converted result value - * Return: 0 if conversion is successful and *res is set to the converted - * value, otherwise it returns -EINVAL and *res is set to 0. + * @cp: The string to be converted + * @base: The number base to use (0 for the default) + * @res: The converted result value + * Return: 0 if conversion is successful and `*res` is set to the converted + * value, otherwise it returns -EINVAL and `*res` is set to 0. * * strict_strtoul converts a string to an unsigned long only if the * string is really an unsigned long string, any string containing @@ -98,7 +98,7 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base); * Given a string this finds a trailing number on the string and returns it. * For example, "abc123" would return 123. * - * @str: String to exxamine + * @str: String to examine * Return: trailing number if found, else -1 */ long trailing_strtol(const char *str); @@ -111,7 +111,7 @@ long trailing_strtol(const char *str); * characters between @str and @end - 1 are examined. If @end is NULL, it is * set to str + strlen(str). * - * @str: String to exxamine + * @str: String to examine * @end: Pointer to end of string to examine, or NULL to use the * whole string * Return: trailing number if found, else -1 @@ -124,7 +124,7 @@ long trailing_strtoln(const char *str, const char *end); * Prints a message on the console(s) and then resets. If CONFIG_PANIC_HANG is * defined, then it will hang instead of resetting. * - * @param fmt: printf() format string for message, which should not include + * @fmt: printf() format string for message, which should not include * \n, followed by arguments */ void panic(const char *fmt, ...) @@ -139,16 +139,16 @@ void panic(const char *fmt, ...) * This function can be used instead of panic() when your board does not * already use printf(), * to keep code size small. * - * @param fmt: string to display, which should not include \n + * @str: string to display, which should not include \n */ void panic_str(const char *str) __attribute__ ((noreturn)); /** * Format a string and place it in a buffer * - * @param buf The buffer to place the result into - * @param fmt The format string to use - * @param ... Arguments for the format string + * @buf: The buffer to place the result into + * @fmt: The format string to use + * @...: Arguments for the format string * * The function returns the number of characters written * into @buf. @@ -161,9 +161,9 @@ int sprintf(char *buf, const char *fmt, ...) /** * Format a string and place it in a buffer (va_list version) * - * @param buf The buffer to place the result into - * @param fmt The format string to use - * @param args Arguments for the format string + * @buf: The buffer to place the result into + * @fmt: The format string to use + * @args: Arguments for the format string * Return: the number of characters which have been written into * the @buf not including the trailing '\0'. * @@ -178,7 +178,7 @@ int vsprintf(char *buf, const char *fmt, va_list args); * * This returns a static string containing the decimal representation of the * given value. The returned value may be overwritten by other calls to other - * simple_... functions, so should be used immediately + * simple... functions, so should be used immediately * * @val: Value to convert * Return: string containing the decimal representation of @val @@ -190,9 +190,9 @@ char *simple_itoa(ulong val); * * This returns a static string containing the hexadecimal representation of the * given value. The returned value may be overwritten by other calls to other - * simple_... functions, so should be used immediately + * simple... functions, so should be used immediately * - * @val: Value to convert + * @num: Value to convert * Return: string containing the hexecimal representation of @val */ char *simple_xtoa(ulong num); @@ -200,10 +200,10 @@ char *simple_xtoa(ulong num); /** * Format a string and place it in a buffer * - * @param buf The buffer to place the result into - * @param size The size of the buffer, including the trailing null space - * @param fmt The format string to use - * @param ... Arguments for the format string + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @...: Arguments for the format string * Return: the number of characters which would be * generated for the given input, excluding the trailing null, * as per ISO C99. If the return is greater than or equal to @@ -217,10 +217,10 @@ int snprintf(char *buf, size_t size, const char *fmt, ...) /** * Format a string and place it in a buffer * - * @param buf The buffer to place the result into - * @param size The size of the buffer, including the trailing null space - * @param fmt The format string to use - * @param ... Arguments for the format string + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @...: Arguments for the format string * * The return value is the number of characters written into @buf not including * the trailing '\0'. If @size is == 0 the function returns 0. @@ -233,10 +233,10 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...) /** * Format a string and place it in a buffer (base function) * - * @param buf The buffer to place the result into - * @param size The size of the buffer, including the trailing null space - * @param fmt The format string to use - * @param args Arguments for the format string + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @args: Arguments for the format string * Return: The number characters which would be generated for the given * input, excluding the trailing '\0', as per ISO C99. Note that fewer * characters may be written if this number of characters is >= size. @@ -258,10 +258,10 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args); /** * Format a string and place it in a buffer (va_list version) * - * @param buf The buffer to place the result into - * @param size The size of the buffer, including the trailing null space - * @param fmt The format string to use - * @param args Arguments for the format string + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @args: Arguments for the format string * Return: the number of characters which have been written into * the @buf not including the trailing '\0'. If @size is == 0 the function * returns 0. @@ -278,8 +278,8 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args); * This prints a value with grouped digits, like 12,345,678 to make it easier * to read. * - * @val: Value to print - * @digits: Number of digiits to print + * @int_val: Value to print + * @digits: Number of digiits to print */ void print_grouped_ull(unsigned long long int_val, int digits); @@ -309,9 +309,9 @@ void str_to_upper(const char *in, char *out, size_t len); /** * vsscanf - Unformat a buffer into a list of arguments - * @buf: input buffer - * @fmt: format of buffer - * @args: arguments + * @inp: input buffer + * @fmt0: format of buffer + * @ap: arguments */ int vsscanf(const char *inp, char const *fmt0, va_list ap); -- cgit v1.3.1 From d667a0d8f413d7278f912aa4e671bc56d28b25f2 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:30:57 -0600 Subject: lib: Fix a few bugs in trailing_strtoln() At present this has a minor bug in that it reads the byte before the start of the string, if it is empty. Also it doesn't handle a non-numeric prefix which is only one character long. Fix these bugs with a reworked implementation. Add a test for the second case. The first one is hard to test. Signed-off-by: Simon Glass --- include/vsprintf.h | 3 +++ lib/strto.c | 11 ++++++----- test/str_ut.c | 2 ++ 3 files changed, 11 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/vsprintf.h b/include/vsprintf.h index d4bf3211da4..5172ceedec1 100644 --- a/include/vsprintf.h +++ b/include/vsprintf.h @@ -98,6 +98,9 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base); * Given a string this finds a trailing number on the string and returns it. * For example, "abc123" would return 123. * + * Note that this does not handle a string without a prefix. See dectoul() for + * that case. + * * @str: String to examine * Return: trailing number if found, else -1 */ diff --git a/lib/strto.c b/lib/strto.c index f1918843765..b1d803a77d9 100644 --- a/lib/strto.c +++ b/lib/strto.c @@ -189,11 +189,12 @@ long trailing_strtoln(const char *str, const char *end) if (!end) end = str + strlen(str); - if (isdigit(end[-1])) { - for (p = end - 1; p > str; p--) { - if (!isdigit(*p)) - return dectoul(p + 1, NULL); - } + p = end - 1; + if (p > str && isdigit(*p)) { + do { + if (!isdigit(p[-1])) + return dectoul(p, NULL); + } while (--p > str); } return -1; diff --git a/test/str_ut.c b/test/str_ut.c index 9674a59f2a6..058b3594379 100644 --- a/test/str_ut.c +++ b/test/str_ut.c @@ -257,6 +257,8 @@ static int str_trailing(struct unit_test_state *uts) ut_asserteq(123, trailing_strtoln(str1, str1 + 6)); ut_asserteq(-1, trailing_strtoln(str1, str1 + 9)); + ut_asserteq(3, trailing_strtol("a3")); + return 0; } STR_TEST(str_trailing, 0); -- cgit v1.3.1 From 8565efd509236dc7d4e766de39edae2cefb10057 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:30:58 -0600 Subject: lib: Add a way to find the postiion of a trailing number At present it is not possible to find out which part of the string is the number part and which is before it. Add a new variant which provides this feature, so we can separate the two in the caller. Signed-off-by: Simon Glass --- include/vsprintf.h | 18 ++++++++++++++++++ lib/strto.c | 14 ++++++++++++-- test/str_ut.c | 13 ++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/vsprintf.h b/include/vsprintf.h index 5172ceedec1..e006af200fd 100644 --- a/include/vsprintf.h +++ b/include/vsprintf.h @@ -121,6 +121,24 @@ long trailing_strtol(const char *str); */ long trailing_strtoln(const char *str, const char *end); +/** + * trailing_strtoln_end() - extract trailing integer from a fixed-length string + * + * Given a fixed-length string this finds a trailing number on the string + * and returns it. For example, "abc123" would return 123. Only the + * characters between @str and @end - 1 are examined. If @end is NULL, it is + * set to str + strlen(str). + * + * @str: String to examine + * @end: Pointer to end of string to examine, or NULL to use the + * whole string + * @endp: If non-NULL, this is set to point to the character where the + * number starts, e.g. for "mmc0" this would be point to the '0'; if no + * trailing number is found, it is set to the end of the string + * Return: training number if found, else -1 + */ +long trailing_strtoln_end(const char *str, const char *end, char const **endp); + /** * panic() - Print a message and reset/hang * diff --git a/lib/strto.c b/lib/strto.c index b1d803a77d9..6462d4fddf1 100644 --- a/lib/strto.c +++ b/lib/strto.c @@ -183,7 +183,7 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base) return simple_strtoull(cp, endp, base); } -long trailing_strtoln(const char *str, const char *end) +long trailing_strtoln_end(const char *str, const char *end, char const **endp) { const char *p; @@ -192,14 +192,24 @@ long trailing_strtoln(const char *str, const char *end) p = end - 1; if (p > str && isdigit(*p)) { do { - if (!isdigit(p[-1])) + if (!isdigit(p[-1])) { + if (endp) + *endp = p; return dectoul(p, NULL); + } } while (--p > str); } + if (endp) + *endp = end; return -1; } +long trailing_strtoln(const char *str, const char *end) +{ + return trailing_strtoln_end(str, end, NULL); +} + long trailing_strtol(const char *str) { return trailing_strtoln(str, NULL); diff --git a/test/str_ut.c b/test/str_ut.c index 058b3594379..5a844347c2b 100644 --- a/test/str_ut.c +++ b/test/str_ut.c @@ -244,7 +244,9 @@ STR_TEST(str_xtoa, 0); static int str_trailing(struct unit_test_state *uts) { - char str1[] = "abc123def"; + const char str1[] = "abc123def"; + const char str2[] = "abc123def456"; + const char *end; ut_asserteq(-1, trailing_strtol("")); ut_asserteq(-1, trailing_strtol("123")); @@ -259,6 +261,15 @@ static int str_trailing(struct unit_test_state *uts) ut_asserteq(3, trailing_strtol("a3")); + ut_asserteq(123, trailing_strtoln_end(str1, str1 + 6, &end)); + ut_asserteq(3, end - str1); + + ut_asserteq(-1, trailing_strtoln_end(str1, str1 + 7, &end)); + ut_asserteq(7, end - str1); + + ut_asserteq(456, trailing_strtoln_end(str2, NULL, &end)); + ut_asserteq(9, end - str2); + return 0; } STR_TEST(str_trailing, 0); -- cgit v1.3.1 From 6aa4fe39122a85ce1ef559139518fa5042406186 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:30:59 -0600 Subject: dm: core: Rename and fix uclass_get_by_name_len() It seems that namelen is more common in U-Boot. Rename this function to fit in better. Also fix a bug where it breaks the operation of uclass_get_by_name() and add a test. Signed-off-by: Simon Glass Reported-by: Patrick Delaunay Reported-by: Tim Harvey --- drivers/core/uclass.c | 7 ++++--- include/dm/uclass.h | 6 +++--- test/dm/core.c | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index 2578803b7a4..4b9b54f0f15 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -180,14 +180,15 @@ void uclass_set_priv(struct uclass *uc, void *priv) uc->priv_ = priv; } -enum uclass_id uclass_get_by_name_len(const char *name, int len) +enum uclass_id uclass_get_by_namelen(const char *name, int len) { int i; for (i = 0; i < UCLASS_COUNT; i++) { struct uclass_driver *uc_drv = lists_uclass_lookup(i); - if (uc_drv && !strncmp(uc_drv->name, name, len)) + if (uc_drv && !strncmp(uc_drv->name, name, len) && + strlen(uc_drv->name) == len) return i; } @@ -196,7 +197,7 @@ enum uclass_id uclass_get_by_name_len(const char *name, int len) enum uclass_id uclass_get_by_name(const char *name) { - return uclass_get_by_name_len(name, strlen(name)); + return uclass_get_by_namelen(name, strlen(name)); } int dev_get_uclass_index(struct udevice *dev, struct uclass **ucp) diff --git a/include/dm/uclass.h b/include/dm/uclass.h index aafe6522886..f6c0110b061 100644 --- a/include/dm/uclass.h +++ b/include/dm/uclass.h @@ -173,13 +173,13 @@ int uclass_get(enum uclass_id key, struct uclass **ucp); const char *uclass_get_name(enum uclass_id id); /** - * uclass_get_by_name_len() - Look up a uclass by its partial driver name + * uclass_get_by_namelen() - Look up a uclass by its driver name * * @name: Name to look up - * @len: Length of the partial name + * @len: Length of @name (the uclass driver name must have the same length) * Return: the associated uclass ID, or UCLASS_INVALID if not found */ -enum uclass_id uclass_get_by_name_len(const char *name, int len); +enum uclass_id uclass_get_by_namelen(const char *name, int len); /** * uclass_get_by_name() - Look up a uclass by its driver name diff --git a/test/dm/core.c b/test/dm/core.c index 0ce0b3c4a2e..2c73ecf54a0 100644 --- a/test/dm/core.c +++ b/test/dm/core.c @@ -1161,6 +1161,8 @@ static int dm_test_uclass_names(struct unit_test_state *uts) ut_asserteq_str("test", uclass_get_name(UCLASS_TEST)); ut_asserteq(UCLASS_TEST, uclass_get_by_name("test")); + ut_asserteq(UCLASS_SPI, uclass_get_by_name("spi")); + return 0; } DM_TEST(dm_test_uclass_names, UT_TESTF_SCAN_PDATA); -- cgit v1.3.1 From 4e0710a2d007032c8a82ef3af45a0e6e9176a2a7 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:00 -0600 Subject: dm: core: Allow finding a uclass device by partial name In some cases two devices are related and the only way to tell is to check that the names partially patch. Add a way to check this without needing to create a new string for the comparison. Fix the comment for device_find_child_by_namelen() while we are here. Signed-off-by: Simon Glass --- drivers/core/uclass.c | 13 ++++++++++--- include/dm/device.h | 2 +- include/dm/uclass-internal.h | 16 ++++++++++++++++ test/dm/core.c | 15 +++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index 4b9b54f0f15..08d9ed82de2 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -274,8 +274,8 @@ int uclass_find_next_device(struct udevice **devp) return 0; } -int uclass_find_device_by_name(enum uclass_id id, const char *name, - struct udevice **devp) +int uclass_find_device_by_namelen(enum uclass_id id, const char *name, int len, + struct udevice **devp) { struct uclass *uc; struct udevice *dev; @@ -289,7 +289,8 @@ int uclass_find_device_by_name(enum uclass_id id, const char *name, return ret; uclass_foreach_dev(dev, uc) { - if (!strcmp(dev->name, name)) { + if (!strncmp(dev->name, name, len) && + strlen(dev->name) == len) { *devp = dev; return 0; } @@ -298,6 +299,12 @@ int uclass_find_device_by_name(enum uclass_id id, const char *name, return -ENODEV; } +int uclass_find_device_by_name(enum uclass_id id, const char *name, + struct udevice **devp) +{ + return uclass_find_device_by_namelen(id, name, strlen(name), devp); +} + int uclass_find_next_free_seq(struct uclass *uc) { struct udevice *dev; diff --git a/include/dm/device.h b/include/dm/device.h index e0f86f5df9f..b474888d025 100644 --- a/include/dm/device.h +++ b/include/dm/device.h @@ -799,7 +799,7 @@ int device_find_first_child_by_uclass(const struct udevice *parent, struct udevice **devp); /** - * device_find_child_by_name() - Find a child by device name + * device_find_child_by_namelen() - Find a child by device name * * @parent: Parent device to search * @name: Name to look for diff --git a/include/dm/uclass-internal.h b/include/dm/uclass-internal.h index daf856c03cf..3ddcdd21439 100644 --- a/include/dm/uclass-internal.h +++ b/include/dm/uclass-internal.h @@ -154,6 +154,22 @@ int uclass_find_first_device(enum uclass_id id, struct udevice **devp); */ int uclass_find_next_device(struct udevice **devp); +/** + * uclass_find_device_by_namelen() - Find uclass device based on ID and name + * + * This searches for a device with the exactly given name. + * + * The device is NOT probed, it is merely returned. + * + * @id: ID to look up + * @name: name of a device to find + * @len: Length of @name (the uclass driver name must have the same length) + * @devp: Returns pointer to device (the first one with the name) + * Return: 0 if OK, -ve on error + */ +int uclass_find_device_by_namelen(enum uclass_id id, const char *name, int len, + struct udevice **devp); + /** * uclass_find_device_by_name() - Find uclass device based on ID and name * diff --git a/test/dm/core.c b/test/dm/core.c index 2c73ecf54a0..ebd504427d1 100644 --- a/test/dm/core.c +++ b/test/dm/core.c @@ -1260,3 +1260,18 @@ static int dm_test_get_stats(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_get_stats, UT_TESTF_SCAN_FDT); + +/* Test uclass_find_device_by_name() */ +static int dm_test_uclass_find_device(struct unit_test_state *uts) +{ + struct udevice *dev; + + ut_assertok(uclass_find_device_by_name(UCLASS_I2C, "i2c@0", &dev)); + ut_asserteq(-ENODEV, + uclass_find_device_by_name(UCLASS_I2C, "i2c@0x", &dev)); + ut_assertok(uclass_find_device_by_namelen(UCLASS_I2C, "i2c@0x", 5, + &dev)); + + return 0; +} +DM_TEST(dm_test_uclass_find_device, UT_TESTF_SCAN_FDT); -- cgit v1.3.1 From 87571b7f2077c567f8d050abe60ff5578f0b7cd8 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:03 -0600 Subject: dm: blk: Add a function to return the device type Use the uclass name to get the device type for a block device. Signed-off-by: Simon Glass --- drivers/block/blk-uclass.c | 7 +++++++ include/blk.h | 8 ++++++++ 2 files changed, 15 insertions(+) (limited to 'include') diff --git a/drivers/block/blk-uclass.c b/drivers/block/blk-uclass.c index f1e4a856467..29f355fa154 100644 --- a/drivers/block/blk-uclass.c +++ b/drivers/block/blk-uclass.c @@ -509,6 +509,13 @@ int blk_get_from_parent(struct udevice *parent, struct udevice **devp) return 0; } +const char *blk_get_devtype(struct udevice *dev) +{ + struct udevice *parent = dev_get_parent(dev); + + return uclass_get_name(device_get_uclass_id(parent)); +}; + int blk_find_max_devnum(enum if_type if_type) { struct udevice *dev; diff --git a/include/blk.h b/include/blk.h index dbe9ae219da..9503369db83 100644 --- a/include/blk.h +++ b/include/blk.h @@ -433,6 +433,14 @@ int blk_select_hwpart(struct udevice *dev, int hwpart); */ int blk_get_from_parent(struct udevice *parent, struct udevice **devp); +/** + * blk_get_devtype() - Get the device type of a block device + * + * @dev: Block device to check + * Return: device tree, i.e. the uclass name of its parent, e.g. "mmc" + */ +const char *blk_get_devtype(struct udevice *dev); + /** * blk_get_by_device() - Get the block device descriptor for the given device * @dev: Instance of a storage device -- cgit v1.3.1 From c24e58f599085efa79a61cb62170b6b2164b8ce9 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:04 -0600 Subject: fs: Add a function to set the filesystem type When sandbox is used with hostfs we won't have a block device, but still must set up the filesystem type before any filesystem operation, such as loading a file. Add a function to handle this. Signed-off-by: Simon Glass --- fs/fs.c | 5 +++++ include/fs.h | 11 +++++++++++ 2 files changed, 16 insertions(+) (limited to 'include') diff --git a/fs/fs.c b/fs/fs.c index b812597ced9..6de1a3eb6d5 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -36,6 +36,11 @@ static int fs_dev_part; static struct disk_partition fs_partition; static int fs_type = FS_TYPE_ANY; +void fs_set_type(int type) +{ + fs_type = type; +} + static inline int fs_probe_unsupported(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { diff --git a/include/fs.h b/include/fs.h index e2beba36b98..b43f16a692f 100644 --- a/include/fs.h +++ b/include/fs.h @@ -57,6 +57,17 @@ int do_ext2load(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); */ int fs_set_blk_dev(const char *ifname, const char *dev_part_str, int fstype); +/** + * fs_set_type() - Tell fs layer which filesystem type is used + * + * This is needed when reading from a non-block device such as sandbox. It does + * a similar job to fs_set_blk_dev() but just sets the filesystem type instead + * of detecting it and loading it on the block device + * + * @type: Filesystem type to use (FS_TYPE...) + */ +void fs_set_type(int type); + /* * fs_set_blk_dev_with_part - Set current block device + partition * -- cgit v1.3.1 From 9d260253e869bf518e35a97f8a394719bd5149fb Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:05 -0600 Subject: bootstd: Add the concept of a bootflow A bootflow encapsulates the process used to boot an operating system. It typically has a control file (such as extlinux.conf) and information about which 'bootdev' it came from. Add the header file for this first, since it is needed by all other files. Signed-off-by: Simon Glass --- MAINTAINERS | 5 ++ include/bootflow.h | 260 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 include/bootflow.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index f7665fccf7a..d6d45573fdc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -693,6 +693,11 @@ M: Simon Glass S: Maintained F: tools/binman/ +BOOTDEVICE +M: Simon Glass +S: Maintained +F: include/bootflow.h + BTRFS M: Marek Behun R: Qu Wenruo diff --git a/include/bootflow.h b/include/bootflow.h new file mode 100644 index 00000000000..6e9a729a9a3 --- /dev/null +++ b/include/bootflow.h @@ -0,0 +1,260 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#ifndef __bootflow_h +#define __bootflow_h + +#include + +/** + * enum bootflow_state_t - states that a particular bootflow can be in + * + * Only bootflows in state BOOTFLOWST_READY can be used to boot. + * + * See bootflow_state[] for the names for each of these + */ +enum bootflow_state_t { + BOOTFLOWST_BASE, /**< Nothing known yet */ + BOOTFLOWST_MEDIA, /**< Media exists */ + BOOTFLOWST_PART, /**< Partition exists */ + BOOTFLOWST_FS, /**< Filesystem exists */ + BOOTFLOWST_FILE, /**< Bootflow file exists */ + BOOTFLOWST_READY, /**< Bootflow file loaded */ + + BOOTFLOWST_COUNT +}; + +/** + * struct bootflow - information about a bootflow + * + * This is connected into two separate linked lists: + * + * bm_sibling - links all bootflows in the same bootdev + * glob_sibling - links all bootflows in all bootdevs + * + * @bm_node: Points to siblings in the same bootdev + * @glob_node: Points to siblings in the global list (all bootdev) + * @dev: Bootdevice device which produced this bootflow + * @blk: Block device which contains this bootflow, NULL if this is a network + * device + * @part: Partition number (0 for whole device) + * @fs_type: Filesystem type (FS_TYPE...) if this is fixed by the media, else 0. + * For example, the sandbox host-filesystem bootdev sets this to + * FS_TYPE_SANDBOX + * @method: Bootmethod device used to perform the boot and read files + * @name: Name of bootflow (allocated) + * @state: Current state (enum bootflow_state_t) + * @subdir: Subdirectory to fetch files from (with trailing /), or NULL if none + * @fname: Filename of bootflow file (allocated) + * @buf: Bootflow file contents (allocated) + * @size: Size of bootflow file in bytes + * @err: Error number received (0 if OK) + */ +struct bootflow { + struct list_head bm_node; + struct list_head glob_node; + struct udevice *dev; + struct udevice *blk; + int part; + int fs_type; + struct udevice *method; + char *name; + enum bootflow_state_t state; + char *subdir; + char *fname; + char *buf; + int size; + int err; +}; + +/** + * enum bootflow_flags_t - flags for the bootflow iterator + * + * @BOOTFLOWF_FIXED: Only used fixed/internal media + * @BOOTFLOWF_SHOW: Show each bootdev before scanning it + * @BOOTFLOWF_ALL: Return bootflows with errors as well + * @BOOTFLOWF_SINGLE_DEV: Just scan one bootmeth + */ +enum bootflow_flags_t { + BOOTFLOWF_FIXED = 1 << 0, + BOOTFLOWF_SHOW = 1 << 1, + BOOTFLOWF_ALL = 1 << 2, + BOOTFLOWF_SINGLE_DEV = 1 << 3, +}; + +/** + * struct bootflow_iter - state for iterating through bootflows + * + * This starts at with the first bootdev/partition/bootmeth and can be used to + * iterate through all of them. + * + * Iteration starts with the bootdev. The first partition (0, i.e. whole device) + * is scanned first. For partition 0, it iterates through all the available + * bootmeths to see which one(s) can provide a bootflow. Then it moves to + * parition 1 (if there is one) and the process continues. Once all partitions + * are examined, it moves to the next bootdev. + * + * Initially @max_part is 0, meaning that only the whole device (@part=0) can be + * used. During scanning, if a partition table is found, then @max_part is + * updated to a larger value, no less than the number of available partitions. + * This ensures that iteration works through all partitions on the bootdev. + * + * @flags: Flags to use (see enum bootflow_flags_t) + * @dev: Current bootdev + * @part: Current partition number (0 for whole device) + * @method: Current bootmeth + * @max_part: Maximum hardware partition number in @dev, 0 if there is no + * partition table + * @err: Error obtained from checking the last iteration. This is used to skip + * forward (e.g. to skip the current partition because it is not valid) + * -ESHUTDOWN: try next bootdev + * @num_devs: Number of bootdevs in @dev_order + * @cur_dev: Current bootdev number, an index into @dev_order[] + * @dev_order: List of bootdevs to scan, in order of priority. The scan starts + * with the first one on the list + * @num_methods: Number of bootmeth devices in @method_order + * @cur_method: Current method number, an index into @method_order + * @method_order: List of bootmeth devices to use, in order + */ +struct bootflow_iter { + int flags; + struct udevice *dev; + int part; + struct udevice *method; + int max_part; + int err; + int num_devs; + int cur_dev; + struct udevice **dev_order; + int num_methods; + int cur_method; + struct udevice **method_order; +}; + +/** + * bootflow_iter_init() - Reset a bootflow iterator + * + * This sets everything to the starting point, ready for use. + * + * @iter: Place to store private info (inited by this call) + * @flags: Flags to use (see enum bootflow_flags_t) + */ +void bootflow_iter_init(struct bootflow_iter *iter, int flags); + +/** + * bootflow_iter_uninit() - Free memory used by an interator + * + * @iter: Iterator to free + */ +void bootflow_iter_uninit(struct bootflow_iter *iter); + +/** + * bootflow_scan_bootdev() - find the first bootflow in a bootdev + * + * If @flags includes BOOTFLOWF_ALL then bootflows with errors are returned too + * + * @dev: Boot device to scan, NULL to work through all of them until it + * finds one that * can supply a bootflow + * @iter: Place to store private info (inited by this call) + * @flags: Flags for bootdev (enum bootflow_flags_t) + * @bflow: Place to put the bootflow if found + * Return: 0 if found, -ENODEV if no device, other -ve on other error + * (iteration can continue) + */ +int bootflow_scan_bootdev(struct udevice *dev, struct bootflow_iter *iter, + int flags, struct bootflow *bflow); + +/** + * bootflow_scan_first() - find the first bootflow + * + * This works through the available bootdev devices until it finds one that + * can supply a bootflow. It then returns that + * + * If @flags includes BOOTFLOWF_ALL then bootflows with errors are returned too + * + * @iter: Place to store private info (inited by this call), with + * @flags: Flags for bootdev (enum bootflow_flags_t) + * @bflow: Place to put the bootflow if found + * Return: 0 if found, -ENODEV if no device, other -ve on other error (iteration + * can continue) + */ +int bootflow_scan_first(struct bootflow_iter *iter, int flags, + struct bootflow *bflow); + +/** + * bootflow_scan_next() - find the next bootflow + * + * This works through the available bootdev devices until it finds one that + * can supply a bootflow. It then returns that bootflow + * + * @iter: Private info (as set up by bootflow_scan_first()) + * @bflow: Place to put the bootflow if found + * Return: 0 if found, -ENODEV if no device, -ESHUTDOWN if no more bootflows, + * other -ve on other error (iteration can continue) + */ +int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow); + +/** + * bootflow_first_glob() - Get the first bootflow from the global list + * + * Returns the first bootflow in the global list, no matter what bootflow it is + * attached to + * + * @bflowp: Returns a pointer to the bootflow + * Return: 0 if found, -ENOENT if there are no bootflows + */ +int bootflow_first_glob(struct bootflow **bflowp); + +/** + * bootflow_next_glob() - Get the next bootflow from the global list + * + * Returns the next bootflow in the global list, no matter what bootflow it is + * attached to + * + * @bflowp: On entry, the last bootflow returned , e.g. from + * bootflow_first_glob() + * Return: 0 if found, -ENOENT if there are no more bootflows + */ +int bootflow_next_glob(struct bootflow **bflowp); + +/** + * bootflow_free() - Free memory used by a bootflow + * + * This frees fields within @bflow, but not the @bflow pointer itself + */ +void bootflow_free(struct bootflow *bflow); + +/** + * bootflow_boot() - boot a bootflow + * + * @bflow: Bootflow to boot + * Return: -EPROTO if bootflow has not been loaded, -ENOSYS if the bootflow + * type is not supported, -EFAULT if the boot returned without an error + * when we are expecting it to boot, -ENOTSUPP if trying method resulted in + * finding out that is not actually supported for this boot and should not + * be tried again unless something changes + */ +int bootflow_boot(struct bootflow *bflow); + +/** + * bootflow_run_boot() - Try to boot a bootflow + * + * @iter: Current iteration (or NULL if none). Used to disable a bootmeth if the + * boot returns -ENOTSUPP + * @bflow: Bootflow to boot + * Return: result of trying to boot + */ +int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow); + +/** + * bootflow_state_get_name() - Get the name of a bootflow state + * + * @state: State to check + * Return: name, or "?" if invalid + */ +const char *bootflow_state_get_name(enum bootflow_state_t state); + +#endif -- cgit v1.3.1 From ef5e3891f57e6fc863fabbc94b1d7da79d1940bb Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:06 -0600 Subject: bootstd: Add the bootstd uclass and core implementation The 'bootstd' device provides the central information about U-Boot standard boot. Add a uclass for bootstd and the various helpers needed to make it work. Also add a binding file. Signed-off-by: Simon Glass --- MAINTAINERS | 2 + boot/Kconfig | 34 ++++++++ boot/Makefile | 3 + boot/bootstd-uclass.c | 152 +++++++++++++++++++++++++++++++++++ configs/efi-x86_app32_defconfig | 1 + configs/efi-x86_app64_defconfig | 1 + doc/device-tree-bindings/bootstd.txt | 28 +++++++ include/bootstd.h | 80 ++++++++++++++++++ include/dm/uclass-id.h | 1 + 9 files changed, 302 insertions(+) create mode 100644 boot/bootstd-uclass.c create mode 100644 doc/device-tree-bindings/bootstd.txt create mode 100644 include/bootstd.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index d6d45573fdc..dd099d6240f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -696,6 +696,8 @@ F: tools/binman/ BOOTDEVICE M: Simon Glass S: Maintained +F: boot/bootstd.c +F: include/bootstd.h F: include/bootflow.h BTRFS diff --git a/boot/Kconfig b/boot/Kconfig index ec5b956490d..9faa55a5418 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -292,6 +292,40 @@ endif # SPL endif # FIT +config BOOTSTD + bool "Standard boot support" + default y + depends on DM && OF_CONTROL && BLK + help + U-Boot supports a standard way of locating something to boot, + typically an Operating System such as Linux, provided by a distro such + as Arch Linux or Debian. Enable this to support iterating through + available bootdevs and using bootmeths to find bootflows suitable for + booting. + + Standard boot is not a standard way of booting, just a framework + within U-Boot for supporting all the different ways that exist. + + Terminology: + + - bootdev - a device which can hold a distro (e.g. MMC) + - bootmeth - a method to scan a bootdev to find bootflows (owned by + U-Boot) + - bootflow - a description of how to boot (owned by the distro) + +config BOOTSTD_FULL + bool "Enhanced features for standard boot" + default y if SANDBOX + help + This enables various useful features for standard boot, which are not + essential for operation: + + - bootdev, bootmeth commands + - extra features in the bootflow command + - support for selecting the ordering of bootmeths ("bootmeth order") + - support for selecting the ordering of bootdevs using the devicetree + as well as the "boot_targets" environment variable + config LEGACY_IMAGE_FORMAT bool "Enable support for the legacy image format" default y if !FIT_SIGNATURE diff --git a/boot/Makefile b/boot/Makefile index 1b99e6ee33f..21dcf6a6056 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -18,6 +18,9 @@ endif obj-y += image.o image-board.o obj-$(CONFIG_ANDROID_AB) += android_ab.o obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o + +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o + obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c new file mode 100644 index 00000000000..615cd89bf0b --- /dev/null +++ b/boot/bootstd-uclass.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Uclass implementation for standard boot + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +/* These are used if filename-prefixes is not present */ +const char *const default_prefixes[] = {"/", "/boot/", NULL}; + +static int bootstd_of_to_plat(struct udevice *dev) +{ + struct bootstd_priv *priv = dev_get_priv(dev); + int ret; + + if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) { + /* Don't check errors since livetree and flattree are different */ + ret = dev_read_string_list(dev, "filename-prefixes", + &priv->prefixes); + dev_read_string_list(dev, "bootdev-order", + &priv->bootdev_order); + } + + return 0; +} + +static void bootstd_clear_glob_(struct bootstd_priv *priv) +{ + while (!list_empty(&priv->glob_head)) { + struct bootflow *bflow; + + bflow = list_first_entry(&priv->glob_head, struct bootflow, + glob_node); + /* add later bootflow_remove(bflow); */ + } +} + +void bootstd_clear_glob(void) +{ + struct bootstd_priv *std; + + if (bootstd_get_priv(&std)) + return; + + bootstd_clear_glob_(std); +} + +static int bootstd_remove(struct udevice *dev) +{ + struct bootstd_priv *priv = dev_get_priv(dev); + + free(priv->prefixes); + free(priv->bootdev_order); + bootstd_clear_glob_(priv); + + return 0; +} + +const char *const *const bootstd_get_bootdev_order(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + return std->bootdev_order; +} + +const char *const *const bootstd_get_prefixes(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + return std->prefixes ? std->prefixes : default_prefixes; +} + +int bootstd_get_priv(struct bootstd_priv **stdp) +{ + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &dev); + if (ret) + return ret; + *stdp = dev_get_priv(dev); + + return 0; +} + +static int bootstd_probe(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + INIT_LIST_HEAD(&std->glob_head); + + return 0; +} + +/* For now, bind the boormethod device if none are found in the devicetree */ +int dm_scan_other(bool pre_reloc_only) +{ + struct udevice *bootstd; + int ret; + + /* These are not needed before relocation */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + /* Create a bootstd device if needed */ + uclass_find_first_device(UCLASS_BOOTSTD, &bootstd); + if (!bootstd) { + ret = device_bind_driver(gd->dm_root, "bootstd_drv", "bootstd", + &bootstd); + if (ret) + return log_msg_ret("bootstd", ret); + } + + return 0; +} + +static const struct udevice_id bootstd_ids[] = { + { .compatible = "u-boot,boot-std" }, + { } +}; + +U_BOOT_DRIVER(bootstd_drv) = { + .id = UCLASS_BOOTSTD, + .name = "bootstd_drv", + .of_to_plat = bootstd_of_to_plat, + .probe = bootstd_probe, + .remove = bootstd_remove, + .of_match = bootstd_ids, + .priv_auto = sizeof(struct bootstd_priv), +}; + +UCLASS_DRIVER(bootstd) = { + .id = UCLASS_BOOTSTD, + .name = "bootstd", +#if CONFIG_IS_ENABLED(OF_REAL) + .post_bind = dm_scan_fdt_dev, +#endif +}; diff --git a/configs/efi-x86_app32_defconfig b/configs/efi-x86_app32_defconfig index 228643a1bd6..7a723c136c2 100644 --- a/configs/efi-x86_app32_defconfig +++ b/configs/efi-x86_app32_defconfig @@ -8,6 +8,7 @@ CONFIG_VENDOR_EFI=y CONFIG_TARGET_EFI_APP32=y CONFIG_DEBUG_UART=y CONFIG_FIT=y +# CONFIG_BOOTSTD is not set CONFIG_SHOW_BOOT_PROGRESS=y CONFIG_USE_BOOTARGS=y CONFIG_BOOTARGS="root=/dev/sdb3 init=/sbin/init rootwait ro" diff --git a/configs/efi-x86_app64_defconfig b/configs/efi-x86_app64_defconfig index 1ed2f130509..98f91d81164 100644 --- a/configs/efi-x86_app64_defconfig +++ b/configs/efi-x86_app64_defconfig @@ -8,6 +8,7 @@ CONFIG_VENDOR_EFI=y CONFIG_TARGET_EFI_APP64=y CONFIG_DEBUG_UART=y CONFIG_FIT=y +# CONFIG_BOOTSTD is not set CONFIG_SHOW_BOOT_PROGRESS=y CONFIG_USE_BOOTARGS=y CONFIG_BOOTARGS="root=/dev/sdb3 init=/sbin/init rootwait ro" diff --git a/doc/device-tree-bindings/bootstd.txt b/doc/device-tree-bindings/bootstd.txt new file mode 100644 index 00000000000..f048b9dd327 --- /dev/null +++ b/doc/device-tree-bindings/bootstd.txt @@ -0,0 +1,28 @@ +U-Boot standard boot device (bootstd) +===================================== + +This is the controlling device for U-Boot standard boot, providing a way to +boot operating systems in a way that can be controlled by distros. + +Required properties: + +compatible: "u-boot,boot-std" + +Optional properties: + +filename-prefixes: + List of strings, each a directory to search for bootflow files + +bootdev-order: + List of bootdevs to check for bootflows, each a bootdev label (the media + uclass followed by the numeric sequence number of the media device) + + +Example: + + bootstd { + compatible = "u-boot,boot-std"; + + filename-prefixes = "/", "/boot/"; + bootdev-order = "mmc2", "mmc1"; + }; diff --git a/include/bootstd.h b/include/bootstd.h new file mode 100644 index 00000000000..b002365f4f0 --- /dev/null +++ b/include/bootstd.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Standard U-Boot boot framework + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#ifndef __bootstd_h +#define __bootstd_h + +struct udevice; + +/** + * struct bootstd_priv - priv data for the bootstd driver + * + * This is attached to the (only) bootstd device, so there is only one instance + * of this struct. It provides overall information about bootdevs and bootflows. + * + * @prefixes: NULL-terminated list of prefixes to use for bootflow filenames, + * e.g. "/", "/boot/"; NULL if none + * @bootdev_order: Order to use for bootdevs (or NULL if none), with each item + * being a bootdev label, e.g. "mmc2", "mmc1"; + * @cur_bootdev: Currently selected bootdev (for commands) + * @cur_bootflow: Currently selected bootflow (for commands) + * @glob_head: Head for the global list of all bootflows across all bootdevs + * @bootmeth_count: Number of bootmeth devices in @bootmeth_order + * @bootmeth_order: List of bootmeth devices to use, in order, NULL-terminated + */ +struct bootstd_priv { + const char **prefixes; + const char **bootdev_order; + struct udevice *cur_bootdev; + struct bootflow *cur_bootflow; + struct list_head glob_head; + int bootmeth_count; + struct udevice **bootmeth_order; +}; + +/** + * bootstd_get_bootdev_order() - Get the boot-order list + * + * This reads the boot order, e.g. {"mmc0", "mmc2", NULL} + * + * The list is alloced by the bootstd driver so should not be freed. That is the + * reason for all the const stuff in the function signature + * + * Return: list of string points, terminated by NULL; or NULL if no boot order + */ +const char *const *const bootstd_get_bootdev_order(struct udevice *dev); + +/** + * bootstd_get_prefixes() - Get the filename-prefixes list + * + * This reads the prefixes, e.g. {"/", "/bpot", NULL} + * + * The list is alloced by the bootstd driver so should not be freed. That is the + * reason for all the const stuff in the function signature + * + * Return: list of string points, terminated by NULL; or NULL if no boot order + */ +const char *const *const bootstd_get_prefixes(struct udevice *dev); + +/** + * bootstd_get_priv() - Get the (single) state for the bootstd system + * + * The state holds a global list of all bootflows that have been found. + * + * Return: 0 if OK, -ve if the uclass does not exist + */ +int bootstd_get_priv(struct bootstd_priv **stdp); + +/** + * bootstd_clear_glob() - Clear the global list of bootflows + * + * This removes all bootflows globally and across all bootdevs. + */ +void bootstd_clear_glob(void); + +#endif diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 0e26e1d1382..cece0626a11 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -38,6 +38,7 @@ enum uclass_id { UCLASS_AXI, /* AXI bus */ UCLASS_BLK, /* Block device */ UCLASS_BOOTCOUNT, /* Bootcount backing store */ + UCLASS_BOOTSTD, /* Standard boot driver */ UCLASS_BUTTON, /* Button */ UCLASS_CACHE, /* Cache controller */ UCLASS_CLK, /* Clock source, e.g. used by peripherals */ -- cgit v1.3.1 From 201417d700a2ab09f247c1be9952176970c0f6bd Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:07 -0600 Subject: bootstd: Add the bootdev uclass A 'bootdev' is a device which can be used to boot an operating system. It is a child of the media device (e.g. MMC) which handles reading files from that device, such as a bootflow file. Add a uclass for bootdev and the various helpers needed to make it work. Also add a binding file, empty for now. Signed-off-by: Simon Glass --- MAINTAINERS | 4 +- boot/Makefile | 1 + boot/bootdev-uclass.c | 640 +++++++++++++++++++++++++++++++++++ doc/device-tree-bindings/bootdev.txt | 8 + include/bootdev.h | 275 +++++++++++++++ include/dm/uclass-id.h | 1 + 6 files changed, 928 insertions(+), 1 deletion(-) create mode 100644 boot/bootdev-uclass.c create mode 100644 doc/device-tree-bindings/bootdev.txt create mode 100644 include/bootdev.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index dd099d6240f..78ec857d182 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -696,9 +696,11 @@ F: tools/binman/ BOOTDEVICE M: Simon Glass S: Maintained +F: boot/bootdev*.c F: boot/bootstd.c -F: include/bootstd.h +F: include/bootdev*.h F: include/bootflow.h +F: include/bootstd.h BTRFS M: Marek Behun diff --git a/boot/Makefile b/boot/Makefile index 21dcf6a6056..301d75fb646 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -19,6 +19,7 @@ obj-y += image.o image-board.o obj-$(CONFIG_ANDROID_AB) += android_ab.o obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootdev-uclass.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c new file mode 100644 index 00000000000..e0baeb82b14 --- /dev/null +++ b/boot/bootdev-uclass.c @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + /* + * Set some sort of limit on the number of partitions a bootdev can + * have. Note that for disks this limits the partitions numbers that + * are scanned to 1..MAX_BOOTFLOWS_PER_BOOTDEV + */ + MAX_PART_PER_BOOTDEV = 30, + + /* Maximum supported length of the "boot_targets" env string */ + BOOT_TARGETS_MAX_LEN = 100, +}; + +int bootdev_add_bootflow(struct bootflow *bflow) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); + struct bootstd_priv *std; + struct bootflow *new; + int ret; + + assert(bflow->dev); + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + new = malloc(sizeof(*bflow)); + if (!new) + return log_msg_ret("bflow", -ENOMEM); + memcpy(new, bflow, sizeof(*bflow)); + + list_add_tail(&new->glob_node, &std->glob_head); + list_add_tail(&new->bm_node, &ucp->bootflow_head); + + return 0; +} + +int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + if (list_empty(&ucp->bootflow_head)) + return -ENOENT; + + *bflowp = list_first_entry(&ucp->bootflow_head, struct bootflow, + bm_node); + + return 0; +} + +int bootdev_next_bootflow(struct bootflow **bflowp) +{ + struct bootflow *bflow = *bflowp; + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); + + *bflowp = NULL; + + if (list_is_last(&bflow->bm_node, &ucp->bootflow_head)) + return -ENOENT; + + *bflowp = list_entry(bflow->bm_node.next, struct bootflow, bm_node); + + return 0; +} + +int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name, + struct udevice **devp) +{ + struct udevice *dev; + char dev_name[30]; + char *str; + int ret; + + snprintf(dev_name, sizeof(dev_name), "%s.%s", parent->name, name); + str = strdup(dev_name); + if (!str) + return -ENOMEM; + ret = device_bind_driver(parent, drv_name, str, &dev); + if (ret) + return ret; + device_set_name_alloced(dev); + *devp = dev; + + return 0; +} + +int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, + struct bootflow_iter *iter, struct bootflow *bflow) +{ + struct blk_desc *desc = dev_get_uclass_plat(blk); + struct disk_partition info; + char partstr[20]; + char name[60]; + int ret; + + /* Sanity check */ + if (iter->part >= MAX_PART_PER_BOOTDEV) + return log_msg_ret("max", -ESHUTDOWN); + + bflow->blk = blk; + if (iter->part) + snprintf(partstr, sizeof(partstr), "part_%x", iter->part); + else + strcpy(partstr, "whole"); + snprintf(name, sizeof(name), "%s.%s", dev->name, partstr); + bflow->name = strdup(name); + if (!bflow->name) + return log_msg_ret("name", -ENOMEM); + + bflow->part = iter->part; + + /* + * partition numbers start at 0 so this cannot succeed, but it can tell + * us whether there is valid media there + */ + ret = part_get_info(desc, iter->part, &info); + if (!iter->part && ret == -ENOENT) + ret = 0; + + /* + * This error indicates the media is not present. Otherwise we just + * blindly scan the next partition. We could be more intelligent here + * and check which partition numbers actually exist. + */ + if (ret == -EOPNOTSUPP) + ret = -ESHUTDOWN; + else + bflow->state = BOOTFLOWST_MEDIA; + if (ret) + return log_msg_ret("part", ret); + + /* + * Currently we don't get the number of partitions, so just + * assume a large number + */ + iter->max_part = MAX_PART_PER_BOOTDEV; + + if (iter->part) { + ret = fs_set_blk_dev_with_part(desc, bflow->part); + bflow->state = BOOTFLOWST_PART; + + /* Use an #ifdef due to info.sys_ind */ +#ifdef CONFIG_DOS_PARTITION + log_debug("%s: Found partition %x type %x fstype %d\n", + blk->name, bflow->part, info.sys_ind, + ret ? -1 : fs_get_type()); +#endif + if (ret) + return log_msg_ret("fs", ret); + bflow->state = BOOTFLOWST_FS; + } + + return 0; +} + +void bootdev_list(bool probe) +{ + struct udevice *dev; + int ret; + int i; + + printf("Seq Probed Status Uclass Name\n"); + printf("--- ------ ------ -------- ------------------\n"); + if (probe) + ret = uclass_first_device_err(UCLASS_BOOTDEV, &dev); + else + ret = uclass_find_first_device(UCLASS_BOOTDEV, &dev); + for (i = 0; dev; i++) { + printf("%3x [ %c ] %6s %-9.9s %s\n", dev_seq(dev), + device_active(dev) ? '+' : ' ', + ret ? simple_itoa(ret) : "OK", + dev_get_uclass_name(dev_get_parent(dev)), dev->name); + if (probe) + ret = uclass_next_device_err(&dev); + else + ret = uclass_find_next_device(&dev); + } + printf("--- ------ ------ -------- ------------------\n"); + printf("(%d bootdev%s)\n", i, i != 1 ? "s" : ""); +} + +int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name) +{ + struct udevice *bdev; + int ret; + + ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, + &bdev); + if (ret) { + if (ret != -ENODEV) { + log_debug("Cannot access bootdev device\n"); + return ret; + } + + ret = bootdev_bind(parent, drv_name, "bootdev", &bdev); + if (ret) { + log_debug("Cannot create bootdev device\n"); + return ret; + } + } + + return 0; +} + +int bootdev_setup_sibling_blk(struct udevice *blk, const char *drv_name) +{ + struct udevice *parent, *dev; + char dev_name[50]; + int ret; + + snprintf(dev_name, sizeof(dev_name), "%s.%s", blk->name, "bootdev"); + + parent = dev_get_parent(blk); + ret = device_find_child_by_name(parent, dev_name, &dev); + if (ret) { + char *str; + + if (ret != -ENODEV) { + log_debug("Cannot access bootdev device\n"); + return ret; + } + str = strdup(dev_name); + if (!str) + return -ENOMEM; + + ret = device_bind_driver(parent, drv_name, str, &dev); + if (ret) { + log_debug("Cannot create bootdev device\n"); + return ret; + } + device_set_name_alloced(dev); + } + + return 0; +} + +int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp) +{ + struct udevice *parent = dev_get_parent(dev); + struct udevice *blk; + int ret, len; + char *p; + + if (device_get_uclass_id(dev) != UCLASS_BOOTDEV) + return -EINVAL; + + /* This should always work if bootdev_setup_sibling_blk() was used */ + p = strstr(dev->name, ".bootdev"); + if (!p) + return log_msg_ret("str", -EINVAL); + + len = p - dev->name; + ret = device_find_child_by_namelen(parent, dev->name, len, &blk); + if (ret) + return log_msg_ret("find", ret); + *blkp = blk; + + return 0; +} + +static int bootdev_get_from_blk(struct udevice *blk, struct udevice **bootdevp) +{ + struct udevice *parent = dev_get_parent(blk); + struct udevice *bootdev; + char dev_name[50]; + int ret; + + if (device_get_uclass_id(blk) != UCLASS_BLK) + return -EINVAL; + + /* This should always work if bootdev_setup_sibling_blk() was used */ + snprintf(dev_name, sizeof(dev_name), "%s.%s", blk->name, "bootdev"); + ret = device_find_child_by_name(parent, dev_name, &bootdev); + if (ret) + return log_msg_ret("find", ret); + *bootdevp = bootdev; + + return 0; +} + +int bootdev_unbind_dev(struct udevice *parent) +{ + struct udevice *dev; + int ret; + + ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, &dev); + if (!ret) { + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (ret) + return log_msg_ret("rem", ret); + ret = device_unbind(dev); + if (ret) + return log_msg_ret("unb", ret); + } + + return 0; +} + +/** + * bootdev_find_by_label() - Convert a label string to a bootdev device + * + * Looks up a label name to find the associated bootdev. For example, if the + * label name is "mmc2", this will find a bootdev for an mmc device whose + * sequence number is 2. + * + * @label: Label string to convert, e.g. "mmc2" + * @devp: Returns bootdev device corresponding to that boot label + * Return: 0 if OK, -EINVAL if the label name (e.g. "mmc") does not refer to a + * uclass, -ENOENT if no bootdev for that media has the sequence number + * (e.g. 2) + */ +int bootdev_find_by_label(const char *label, struct udevice **devp) +{ + struct udevice *media; + struct uclass *uc; + enum uclass_id id; + const char *end; + int seq; + + seq = trailing_strtoln_end(label, NULL, &end); + id = uclass_get_by_namelen(label, end - label); + log_debug("find %s: seq=%d, id=%d/%s\n", label, seq, id, + uclass_get_name(id)); + if (id == UCLASS_INVALID) { + log_warning("Unknown uclass '%s' in label\n", label); + return -EINVAL; + } + if (id == UCLASS_USB) + id = UCLASS_MASS_STORAGE; + + /* Iterate through devices in the media uclass (e.g. UCLASS_MMC) */ + uclass_id_foreach_dev(id, media, uc) { + struct udevice *bdev, *blk; + int ret; + + /* if there is no seq, match anything */ + if (seq != -1 && dev_seq(media) != seq) { + log_debug("- skip, media seq=%d\n", dev_seq(media)); + continue; + } + + ret = device_find_first_child_by_uclass(media, UCLASS_BOOTDEV, + &bdev); + if (ret) { + log_debug("- looking via blk, seq=%d, id=%d\n", seq, + id); + ret = blk_find_device(id, seq, &blk); + if (!ret) { + log_debug("- get from blk %s\n", blk->name); + ret = bootdev_get_from_blk(blk, &bdev); + } + } + if (!ret) { + log_debug("- found %s\n", bdev->name); + *devp = bdev; + return 0; + } + log_debug("- no device in %s\n", media->name); + } + log_warning("Unknown seq %d for label '%s'\n", seq, label); + + return -ENOENT; +} + +int bootdev_find_by_any(const char *name, struct udevice **devp) +{ + struct udevice *dev; + int ret, seq; + char *endp; + + seq = simple_strtol(name, &endp, 16); + + /* Select by name, label or number */ + if (*endp) { + ret = uclass_get_device_by_name(UCLASS_BOOTDEV, name, &dev); + if (ret == -ENODEV) { + ret = bootdev_find_by_label(name, &dev); + if (ret) { + printf("Cannot find bootdev '%s' (err=%d)\n", + name, ret); + return ret; + } + ret = device_probe(dev); + } + if (ret) { + printf("Cannot probe bootdev '%s' (err=%d)\n", name, + ret); + return ret; + } + } else { + ret = uclass_get_device_by_seq(UCLASS_BOOTDEV, seq, &dev); + } + if (ret) { + printf("Cannot find '%s' (err=%d)\n", name, ret); + return ret; + } + + *devp = dev; + + return 0; +} + +int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + const struct bootdev_ops *ops = bootdev_get_ops(dev); + + if (!ops->get_bootflow) + return -ENOSYS; + memset(bflow, '\0', sizeof(*bflow)); + bflow->dev = dev; + bflow->method = iter->method; + bflow->state = BOOTFLOWST_BASE; + + return ops->get_bootflow(dev, iter, bflow); +} + +void bootdev_clear_bootflows(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + while (!list_empty(&ucp->bootflow_head)) { + struct bootflow *bflow; + + bflow = list_first_entry(&ucp->bootflow_head, struct bootflow, + bm_node); + /* later bootflow_remove(bflow); */ + } +} + +/** + * h_cmp_bootdev() - Compare two bootdevs to find out which should go first + * + * @v1: struct udevice * of first bootdev device + * @v2: struct udevice * of second bootdev device + * Return: sort order (<0 if dev1 < dev2, ==0 if equal, >0 if dev1 > dev2) + */ +static int h_cmp_bootdev(const void *v1, const void *v2) +{ + const struct udevice *dev1 = *(struct udevice **)v1; + const struct udevice *dev2 = *(struct udevice **)v2; + const struct bootdev_uc_plat *ucp1 = dev_get_uclass_plat(dev1); + const struct bootdev_uc_plat *ucp2 = dev_get_uclass_plat(dev2); + int diff; + + /* Use priority first */ + diff = ucp1->prio - ucp2->prio; + if (diff) + return diff; + + /* Fall back to seq for devices of the same priority */ + diff = dev_seq(dev1) - dev_seq(dev2); + + return diff; +} + +/** + * build_order() - Build the ordered list of bootdevs to use + * + * This builds an ordered list of devices by one of three methods: + * - using the boot_targets environment variable, if non-empty + * - using the bootdev-order devicetree property, if present + * - sorted by priority and sequence number + * + * @bootstd: BOOTSTD device to use + * @order: Bootdevs listed in default order + * @max_count: Number of entries in @order + * Return: number of bootdevs found in the ordering, or -E2BIG if the + * boot_targets string is too long, or -EXDEV if the ordering produced 0 results + */ +static int build_order(struct udevice *bootstd, struct udevice **order, + int max_count) +{ + const char *overflow_target = NULL; + const char *const *labels; + struct udevice *dev; + const char *targets; + int i, ret, count; + + targets = env_get("boot_targets"); + labels = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + bootstd_get_bootdev_order(bootstd) : NULL; + if (targets) { + char str[BOOT_TARGETS_MAX_LEN]; + char *target; + + if (strlen(targets) >= BOOT_TARGETS_MAX_LEN) + return log_msg_ret("len", -E2BIG); + + /* make a copy of the string, since strok() will change it */ + strcpy(str, targets); + for (i = 0, target = strtok(str, " "); target; + target = strtok(NULL, " ")) { + ret = bootdev_find_by_label(target, &dev); + if (!ret) { + if (i == max_count) { + overflow_target = target; + break; + } + order[i++] = dev; + } + } + count = i; + } else if (labels) { + int upto; + + upto = 0; + for (i = 0; labels[i]; i++) { + ret = bootdev_find_by_label(labels[i], &dev); + if (!ret) { + if (upto == max_count) { + overflow_target = labels[i]; + break; + } + order[upto++] = dev; + } + } + count = upto; + } else { + /* sort them into priority order */ + count = max_count; + qsort(order, count, sizeof(struct udevice *), h_cmp_bootdev); + } + + if (overflow_target) { + log_warning("Expected at most %d bootdevs, but overflowed with boot_target '%s'\n", + max_count, overflow_target); + } + + if (!count) + return log_msg_ret("targ", -EXDEV); + + return count; +} + +int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp) +{ + struct udevice *bootstd, *dev = *devp, **order; + int upto, i; + int count; + int ret; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) { + log_err("Missing bootstd device\n"); + return log_msg_ret("std", ret); + } + + /* Handle scanning a single device */ + if (dev) { + iter->flags |= BOOTFLOWF_SINGLE_DEV; + return 0; + } + + count = uclass_id_count(UCLASS_BOOTDEV); + if (!count) + return log_msg_ret("count", -ENOENT); + + order = calloc(count, sizeof(struct udevice *)); + if (!order) + return log_msg_ret("order", -ENOMEM); + + /* + * Get a list of bootdevs, in seq order (i.e. using aliases). There may + * be gaps so try to count up high enough to find them all. + */ + for (i = 0, upto = 0; upto < count && i < 20 + count * 2; i++) { + ret = uclass_find_device_by_seq(UCLASS_BOOTDEV, i, &dev); + if (!ret) + order[upto++] = dev; + } + log_debug("Found %d bootdevs\n", count); + if (upto != count) + log_debug("Expected %d bootdevs, found %d using aliases\n", + count, upto); + + count = build_order(bootstd, order, upto); + if (count < 0) { + free(order); + return log_msg_ret("build", count); + } + + iter->dev_order = order; + iter->num_devs = count; + iter->cur_dev = 0; + + dev = *order; + ret = device_probe(dev); + if (ret) + return log_msg_ret("probe", ret); + *devp = dev; + + return 0; +} + +static int bootdev_post_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + INIT_LIST_HEAD(&ucp->bootflow_head); + + return 0; +} + +static int bootdev_pre_unbind(struct udevice *dev) +{ + bootdev_clear_bootflows(dev); + + return 0; +} + +UCLASS_DRIVER(bootdev) = { + .id = UCLASS_BOOTDEV, + .name = "bootdev", + .flags = DM_UC_FLAG_SEQ_ALIAS, + .per_device_plat_auto = sizeof(struct bootdev_uc_plat), + .post_bind = bootdev_post_bind, + .pre_unbind = bootdev_pre_unbind, +}; diff --git a/doc/device-tree-bindings/bootdev.txt b/doc/device-tree-bindings/bootdev.txt new file mode 100644 index 00000000000..95b7fec8212 --- /dev/null +++ b/doc/device-tree-bindings/bootdev.txt @@ -0,0 +1,8 @@ +U-Boot boot device (bootdev) +============================ + +A bootdev provides a way to obtain a bootflow file from a device. It is a +child of the media device (UCLASS_MMC, UCLASS_SPI_FLASH, etc.) + +The bootdev driver is provided by the media devices. The bindings for each +are described in this file (to come). diff --git a/include/bootdev.h b/include/bootdev.h new file mode 100644 index 00000000000..9fc219839fe --- /dev/null +++ b/include/bootdev.h @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#ifndef __bootdev_h +#define __bootdev_h + +#include + +struct bootflow; +struct bootflow_iter; +struct udevice; + +/** + * enum bootdev_prio_t - priority of each bootdev + * + * These values are associated with each bootdev and set up by the driver. + * + * Smallest value is the highest priority. By default, bootdevs are scanned from + * highest to lowest priority + */ +enum bootdev_prio_t { + BOOTDEVP_0_INTERNAL_FAST = 10, + BOOTDEVP_1_INTERNAL_SLOW = 20, + BOOTDEVP_2_SCAN_FAST = 30, + BOOTDEVP_3_SCAN_SLOW = 40, + BOOTDEVP_4_NET_BASE = 50, + BOOTDEVP_5_NET_FALLBACK = 60, + BOOTDEVP_6_SYSTEM = 70, + + BOOTDEVP_COUNT, +}; + +/** + * struct bootdev_uc_plat - uclass information about a bootdev + * + * This is attached to each device in the bootdev uclass and accessible via + * dev_get_uclass_plat(dev) + * + * @bootflows: List of available bootflows for this bootdev + * @piro: Priority of this bootdev + */ +struct bootdev_uc_plat { + struct list_head bootflow_head; + enum bootdev_prio_t prio; +}; + +/** struct bootdev_ops - Operations for the bootdev uclass */ +struct bootdev_ops { + /** + * get_bootflow() - get a bootflow + * + * @dev: Bootflow device to check + * @iter: Provides current dev, part, method to get. Should update + * max_part if there is a partition table. Should update state, + * subdir, fname, buf, size according to progress + * @bflow: Updated bootflow if found + * Return: 0 if OK, -ESHUTDOWN if there are no more bootflows on this + * device, -ENOSYS if this device doesn't support bootflows, + * other -ve value on other error + */ + int (*get_bootflow)(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow); +}; + +#define bootdev_get_ops(dev) ((struct bootdev_ops *)(dev)->driver->ops) + +/** + * bootdev_get_bootflow() - get a bootflow + * + * @dev: Bootflow device to check + * @iter: Provides current part, method to get + * @bflow: Returns bootflow if found + * Return: 0 if OK, -ESHUTDOWN if there are no more bootflows on this device, + * -ENOSYS if this device doesn't support bootflows, other -ve value on + * other error + */ +int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow); + +/** + * bootdev_bind() - Bind a new named bootdev device + * + * @parent: Parent of the new device + * @drv_name: Driver name to use for the bootdev device + * @name: Name for the device (parent name is prepended) + * @devp: the new device (which has not been probed) + */ +int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name, + struct udevice **devp); + +/** + * bootdev_find_in_blk() - Find a bootdev in a block device + * + * @dev: Bootflow device associated with this block device + * @blk: Block device to search + * @iter: Provides current dev, part, method to get. Should update + * max_part if there is a partition table + * @bflow: On entry, provides information about the partition and device to + * check. On exit, returns bootflow if found + * Return: 0 if found, -ESHUTDOWN if no more bootflows, other -ve on error + */ +int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, + struct bootflow_iter *iter, struct bootflow *bflow); + +/** + * bootdev_list() - List all available bootdevs + * + * @probe: true to probe devices, false to leave them as is + */ +void bootdev_list(bool probe); + +/** + * bootdev_clear_bootflows() - Clear bootflows from a bootdev + * + * Each bootdev maintains a list of discovered bootflows. This provides a + * way to clear it. These bootflows are removed from the global list too. + * + * @dev: bootdev device to update + */ +void bootdev_clear_bootflows(struct udevice *dev); + +/** + * bootdev_add_bootflow() - Add a bootflow to the bootdev's list + * + * All fields in @bflow must be set up. Note that @bflow->dev is used to add the + * bootflow to that device. + * + * @dev: Bootdevice device to add to + * @bflow: Bootflow to add. Note that fields within bflow must be allocated + * since this function takes over ownership of these. This functions makes + * a copy of @bflow itself (without allocating its fields again), so the + * caller must dispose of the memory used by the @bflow pointer itself + * Return: 0 if OK, -ENOMEM if out of memory + */ +int bootdev_add_bootflow(struct bootflow *bflow); + +/** + * bootdev_first_bootflow() - Get the first bootflow from a bootdev + * + * Returns the first bootflow attached to a bootdev + * + * @dev: bootdev device + * @bflowp: Returns a pointer to the bootflow + * Return: 0 if found, -ENOENT if there are no bootflows + */ +int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp); + +/** + * bootdev_next_bootflow() - Get the next bootflow from a bootdev + * + * Returns the next bootflow attached to a bootdev + * + * @bflowp: On entry, the last bootflow returned , e.g. from + * bootdev_first_bootflow() + * Return: 0 if found, -ENOENT if there are no more bootflows + */ +int bootdev_next_bootflow(struct bootflow **bflowp); + +/** + * bootdev_find_by_label() - Look up a bootdev by label + * + * Each bootdev has a label which contains the media-uclass name and a number, + * e.g. 'mmc2'. This looks up the label and returns the associated bootdev + * + * The lookup is performed based on the media device's sequence number. So for + * 'mmc2' this looks for a device in UCLASS_MMC with a dev_seq() of 2. + * + * @label: Label to look up (e.g. "mmc1" or "mmc0") + * @devp: Returns the bootdev device found, or NULL if none (note it does not + * return the media device, but its bootdev child) + * Return: 0 if OK, -EINVAL if the uclass is not supported by this board, + * -ENOENT if there is no device with that number + */ +int bootdev_find_by_label(const char *label, struct udevice **devp); + +/** + * bootdev_find_by_any() - Find a bootdev by name, label or sequence + * + * @name: name (e.g. "mmc2.bootdev"), label ("mmc2"), or sequence ("2") to find + * @devp: returns the device found, on success + * Return: 0 if OK, -ve on error + */ +int bootdev_find_by_any(const char *name, struct udevice **devp); + +/** + * bootdev_setup_iter_order() - Set up the ordering of bootdevs to scan + * + * This sets up the ordering information in @iter, based on the priority of each + * bootdev and the bootdev-order property in the bootstd node + * + * If a single device is requested, no ordering is needed + * + * @iter: Iterator to update with the order + * @devp: On entry, *devp is NULL to scan all, otherwise this is the (single) + * device to scan. Returns the first device to use, which is the passed-in + * @devp if it was non-NULL + * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve + * on other error + */ +int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp); + +#if CONFIG_IS_ENABLED(BOOTSTD) +/** + * bootdev_setup_for_dev() - Bind a new bootdev device + * + * Creates a bootdev device as a child of @parent. This should be called from + * the driver's bind() method or its uclass' post_bind() method. + * + * If a child bootdev already exists, this function does nothing + * + * @parent: Parent device (e.g. MMC or Ethernet) + * @drv_name: Name of bootdev driver to bind + * Return: 0 if OK, -ve on error + */ +int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name); + +/** + * bootdev_setup_for_blk() - Bind a new bootdev device for a blk device + * + * Creates a bootdev device as a sibling of @blk. This should be called from + * the driver's bind() method or its uclass' post_bind() method, at the same + * time as the bould device is bound + * + * If a device of the same name already exists, this function does nothing + * + * @parent: Parent device (e.g. MMC or Ethernet) + * @drv_name: Name of bootdev driver to bind + * Return: 0 if OK, -ve on error + */ +int bootdev_setup_sibling_blk(struct udevice *blk, const char *drv_name); + +/** + * bootdev_get_sibling_blk() - Locate the block device for a bootdev + * + * @dev: bootdev to check + * @blkp: returns associated block device + * Return: 0 if OK, -EINVAL if @dev is not a bootdev device, other -ve on other + * error + */ +int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp); + +/** + * bootdev_unbind_dev() - Unbind a bootdev device + * + * Remove and unbind a bootdev device which is a child of @parent. This should + * be called from the driver's unbind() method or its uclass' post_bind() + * method. + * + * @parent: Parent device (e.g. MMC or Ethernet) + * Return: 0 if OK, -ve on error + */ +int bootdev_unbind_dev(struct udevice *parent); +#else +static inline int bootdev_setup_for_dev(struct udevice *parent, + const char *drv_name) +{ + return 0; +} + +static inline int bootdev_setup_sibling_blk(struct udevice *blk, + const char *drv_name) +{ + return 0; +} + +static inline int bootdev_unbind_dev(struct udevice *parent) +{ + return 0; +} +#endif + +#endif diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index cece0626a11..97ba4c02c42 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -38,6 +38,7 @@ enum uclass_id { UCLASS_AXI, /* AXI bus */ UCLASS_BLK, /* Block device */ UCLASS_BOOTCOUNT, /* Bootcount backing store */ + UCLASS_BOOTDEV, /* Boot device for locating an OS to boot */ UCLASS_BOOTSTD, /* Standard boot driver */ UCLASS_BUTTON, /* Button */ UCLASS_CACHE, /* Cache controller */ -- cgit v1.3.1 From a950d31abe980ba40a0a94fbf41136550187f8cd Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:08 -0600 Subject: bootstd: Add the bootmeth uclass and helpers A bootmeth is a method of locating an operating system. For now, just add the uclass itself. Drivers for particular bootmeths are added later. If no bootmeths devices are included in the devicetree, create them automatically. This avoids the need for boilerplate in the devicetree files. Signed-off-by: Simon Glass --- MAINTAINERS | 2 + boot/Makefile | 1 + boot/bootmeth-uclass.c | 333 +++++++++++++++++++++++++++++++++++++++++++++++++ boot/bootstd-uclass.c | 29 ++++- include/bootmeth.h | 234 ++++++++++++++++++++++++++++++++++ include/dm/uclass-id.h | 1 + include/env_callback.h | 7 ++ 7 files changed, 605 insertions(+), 2 deletions(-) create mode 100644 boot/bootmeth-uclass.c create mode 100644 include/bootmeth.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 78ec857d182..7b6aada33c4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -696,10 +696,12 @@ F: tools/binman/ BOOTDEVICE M: Simon Glass S: Maintained +F: boot/bootmeth*.c F: boot/bootdev*.c F: boot/bootstd.c F: include/bootdev*.h F: include/bootflow.h +F: include/bootmeth.h F: include/bootstd.h BTRFS diff --git a/boot/Makefile b/boot/Makefile index 301d75fb646..1150051e737 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_ANDROID_AB) += android_ab.o obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootdev-uclass.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootmeth-uclass.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c new file mode 100644 index 00000000000..c040d5f92b2 --- /dev/null +++ b/boot/bootmeth-uclass.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->check) + return 0; + + return ops->check(dev, iter); +} + +int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->read_bootflow) + return -ENOSYS; + + return ops->read_bootflow(dev, bflow); +} + +int bootmeth_boot(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->boot) + return -ENOSYS; + + return ops->boot(dev, bflow); +} + +int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->read_file) + return -ENOSYS; + + return ops->read_file(dev, bflow, file_path, addr, sizep); +} + +/** + * bootmeth_setup_iter_order() - Set up the ordering of bootmeths to scan + * + * This sets up the ordering information in @iter, based on the selected + * ordering of the bootmethds in bootstd_priv->bootmeth_order. If there is no + * ordering there, then all bootmethods are added + * + * @iter: Iterator to update with the order + * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve + * on other error + */ +int bootmeth_setup_iter_order(struct bootflow_iter *iter) +{ + struct bootstd_priv *std; + struct udevice **order; + int count; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + /* Create an array large enough */ + count = std->bootmeth_count ? std->bootmeth_count : + uclass_id_count(UCLASS_BOOTMETH); + if (!count) + return log_msg_ret("count", -ENOENT); + + order = calloc(count, sizeof(struct udevice *)); + if (!order) + return log_msg_ret("order", -ENOMEM); + + /* If we have an ordering, copy it */ + if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && std->bootmeth_count) { + memcpy(order, std->bootmeth_order, + count * sizeof(struct bootmeth *)); + } else { + struct udevice *dev; + int i, upto; + + /* + * Get a list of bootmethods, in seq order (i.e. using aliases). + * There may be gaps so try to count up high enough to find them + * all. + */ + for (i = 0, upto = 0; upto < count && i < 20 + count * 2; i++) { + ret = uclass_get_device_by_seq(UCLASS_BOOTMETH, i, + &dev); + if (!ret) + order[upto++] = dev; + } + count = upto; + } + + iter->method_order = order; + iter->num_methods = count; + iter->cur_method = 0; + + return 0; +} + +int bootmeth_set_order(const char *order_str) +{ + struct bootstd_priv *std; + struct udevice **order; + int count, ret, i, len; + const char *s, *p; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + if (!order_str) { + free(std->bootmeth_order); + std->bootmeth_order = NULL; + std->bootmeth_count = 0; + return 0; + } + + /* Create an array large enough */ + count = uclass_id_count(UCLASS_BOOTMETH); + if (!count) + return log_msg_ret("count", -ENOENT); + + order = calloc(count + 1, sizeof(struct udevice *)); + if (!order) + return log_msg_ret("order", -ENOMEM); + + for (i = 0, s = order_str; *s && i < count; s = p + (*p == ' '), i++) { + struct udevice *dev; + + p = strchrnul(s, ' '); + len = p - s; + ret = uclass_find_device_by_namelen(UCLASS_BOOTMETH, s, len, + &dev); + if (ret) { + printf("Unknown bootmeth '%.*s'\n", len, s); + free(order); + return ret; + } + order[i] = dev; + } + order[i] = NULL; + free(std->bootmeth_order); + std->bootmeth_order = order; + std->bootmeth_count = i; + + return 0; +} + +/** + * setup_fs() - Set up read to read a file + * + * We must redo the setup before each filesystem operation. This function + * handles that, including setting the filesystem type if a block device is not + * being used + * + * @bflow: Information about file to try + * @desc: Block descriptor to read from (NULL if not a block device) + * Return: 0 if OK, -ve on error + */ +static int setup_fs(struct bootflow *bflow, struct blk_desc *desc) +{ + int ret; + + if (desc) { + ret = fs_set_blk_dev_with_part(desc, bflow->part); + if (ret) + return log_msg_ret("set", ret); + } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) { + fs_set_type(bflow->fs_type); + } + + return 0; +} + +int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc, + const char *prefix, const char *fname) +{ + char path[200]; + loff_t size; + int ret, ret2; + + snprintf(path, sizeof(path), "%s%s", prefix ? prefix : "", fname); + log_debug("trying: %s\n", path); + + free(bflow->fname); + bflow->fname = strdup(path); + if (!bflow->fname) + return log_msg_ret("name", -ENOMEM); + + if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) + fs_set_type(bflow->fs_type); + + ret = fs_size(path, &size); + log_debug(" %s - err=%d\n", path, ret); + + /* Sadly FS closes the file after fs_size() so we must redo this */ + ret2 = setup_fs(bflow, desc); + if (ret2) + return log_msg_ret("fs", ret2); + + if (ret) + return log_msg_ret("size", ret); + + bflow->size = size; + bflow->state = BOOTFLOWST_FILE; + + return 0; +} + +int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align) +{ + loff_t bytes_read; + ulong addr; + char *buf; + uint size; + int ret; + + size = bflow->size; + log_debug(" - script file size %x\n", size); + if (size > size_limit) + return log_msg_ret("chk", -E2BIG); + + buf = memalign(align, size + 1); + if (!buf) + return log_msg_ret("buf", -ENOMEM); + addr = map_to_sysmem(buf); + + ret = fs_read(bflow->fname, addr, 0, 0, &bytes_read); + if (ret) { + free(buf); + return log_msg_ret("read", ret); + } + if (size != bytes_read) + return log_msg_ret("bread", -EINVAL); + buf[size] = '\0'; + bflow->state = BOOTFLOWST_READY; + bflow->buf = buf; + + return 0; +} + +int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + struct blk_desc *desc = NULL; + loff_t len_read; + loff_t size; + int ret; + + if (bflow->blk) + desc = dev_get_uclass_plat(bflow->blk); + + ret = setup_fs(bflow, desc); + if (ret) + return log_msg_ret("fs", ret); + + ret = fs_size(file_path, &size); + if (ret) + return log_msg_ret("size", ret); + if (size > *sizep) + return log_msg_ret("spc", -ENOSPC); + + ret = setup_fs(bflow, desc); + if (ret) + return log_msg_ret("fs", ret); + + ret = fs_read(file_path, addr, 0, 0, &len_read); + if (ret) + return ret; + *sizep = len_read; + + return 0; +} + +#ifdef CONFIG_BOOTSTD_FULL +/** + * on_bootmeths() - Update the bootmeth order + * + * This will check for a valid baudrate and only apply it if valid. + */ +static int on_bootmeths(const char *name, const char *value, enum env_op op, + int flags) +{ + int ret; + + switch (op) { + case env_op_create: + case env_op_overwrite: + ret = bootmeth_set_order(value); + if (ret) + return 1; + return 0; + case env_op_delete: + bootmeth_set_order(NULL); + fallthrough; + default: + return 0; + } +} +U_BOOT_ENV_CALLBACK(bootmeths, on_bootmeths); +#endif /* CONFIG_BOOTSTD_FULL */ + +UCLASS_DRIVER(bootmeth) = { + .id = UCLASS_BOOTMETH, + .name = "bootmeth", + .flags = DM_UC_FLAG_SEQ_ALIAS, + .per_device_plat_auto = sizeof(struct bootmeth_uc_plat), +}; diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c index 615cd89bf0b..4c71c2829ef 100644 --- a/boot/bootstd-uclass.c +++ b/boot/bootstd-uclass.c @@ -109,8 +109,10 @@ static int bootstd_probe(struct udevice *dev) /* For now, bind the boormethod device if none are found in the devicetree */ int dm_scan_other(bool pre_reloc_only) { - struct udevice *bootstd; - int ret; + struct driver *drv = ll_entry_start(struct driver, driver); + const int n_ents = ll_entry_count(struct driver, driver); + struct udevice *dev, *bootstd; + int i, ret; /* These are not needed before relocation */ if (!(gd->flags & GD_FLG_RELOC)) @@ -125,6 +127,29 @@ int dm_scan_other(bool pre_reloc_only) return log_msg_ret("bootstd", ret); } + /* If there are no bootmeth devices, create them */ + uclass_find_first_device(UCLASS_BOOTMETH, &dev); + if (dev) + return 0; + + for (i = 0; i < n_ents; i++, drv++) { + /* + * Disable EFI Manager for now as no one uses it so it is + * confusing + */ + if (drv->id == UCLASS_BOOTMETH && + strcmp("efi_mgr_bootmeth", drv->name)) { + const char *name = drv->name; + + if (!strncmp("bootmeth_", name, 9)) + name += 9; + ret = device_bind(bootstd, drv, name, 0, ofnode_null(), + &dev); + if (ret) + return log_msg_ret("meth", ret); + } + } + return 0; } diff --git a/include/bootmeth.h b/include/bootmeth.h new file mode 100644 index 00000000000..484e503e338 --- /dev/null +++ b/include/bootmeth.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#ifndef __bootmeth_h +#define __bootmeth_h + +struct blk_desc; +struct bootflow; +struct bootflow_iter; +struct udevice; + +/** + * struct bootmeth_uc_plat - information the uclass keeps about each bootmeth + * + * @desc: A long description of the bootmeth + */ +struct bootmeth_uc_plat { + const char *desc; +}; + +/** struct bootmeth_ops - Operations for boot methods */ +struct bootmeth_ops { + /** + * check_supported() - check if a bootmeth supports this bootflow + * + * This is optional. If not provided, the bootdev is assumed to be + * supported + * + * The bootmeth can check the bootdev (e.g. to make sure it is a + * network device) or the partition information. The following fields + * in @iter are available: + * + * name, dev, state, part + * max_part may be set if part != 0 (i.e. there is a valid partition + * table). Otherwise max_part is 0 + * method is available but is the same as @dev + * the partition has not yet been read, nor has the filesystem been + * checked + * + * It may update only the flags in @iter + * + * @dev: Bootmethod device to check against + * @iter: On entry, provides bootdev, hwpart, part + * Return: 0 if OK, -ENOTSUPP if this bootdev is not supported + */ + int (*check)(struct udevice *dev, struct bootflow_iter *iter); + + /** + * read_bootflow() - read a bootflow for a device + * + * @dev: Bootmethod device to use + * @bflow: On entry, provides dev, hwpart, part and method. + * Returns updated bootflow if found + * Return: 0 if OK, -ve on error + */ + int (*read_bootflow)(struct udevice *dev, struct bootflow *bflow); + + /** + * read_file() - read a file needed for a bootflow + * + * Read a file from the same place as the bootflow came from + * + * @dev: Bootmethod device to use + * @bflow: Bootflow providing info on where to read from + * @file_path: Path to file (may be absolute or relative) + * @addr: Address to load file + * @sizep: On entry provides the maximum permitted size; on exit + * returns the size of the file + * Return: 0 if OK, -ENOSPC if the file is too large for @sizep, other + * -ve value if something else goes wrong + */ + int (*read_file)(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep); + + /** + * boot() - boot a bootflow + * + * @dev: Bootmethod device to boot + * @bflow: Bootflow to boot + * Return: does not return on success, since it should boot the + * Operating Systemn. Returns -EFAULT if that fails, -ENOTSUPP if + * trying method resulted in finding out that is not actually + * supported for this boot and should not be tried again unless + * something changes, other -ve on other error + */ + int (*boot)(struct udevice *dev, struct bootflow *bflow); +}; + +#define bootmeth_get_ops(dev) ((struct bootmeth_ops *)(dev)->driver->ops) + +/** + * bootmeth_check() - check if a bootmeth supports this bootflow + * + * This is optional. If not provided, the bootdev is assumed to be + * supported + * + * The bootmeth can check the bootdev (e.g. to make sure it is a + * network device) or the partition information. The following fields + * in @iter are available: + * + * name, dev, state, part + * max_part may be set if part != 0 (i.e. there is a valid partition + * table). Otherwise max_part is 0 + * method is available but is the same as @dev + * the partition has not yet been read, nor has the filesystem been + * checked + * + * It may update only the flags in @iter + * + * @dev: Bootmethod device to check against + * @iter: On entry, provides bootdev, hwpart, part + * Return: 0 if OK, -ENOTSUPP if this bootdev is not supported + */ +int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter); + +/** + * bootmeth_read_bootflow() - set up a bootflow for a device + * + * @dev: Bootmethod device to check + * @bflow: On entry, provides dev, hwpart, part and method. + * Returns updated bootflow if found + * Return: 0 if OK, -ve on error + */ +int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow); + +/** + * bootmeth_read_file() - read a file needed for a bootflow + * + * Read a file from the same place as the bootflow came from + * + * @dev: Bootmethod device to use + * @bflow: Bootflow providing info on where to read from + * @file_path: Path to file (may be absolute or relative) + * @addr: Address to load file + * @sizep: On entry provides the maximum permitted size; on exit + * returns the size of the file + * Return: 0 if OK, -ENOSPC if the file is too large for @sizep, other + * -ve value if something else goes wrong + */ +int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep); + +/** + * bootmeth_boot() - boot a bootflow + * + * @dev: Bootmethod device to boot + * @bflow: Bootflow to boot + * Return: does not return on success, since it should boot the + * Operating Systemn. Returns -EFAULT if that fails, other -ve on + * other error + */ +int bootmeth_boot(struct udevice *dev, struct bootflow *bflow); + +/** + * bootmeth_setup_iter_order() - Set up the ordering of bootmeths to scan + * + * This sets up the ordering information in @iter, based on the selected + * ordering of the bootmethds in bootstd_priv->bootmeth_order. If there is no + * ordering there, then all bootmethods are added + * + * @iter: Iterator to update with the order + * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve + * on other error + */ +int bootmeth_setup_iter_order(struct bootflow_iter *iter); + +/** + * bootmeth_set_order() - Set the bootmeth order + * + * This selects the ordering to use for bootmeths + * + * @order_str: String containing the ordering. This is a comma-separate list of + * bootmeth-device names, e.g. "syslinux,efi". If empty then a default ordering + * is used, based on the sequence number of devices (i.e. using aliases) + * Return: 0 if OK, -ENODEV if an unknown bootmeth is mentioned, -ENOMEM if + * out of memory, -ENOENT if there are no bootmeth devices + */ +int bootmeth_set_order(const char *order_str); + +/** + * bootmeth_try_file() - See we can access a given file + * + * Check for a file with a given name. If found, the filename is allocated in + * @bflow + * + * Sets the state to BOOTFLOWST_FILE on success. It also calls + * fs_set_blk_dev_with_part() so that this does not need to be done by the + * caller before reading the file. + * + * @bflow: Information about file to try + * @desc: Block descriptor to read from + * @prefix: Filename prefix to prepend to @fname (NULL for none) + * @fname: Filename to read + * Return: 0 if OK, -ENOMEM if not enough memory to allocate bflow->fname, + * other -ve value on other error + */ +int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc, + const char *prefix, const char *fname); + +/** + * bootmeth_alloc_file() - Allocate and read a bootflow file + * + * Allocates memory for a bootflow file and reads it in. Sets the state to + * BOOTFLOWST_READY on success + * + * Note that fs_set_blk_dev_with_part() must have been called previously. + * + * @bflow: Information about file to read + * @size_limit: Maximum file size to permit + * @align: Allocation alignment (1 for unaligned) + * Return: 0 if OK, -E2BIG if file is too large, -ENOMEM if out of memory, + * other -ve on other error + */ +int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align); + +/** + * bootmeth_common_read_file() - Common handler for reading a file + * + * Reads a named file from the same location as the bootflow file. + * + * @dev: bootmeth device to read from + * @bflow: Bootflow information + * @file_path: Path to file + * @addr: Address to load file to + * @sizep: On entry, the maximum file size to accept, on exit the actual file + * size read + */ +int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep); + +#endif diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 97ba4c02c42..5cee8bebf8e 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -39,6 +39,7 @@ enum uclass_id { UCLASS_BLK, /* Block device */ UCLASS_BOOTCOUNT, /* Bootcount backing store */ UCLASS_BOOTDEV, /* Boot device for locating an OS to boot */ + UCLASS_BOOTMETH, /* Bootmethod for booting an OS */ UCLASS_BOOTSTD, /* Standard boot driver */ UCLASS_BUTTON, /* Button */ UCLASS_CACHE, /* Cache controller */ diff --git a/include/env_callback.h b/include/env_callback.h index 05e9516a0f4..d5d2b2fcad6 100644 --- a/include/env_callback.h +++ b/include/env_callback.h @@ -57,6 +57,12 @@ #define NET_CALLBACKS #endif +#ifdef CONFIG_BOOTSTD +#define BOOTSTD_CALLBACK "bootmeths:bootmeths," +#else +#define BOOTSTD_CALLBACK +#endif + /* * This list of callback bindings is static, but may be overridden by defining * a new association in the ".callbacks" environment variable. @@ -65,6 +71,7 @@ ENV_DOT_ESCAPE ENV_FLAGS_VAR ":flags," \ "baudrate:baudrate," \ NET_CALLBACKS \ + BOOTSTD_CALLBACK \ "loadaddr:loadaddr," \ SILENT_CALLBACK \ SPLASHIMAGE_CALLBACK \ -- cgit v1.3.1 From a8f5be178db53f41338730e001b0f2ab459f7e31 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:09 -0600 Subject: bootstd: Add support for bootflows Add support for bootflows, including maintaining a list of them and iterating to find them. Signed-off-by: Simon Glass --- MAINTAINERS | 3 +- boot/Makefile | 1 + boot/bootdev-uclass.c | 11 +- boot/bootflow.c | 411 ++++++++++++++++++++++++++++++++++++++++++++++++++ boot/bootstd-uclass.c | 2 +- include/bootflow.h | 50 ++++++ 6 files changed, 475 insertions(+), 3 deletions(-) create mode 100644 boot/bootflow.c (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 7b6aada33c4..24e2ad75f82 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -696,8 +696,9 @@ F: tools/binman/ BOOTDEVICE M: Simon Glass S: Maintained -F: boot/bootmeth*.c F: boot/bootdev*.c +F: boot/bootflow.c +F: boot/bootmeth*.c F: boot/bootstd.c F: include/bootdev*.h F: include/bootflow.h diff --git a/boot/Makefile b/boot/Makefile index 1150051e737..ac861fd035f 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_ANDROID_AB) += android_ab.o obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootdev-uclass.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootmeth-uclass.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c index e0baeb82b14..1ede933c2f2 100644 --- a/boot/bootdev-uclass.c +++ b/boot/bootdev-uclass.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,10 @@ int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, bflow->part = iter->part; + ret = bootmeth_check(bflow->method, iter); + if (ret) + return log_msg_ret("check", ret); + /* * partition numbers start at 0 so this cannot succeed, but it can tell * us whether there is valid media there @@ -171,6 +176,10 @@ int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, bflow->state = BOOTFLOWST_FS; } + ret = bootmeth_read_bootflow(bflow->method, bflow); + if (ret) + return log_msg_ret("method", ret); + return 0; } @@ -445,7 +454,7 @@ void bootdev_clear_bootflows(struct udevice *dev) bflow = list_first_entry(&ucp->bootflow_head, struct bootflow, bm_node); - /* later bootflow_remove(bflow); */ + bootflow_remove(bflow); } } diff --git a/boot/bootflow.c b/boot/bootflow.c new file mode 100644 index 00000000000..24ba3c34660 --- /dev/null +++ b/boot/bootflow.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* error codes used to signal running out of things */ +enum { + BF_NO_MORE_PARTS = -ESHUTDOWN, + BF_NO_MORE_DEVICES = -ENODEV, +}; + +/** + * bootflow_state - name for each state + * + * See enum bootflow_state_t for what each of these means + */ +static const char *const bootflow_state[BOOTFLOWST_COUNT] = { + "base", + "media", + "part", + "fs", + "file", + "ready", +}; + +const char *bootflow_state_get_name(enum bootflow_state_t state) +{ + /* This doesn't need to be a useful name, since it will never occur */ + if (state < 0 || state >= BOOTFLOWST_COUNT) + return "?"; + + return bootflow_state[state]; +} + +int bootflow_first_glob(struct bootflow **bflowp) +{ + struct bootstd_priv *std; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + if (list_empty(&std->glob_head)) + return -ENOENT; + + *bflowp = list_first_entry(&std->glob_head, struct bootflow, + glob_node); + + return 0; +} + +int bootflow_next_glob(struct bootflow **bflowp) +{ + struct bootstd_priv *std; + struct bootflow *bflow = *bflowp; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + *bflowp = NULL; + + if (list_is_last(&bflow->glob_node, &std->glob_head)) + return -ENOENT; + + *bflowp = list_entry(bflow->glob_node.next, struct bootflow, glob_node); + + return 0; +} + +void bootflow_iter_init(struct bootflow_iter *iter, int flags) +{ + memset(iter, '\0', sizeof(*iter)); + iter->flags = flags; +} + +void bootflow_iter_uninit(struct bootflow_iter *iter) +{ + free(iter->dev_order); + free(iter->method_order); +} + +int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, + const struct udevice *bmeth) +{ + /* We only support disabling the current bootmeth */ + if (bmeth != iter->method || iter->cur_method >= iter->num_methods || + iter->method_order[iter->cur_method] != bmeth) + return -EINVAL; + + memmove(&iter->method_order[iter->cur_method], + &iter->method_order[iter->cur_method + 1], + (iter->num_methods - iter->cur_method - 1) * sizeof(void *)); + + iter->num_methods--; + + return 0; +} + +static void bootflow_iter_set_dev(struct bootflow_iter *iter, + struct udevice *dev) +{ + iter->dev = dev; + if ((iter->flags & (BOOTFLOWF_SHOW | BOOTFLOWF_SINGLE_DEV)) == + BOOTFLOWF_SHOW) { + if (dev) + printf("Scanning bootdev '%s':\n", dev->name); + else + printf("No more bootdevs\n"); + } +} + +/** + * iter_incr() - Move to the next item (method, part, bootdev) + * + * Return: 0 if OK, BF_NO_MORE_DEVICES if there are no more bootdevs + */ +static int iter_incr(struct bootflow_iter *iter) +{ + struct udevice *dev; + int ret; + + if (iter->err == BF_NO_MORE_DEVICES) + return BF_NO_MORE_DEVICES; + + if (iter->err != BF_NO_MORE_PARTS) { + /* Get the next boothmethod */ + if (++iter->cur_method < iter->num_methods) { + iter->method = iter->method_order[iter->cur_method]; + return 0; + } + } + + /* No more bootmeths; start at the first one, and... */ + iter->cur_method = 0; + iter->method = iter->method_order[iter->cur_method]; + + if (iter->err != BF_NO_MORE_PARTS) { + /* ...select next partition */ + if (++iter->part <= iter->max_part) + return 0; + } + + /* No more partitions; start at the first one and...*/ + iter->part = 0; + + /* + * Note: as far as we know, there is no partition table on the next + * bootdev, so set max_part to 0 until we discover otherwise. See + * bootdev_find_in_blk() for where this is set. + */ + iter->max_part = 0; + + /* ...select next bootdev */ + if (iter->flags & BOOTFLOWF_SINGLE_DEV) { + ret = -ENOENT; + } else if (++iter->cur_dev == iter->num_devs) { + ret = -ENOENT; + bootflow_iter_set_dev(iter, NULL); + } else { + dev = iter->dev_order[iter->cur_dev]; + ret = device_probe(dev); + if (!log_msg_ret("probe", ret)) + bootflow_iter_set_dev(iter, dev); + } + + /* if there are no more bootdevs, give up */ + if (ret) + return log_msg_ret("incr", BF_NO_MORE_DEVICES); + + return 0; +} + +/** + * bootflow_check() - Check if a bootflow can be obtained + * + * @iter: Provides part, bootmeth to use + * @bflow: Bootflow to update on success + * Return: 0 if OK, -ENOSYS if there is no bootflow support on this device, + * BF_NO_MORE_PARTS if there are no more partitions on bootdev + */ +static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow) +{ + struct udevice *dev; + int ret; + + dev = iter->dev; + ret = bootdev_get_bootflow(dev, iter, bflow); + + /* If we got a valid bootflow, return it */ + if (!ret) { + log_debug("Bootdevice '%s' part %d method '%s': Found bootflow\n", + dev->name, iter->part, iter->method->name); + return 0; + } + + /* Unless there is nothing more to try, move to the next device */ + else if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { + log_debug("Bootdevice '%s' part %d method '%s': Error %d\n", + dev->name, iter->part, iter->method->name, ret); + /* + * For 'all' we return all bootflows, even + * those with errors + */ + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + if (ret) + return log_msg_ret("check", ret); + + return 0; +} + +int bootflow_scan_bootdev(struct udevice *dev, struct bootflow_iter *iter, + int flags, struct bootflow *bflow) +{ + int ret; + + bootflow_iter_init(iter, flags); + + ret = bootdev_setup_iter_order(iter, &dev); + if (ret) + return log_msg_ret("obdev", -ENODEV); + bootflow_iter_set_dev(iter, dev); + + ret = bootmeth_setup_iter_order(iter); + if (ret) + return log_msg_ret("obmeth", -ENODEV); + + /* Find the first bootmeth (there must be at least one!) */ + iter->method = iter->method_order[iter->cur_method]; + + ret = bootflow_check(iter, bflow); + if (ret) { + if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + iter->err = ret; + ret = bootflow_scan_next(iter, bflow); + if (ret) + return log_msg_ret("get", ret); + } + + return 0; +} + +int bootflow_scan_first(struct bootflow_iter *iter, int flags, + struct bootflow *bflow) +{ + int ret; + + ret = bootflow_scan_bootdev(NULL, iter, flags, bflow); + if (ret) + return log_msg_ret("start", ret); + + return 0; +} + +int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow) +{ + int ret; + + do { + ret = iter_incr(iter); + if (ret == BF_NO_MORE_DEVICES) + return log_msg_ret("done", ret); + + if (!ret) { + ret = bootflow_check(iter, bflow); + if (!ret) + return 0; + iter->err = ret; + if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + } else { + iter->err = ret; + } + + } while (1); +} + +void bootflow_free(struct bootflow *bflow) +{ + free(bflow->name); + free(bflow->subdir); + free(bflow->fname); + free(bflow->buf); +} + +void bootflow_remove(struct bootflow *bflow) +{ + list_del(&bflow->bm_node); + list_del(&bflow->glob_node); + + bootflow_free(bflow); + free(bflow); +} + +int bootflow_boot(struct bootflow *bflow) +{ + int ret; + + if (bflow->state != BOOTFLOWST_READY) + return log_msg_ret("load", -EPROTO); + + ret = bootmeth_boot(bflow->method, bflow); + if (ret) + return log_msg_ret("boot", ret); + + /* + * internal error, should not get here since we should have booted + * something or returned an error + */ + + return log_msg_ret("end", -EFAULT); +} + +int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow) +{ + int ret; + + printf("** Booting bootflow '%s' with %s\n", bflow->name, + bflow->method->name); + ret = bootflow_boot(bflow); + if (!IS_ENABLED(CONFIG_BOOTSTD_FULL)) { + printf("Boot failed (err=%d)\n", ret); + return ret; + } + + switch (ret) { + case -EPROTO: + printf("Bootflow not loaded (state '%s')\n", + bootflow_state_get_name(bflow->state)); + break; + case -ENOSYS: + printf("Boot method '%s' not supported\n", bflow->method->name); + break; + case -ENOTSUPP: + /* Disable this bootflow for this iteration */ + if (iter) { + int ret2; + + ret2 = bootflow_iter_drop_bootmeth(iter, bflow->method); + if (!ret2) { + printf("Boot method '%s' failed and will not be retried\n", + bflow->method->name); + } + } + + break; + default: + printf("Boot failed (err=%d)\n", ret); + break; + } + + return ret; +} + +int bootflow_iter_uses_blk_dev(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id != UCLASS_ETH && id != UCLASS_BOOTSTD) + return 0; + + return -ENOTSUPP; +} + +int bootflow_iter_uses_network(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id == UCLASS_ETH) + return 0; + + return -ENOTSUPP; +} + +int bootflow_iter_uses_system(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id == UCLASS_BOOTSTD) + return 0; + + return -ENOTSUPP; +} diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c index 4c71c2829ef..266bd7cb2e3 100644 --- a/boot/bootstd-uclass.c +++ b/boot/bootstd-uclass.c @@ -45,7 +45,7 @@ static void bootstd_clear_glob_(struct bootstd_priv *priv) bflow = list_first_entry(&priv->glob_head, struct bootflow, glob_node); - /* add later bootflow_remove(bflow); */ + bootflow_remove(bflow); } } diff --git a/include/bootflow.h b/include/bootflow.h index 6e9a729a9a3..c30ba042a48 100644 --- a/include/bootflow.h +++ b/include/bootflow.h @@ -151,6 +151,18 @@ void bootflow_iter_init(struct bootflow_iter *iter, int flags); */ void bootflow_iter_uninit(struct bootflow_iter *iter); +/** + * bootflow_iter_drop_bootmeth() - Remove a bootmeth from an iterator + * + * Update the iterator so that the bootmeth will not be used again while this + * iterator is in use + * + * @iter: Iterator to update + * @bmeth: Boot method to remove + */ +int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, + const struct udevice *bmeth); + /** * bootflow_scan_bootdev() - find the first bootflow in a bootdev * @@ -257,4 +269,42 @@ int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow); */ const char *bootflow_state_get_name(enum bootflow_state_t state); +/** + * bootflow_remove() - Remove a bootflow and free its memory + * + * This updates the linked lists containing the bootflow then frees it. + * + * @bflow: Bootflow to remove + */ +void bootflow_remove(struct bootflow *bflow); + +/** + * bootflow_iter_uses_blk_dev() - Check that a bootflow uses a block device + * + * This checks the bootdev in the bootflow to make sure it uses a block device + * + * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. ethernet) + */ +int bootflow_iter_uses_blk_dev(const struct bootflow_iter *iter); + +/** + * bootflow_iter_uses_network() - Check that a bootflow uses a network device + * + * This checks the bootdev in the bootflow to make sure it uses a network + * device + * + * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. MMC) + */ +int bootflow_iter_uses_network(const struct bootflow_iter *iter); + +/** + * bootflow_iter_uses_system() - Check that a bootflow uses the bootstd device + * + * This checks the bootdev in the bootflow to make sure it uses the bootstd + * device + * + * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. MMC) + */ +int bootflow_iter_uses_system(const struct bootflow_iter *iter); + #endif -- cgit v1.3.1 From 31aefaf89a5b5b259244a2ca83862e8d172a03a9 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:13 -0600 Subject: bootstd: Add an implementation of distro boot Add a bootmeth driver which handles distro boot from a disk, so we can boot a bootflow using this commonly used mechanism. In effect, this provides the same functionality as the 'sysboot' command and shares the same code. But the interface into it is via a bootmeth. For now this requires the 'pxe' command be enabled. Future work may tidy this up so that it can be used without CONFIG_CMDLINE being enabled. Signed-off-by: Simon Glass --- MAINTAINERS | 3 +- boot/Kconfig | 15 ++++++ boot/Makefile | 2 + boot/bootmeth_distro.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++ include/distro.h | 24 +++++++++ 5 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 boot/bootmeth_distro.c create mode 100644 include/distro.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 5a48b841990..d9125fca850 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -701,7 +701,8 @@ F: boot/bootflow.c F: boot/bootmeth*.c F: boot/bootstd.c F: cmd/bootdev.c -F: include/bootdev*.h +F: cmd/bootflow.c +F: include/bootdev.h F: include/bootflow.h F: include/bootmeth.h F: include/bootstd.h diff --git a/boot/Kconfig b/boot/Kconfig index 9faa55a5418..d34c31303dc 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -326,6 +326,21 @@ config BOOTSTD_FULL - support for selecting the ordering of bootdevs using the devicetree as well as the "boot_targets" environment variable +if BOOTSTD + +config BOOTMETH_DISTRO + bool "Bootdev support for distro boot" + depends on CMD_PXE + default y + help + Enables support for distro boot using bootdevs. This makes the + bootdevs look for a 'extlinux/extlinux.conf' on each filesystem + they scan. + + This provides a way to try out standard boot on an existing boot flow. + +endif + config LEGACY_IMAGE_FORMAT bool "Enable support for the legacy image format" default y if !FIT_SIGNATURE diff --git a/boot/Makefile b/boot/Makefile index ac861fd035f..6b7014b41ac 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -24,6 +24,8 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootmeth-uclass.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_DISTRO) += bootmeth_distro.o + obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o diff --git a/boot/bootmeth_distro.c b/boot/bootmeth_distro.c new file mode 100644 index 00000000000..2b41e654ade --- /dev/null +++ b/boot/bootmeth_distro.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for distro boot (syslinux boot from a block device) + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int disto_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, ulong *sizep) +{ + struct distro_info *info = ctx->userdata; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + + /* Allow up to 1GB */ + *sizep = 1 << 30; + ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, + sizep); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int distro_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_uses_blk_dev(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static int distro_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc; + const char *const *prefixes; + struct udevice *bootstd; + const char *prefix; + loff_t size; + int ret, i; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) + return log_msg_ret("std", ret); + + /* If a block device, we require a partition table */ + if (bflow->blk && !bflow->part) + return -ENOENT; + + prefixes = bootstd_get_prefixes(bootstd); + i = 0; + desc = bflow->blk ? dev_get_uclass_plat(bflow->blk) : NULL; + do { + prefix = prefixes ? prefixes[i] : NULL; + + ret = bootmeth_try_file(bflow, desc, prefix, DISTRO_FNAME); + } while (ret && prefixes && prefixes[++i]); + if (ret) + return log_msg_ret("try", ret); + size = bflow->size; + + ret = bootmeth_alloc_file(bflow, 0x10000, 1); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int distro_boot(struct udevice *dev, struct bootflow *bflow) +{ + struct cmd_tbl cmdtp = {}; /* dummy */ + struct pxe_context ctx; + struct distro_info info; + ulong addr; + int ret; + + addr = map_to_sysmem(bflow->buf); + info.dev = dev; + info.bflow = bflow; + ret = pxe_setup_ctx(&ctx, &cmdtp, disto_getfile, &info, true, + bflow->subdir); + if (ret) + return log_msg_ret("ctx", -EINVAL); + + ret = pxe_process(&ctx, addr, false); + if (ret) + return log_msg_ret("bread", -EINVAL); + + return 0; +} + +static int distro_bootmeth_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "Syslinux boot from a block device" : "syslinux"; + + return 0; +} + +static struct bootmeth_ops distro_bootmeth_ops = { + .check = distro_check, + .read_bootflow = distro_read_bootflow, + .read_file = bootmeth_common_read_file, + .boot = distro_boot, +}; + +static const struct udevice_id distro_bootmeth_ids[] = { + { .compatible = "u-boot,distro-syslinux" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_distro) = { + .name = "bootmeth_distro", + .id = UCLASS_BOOTMETH, + .of_match = distro_bootmeth_ids, + .ops = &distro_bootmeth_ops, + .bind = distro_bootmeth_bind, +}; diff --git a/include/distro.h b/include/distro.h new file mode 100644 index 00000000000..2ee145871b2 --- /dev/null +++ b/include/distro.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#ifndef __distro_h +#define __distro_h + +#define DISTRO_FNAME "extlinux/extlinux.conf" + +/** + * struct distro_info - useful information for distro_getfile() + * + * @dev: bootmethod device being used to boot + * @bflow: bootflow being booted + */ +struct distro_info { + struct udevice *dev; + struct bootflow *bflow; + struct cmd_tbl *cmdtp; +}; + +#endif -- cgit v1.3.1 From b8aa463e9ba96e66fc24e0d7cd0e07e9bc7bc839 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:14 -0600 Subject: bootstd: mmc: Add a bootdev driver Add a bootdev driver for MMC. It mostly just calls the bootdev helper function. Add a function to obtain the block device for an MMC controller. Fix up the comment for mmc_get_blk_desc() while we are here. Signed-off-by: Simon Glass --- MAINTAINERS | 1 + drivers/mmc/Makefile | 5 ++++ drivers/mmc/mmc-uclass.c | 23 ++++++++++++++++++ drivers/mmc/mmc_bootdev.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++ include/mmc.h | 12 ++++++++- 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 drivers/mmc/mmc_bootdev.c (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index d9125fca850..239fa2eeb75 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -702,6 +702,7 @@ F: boot/bootmeth*.c F: boot/bootstd.c F: cmd/bootdev.c F: cmd/bootflow.c +F: drivers/mmc/mmc_bootdev.c F: include/bootdev.h F: include/bootflow.h F: include/bootmeth.h diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 17ebc04203e..96275093022 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -5,6 +5,11 @@ obj-y += mmc.o obj-$(CONFIG_$(SPL_)DM_MMC) += mmc-uclass.o + +ifdef CONFIG_$(SPL_TPL_)DM_MMC +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += mmc_bootdev.o +endif + obj-$(CONFIG_$(SPL_)MMC_WRITE) += mmc_write.o obj-$(CONFIG_MMC_PWRSEQ) += mmc-pwrseq.o obj-$(CONFIG_MMC_SDHCI_ADMA_HELPERS) += sdhci-adma.o diff --git a/drivers/mmc/mmc-uclass.c b/drivers/mmc/mmc-uclass.c index 57da788ad80..688bdc06d42 100644 --- a/drivers/mmc/mmc-uclass.c +++ b/drivers/mmc/mmc-uclass.c @@ -8,6 +8,7 @@ #define LOG_CATEGORY UCLASS_MMC #include +#include #include #include #include @@ -315,6 +316,20 @@ int mmc_get_next_devnum(void) return blk_find_max_devnum(IF_TYPE_MMC); } +int mmc_get_blk(struct udevice *dev, struct udevice **blkp) +{ + struct udevice *blk; + int ret; + + device_find_first_child_by_uclass(dev, UCLASS_BLK, &blk); + ret = device_probe(blk); + if (ret) + return ret; + *blkp = blk; + + return 0; +} + struct blk_desc *mmc_get_blk_desc(struct mmc *mmc) { struct blk_desc *desc; @@ -406,6 +421,10 @@ int mmc_bind(struct udevice *dev, struct mmc *mmc, const struct mmc_config *cfg) mmc->cfg = cfg; mmc->priv = dev; + ret = bootdev_setup_for_dev(dev, "mmc_bootdev"); + if (ret) + return log_msg_ret("bootdev", ret); + /* the following chunk was from mmc_register() */ /* Setup dsr related values */ @@ -424,12 +443,16 @@ int mmc_bind(struct udevice *dev, struct mmc *mmc, const struct mmc_config *cfg) int mmc_unbind(struct udevice *dev) { struct udevice *bdev; + int ret; device_find_first_child_by_uclass(dev, UCLASS_BLK, &bdev); if (bdev) { device_remove(bdev, DM_REMOVE_NORMAL); device_unbind(bdev); } + ret = bootdev_unbind_dev(dev); + if (ret) + return log_msg_ret("bootdev", ret); return 0; } diff --git a/drivers/mmc/mmc_bootdev.c b/drivers/mmc/mmc_bootdev.c new file mode 100644 index 00000000000..b4f41fb3a67 --- /dev/null +++ b/drivers/mmc/mmc_bootdev.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootdevice for MMC + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include + +static int mmc_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + struct udevice *mmc_dev = dev_get_parent(dev); + struct udevice *blk; + int ret; + + ret = mmc_get_blk(mmc_dev, &blk); + /* + * If there is no media, indicate that no more partitions should be + * checked + */ + if (ret == -EOPNOTSUPP) + ret = -ESHUTDOWN; + if (ret) + return log_msg_ret("blk", ret); + assert(blk); + ret = bootdev_find_in_blk(dev, blk, iter, bflow); + if (ret) + return log_msg_ret("find", ret); + + return 0; +} + +static int mmc_bootdev_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + ucp->prio = BOOTDEVP_0_INTERNAL_FAST; + + return 0; +} + +struct bootdev_ops mmc_bootdev_ops = { + .get_bootflow = mmc_get_bootflow, +}; + +static const struct udevice_id mmc_bootdev_ids[] = { + { .compatible = "u-boot,bootdev-mmc" }, + { } +}; + +U_BOOT_DRIVER(mmc_bootdev) = { + .name = "mmc_bootdev", + .id = UCLASS_BOOTDEV, + .ops = &mmc_bootdev_ops, + .bind = mmc_bootdev_bind, + .of_match = mmc_bootdev_ids, +}; diff --git a/include/mmc.h b/include/mmc.h index 6bdcce881dd..9b4dc683110 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -956,10 +956,20 @@ int mmc_get_env_dev(void); * mmc_get_blk_desc() - Get the block descriptor for an MMC device * * @mmc: MMC device - * Return: block device if found, else NULL + * Return: block descriptor if found, else NULL */ struct blk_desc *mmc_get_blk_desc(struct mmc *mmc); +/** + * mmc_get_blk() - Get the block device for an MMC device + * + * @dev: MMC device + * @blkp: Returns pointer to probed block device on sucesss + * + * Return: 0 on success, -ve on error + */ +int mmc_get_blk(struct udevice *dev, struct udevice **blkp); + /** * mmc_send_ext_csd() - read the extended CSD register * -- cgit v1.3.1 From fb1451bec2a54046eeb541d77ba0e5eb55302d46 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 24 Apr 2022 23:31:24 -0600 Subject: bootstd: Add tests for bootstd including all uclasses Add a set of combined tests for the bootdev, bootflow and bootmeth commands, along with associated functionality. Expand the sandbox console-recording limit so that these can work. These tests rely on a filesystem script which is not yet added to the Python tests. It is included here as a shell script. Signed-off-by: Simon Glass --- MAINTAINERS | 1 + arch/sandbox/dts/test.dts | 18 ++ configs/sandbox_defconfig | 3 +- configs/sandbox_flattree_defconfig | 3 +- include/test/suites.h | 2 + test/Makefile | 1 + test/boot/Makefile | 5 + test/boot/bootdev.c | 223 +++++++++++++++++++++ test/boot/bootflow.c | 400 +++++++++++++++++++++++++++++++++++++ test/boot/bootmeth.c | 122 +++++++++++ test/boot/bootstd_common.c | 35 ++++ test/boot/bootstd_common.h | 27 +++ test/cmd_ut.c | 7 + 13 files changed, 845 insertions(+), 2 deletions(-) create mode 100644 test/boot/Makefile create mode 100644 test/boot/bootdev.c create mode 100644 test/boot/bootflow.c create mode 100644 test/boot/bootmeth.c create mode 100644 test/boot/bootstd_common.c create mode 100644 test/boot/bootstd_common.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 77a34082249..963ec91c891 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -708,6 +708,7 @@ F: include/bootflow.h F: include/bootmeth.h F: include/bootstd.h F: net/eth_bootdevice.c +F: test/boot/ BTRFS M: Marek Behun diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 5b38ee4a5f9..a8a86bc7155 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -74,6 +74,21 @@ }; }; + bootstd { + compatible = "u-boot,boot-std"; + + filename-prefixes = "/", "/boot/"; + bootdev-order = "mmc2", "mmc1"; + + syslinux { + compatible = "u-boot,distro-syslinux"; + }; + + efi { + compatible = "u-boot,distro-efi"; + }; + }; + reboot-mode0 { compatible = "reboot-mode-gpio"; gpios = <&gpio_c 0 GPIO_ACTIVE_HIGH>, <&gpio_c 1 GPIO_ACTIVE_HIGH>; @@ -891,10 +906,13 @@ non-removable; }; + /* This is used for the bootdev tests */ mmc1 { compatible = "sandbox,mmc"; + filename = "mmc1.img"; }; + /* This is used for the fastboot tests */ mmc0 { compatible = "sandbox,mmc"; }; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 14d7af4db28..fe8ea4626d9 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -28,7 +28,7 @@ CONFIG_AUTOBOOT_STOP_STR_CRYPT="$5$rounds=640000$HrpE65IkB8CM5nCL$BKT3QdF98Bo8fJ CONFIG_IMAGE_PRE_LOAD=y CONFIG_IMAGE_PRE_LOAD_SIG=y CONFIG_CONSOLE_RECORD=y -CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000 +CONFIG_CONSOLE_RECORD_OUT_SIZE=0x6000 CONFIG_PRE_CONSOLE_BUFFER=y CONFIG_LOG=y CONFIG_DISPLAY_BOARDINFO_LATE=y @@ -120,6 +120,7 @@ CONFIG_ENV_IS_IN_EXT4=y CONFIG_ENV_EXT4_INTERFACE="host" CONFIG_ENV_EXT4_DEVICE_AND_PART="0:0" CONFIG_ENV_IMPORT_FDT=y +# CONFIG_BOOTDEV_ETH is not set CONFIG_BOOTP_SEND_HOSTNAME=y CONFIG_NETCONSOLE=y CONFIG_IP_DEFRAG=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index ded16af9f51..80a4be47eba 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -17,7 +17,7 @@ CONFIG_BOOTSTAGE_FDT=y CONFIG_BOOTSTAGE_STASH=y CONFIG_BOOTSTAGE_STASH_SIZE=0x4096 CONFIG_CONSOLE_RECORD=y -CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000 +CONFIG_CONSOLE_RECORD_OUT_SIZE=0x6000 CONFIG_DISPLAY_BOARDINFO_LATE=y CONFIG_CMD_CPU=y CONFIG_CMD_LICENSE=y @@ -72,6 +72,7 @@ CONFIG_ENV_IS_NOWHERE=y CONFIG_ENV_IS_IN_EXT4=y CONFIG_ENV_EXT4_INTERFACE="host" CONFIG_ENV_EXT4_DEVICE_AND_PART="0:0" +# CONFIG_BOOTDEV_ETH is not set CONFIG_BOOTP_SEND_HOSTNAME=y CONFIG_NETCONSOLE=y CONFIG_IP_DEFRAG=y diff --git a/include/test/suites.h b/include/test/suites.h index 6553a765c6a..ee6858a802a 100644 --- a/include/test/suites.h +++ b/include/test/suites.h @@ -29,6 +29,8 @@ int cmd_ut_category(const char *name, const char *prefix, int do_ut_addrmap(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); +int do_ut_bootstd(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]); int do_ut_bloblist(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_common(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); diff --git a/test/Makefile b/test/Makefile index b3b2902e2e7..abd605a4351 100644 --- a/test/Makefile +++ b/test/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_UT_TIME) += time_ut.o obj-y += ut.o ifeq ($(CONFIG_SPL_BUILD),) +obj-$(CONFIG_UNIT_TEST) += boot/ obj-$(CONFIG_UNIT_TEST) += common/ obj-$(CONFIG_UNIT_TEST) += lib/ obj-y += log/ diff --git a/test/boot/Makefile b/test/boot/Makefile new file mode 100644 index 00000000000..1730792b5fa --- /dev/null +++ b/test/boot/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2021 Google LLC + +obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o diff --git a/test/boot/bootdev.c b/test/boot/bootdev.c new file mode 100644 index 00000000000..1c2a79fb108 --- /dev/null +++ b/test/boot/bootdev.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for bootdev functions. All start with 'bootdev' + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bootstd_common.h" + +/* Allow reseting the USB-started flag */ +extern char usb_started; + +/* Check 'bootdev list' command */ +static int bootdev_test_cmd_list(struct unit_test_state *uts) +{ + int probed; + + console_record_reset_enable(); + for (probed = 0; probed < 2; probed++) { + int probe_ch = probed ? '+' : ' '; + + ut_assertok(run_command(probed ? "bootdev list -p" : + "bootdev list", 0)); + ut_assert_nextline("Seq Probed Status Uclass Name"); + ut_assert_nextlinen("---"); + ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 0, probe_ch, "OK", + "mmc", "mmc2.bootdev"); + ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 1, probe_ch, "OK", + "mmc", "mmc1.bootdev"); + ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 2, probe_ch, "OK", + "mmc", "mmc0.bootdev"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(3 bootdevs)"); + ut_assert_console_end(); + } + + return 0; +} +BOOTSTD_TEST(bootdev_test_cmd_list, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootdev select' and 'info' commands */ +static int bootdev_test_cmd_select(struct unit_test_state *uts) +{ + struct bootstd_priv *std; + + /* get access to the CLI's cur_bootdev */ + ut_assertok(bootstd_get_priv(&std)); + + console_record_reset_enable(); + ut_asserteq(1, run_command("bootdev info", 0)); + ut_assert_nextlinen("Please use"); + ut_assert_console_end(); + + /* select by sequence */ + ut_assertok(run_command("bootdev select 0", 0)); + ut_assert_console_end(); + + ut_assertok(run_command("bootdev info", 0)); + ut_assert_nextline("Name: mmc2.bootdev"); + ut_assert_nextline("Sequence: 0"); + ut_assert_nextline("Status: Probed"); + ut_assert_nextline("Uclass: mmc"); + ut_assert_nextline("Bootflows: 0 (0 valid)"); + ut_assert_console_end(); + + /* select by bootdev name */ + ut_assertok(run_command("bootdev select mmc1.bootdev", 0)); + ut_assert_console_end(); + ut_assertnonnull(std->cur_bootdev); + ut_asserteq_str("mmc1.bootdev", std->cur_bootdev->name); + + /* select by bootdev label*/ + ut_assertok(run_command("bootdev select mmc1", 0)); + ut_assert_console_end(); + ut_assertnonnull(std->cur_bootdev); + ut_asserteq_str("mmc1.bootdev", std->cur_bootdev->name); + + /* deselect */ + ut_assertok(run_command("bootdev select", 0)); + ut_assert_console_end(); + ut_assertnull(std->cur_bootdev); + + ut_asserteq(1, run_command("bootdev info", 0)); + ut_assert_nextlinen("Please use"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootdev_test_cmd_select, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check bootdev labels */ +static int bootdev_test_labels(struct unit_test_state *uts) +{ + struct udevice *dev, *media; + + ut_assertok(bootdev_find_by_label("mmc2", &dev)); + ut_asserteq(UCLASS_BOOTDEV, device_get_uclass_id(dev)); + media = dev_get_parent(dev); + ut_asserteq(UCLASS_MMC, device_get_uclass_id(media)); + ut_asserteq_str("mmc2", media->name); + + /* Check invalid uclass */ + ut_asserteq(-EINVAL, bootdev_find_by_label("fred0", &dev)); + + /* Check unknown sequence number */ + ut_asserteq(-ENOENT, bootdev_find_by_label("mmc6", &dev)); + + return 0; +} +BOOTSTD_TEST(bootdev_test_labels, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check bootdev ordering with the bootdev-order property */ +static int bootdev_test_order(struct unit_test_state *uts) +{ + struct bootflow_iter iter; + struct bootflow bflow; + + /* + * First try the order set by the bootdev-order property + * Like all sandbox unit tests this relies on the devicetree setting up + * the required devices: + * + * mmc0 - nothing connected + * mmc1 - connected to mmc1.img file + * mmc2 - nothing connected + */ + ut_assertok(env_set("boot_targets", NULL)); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(2, iter.num_devs); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("mmc1.bootdev", iter.dev_order[1]->name); + bootflow_iter_uninit(&iter); + + /* Use the environment variable to override it */ + ut_assertok(env_set("boot_targets", "mmc1 mmc2")); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(2, iter.num_devs); + ut_asserteq_str("mmc1.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[1]->name); + bootflow_iter_uninit(&iter); + + /* + * Now drop both orderings, to check the default (prioriy/sequence) + * ordering + */ + ut_assertok(env_set("boot_targets", NULL)); + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(3, iter.num_devs); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("mmc1.bootdev", iter.dev_order[1]->name); + ut_asserteq_str("mmc0.bootdev", iter.dev_order[2]->name); + + /* + * Check that adding aliases for the bootdevs works. We just fake it by + * setting the sequence numbers directly. + */ + iter.dev_order[0]->seq_ = 0; + iter.dev_order[1]->seq_ = 3; + iter.dev_order[2]->seq_ = 2; + bootflow_iter_uninit(&iter); + + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(3, iter.num_devs); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("mmc0.bootdev", iter.dev_order[1]->name); + ut_asserteq_str("mmc1.bootdev", iter.dev_order[2]->name); + bootflow_iter_uninit(&iter); + + return 0; +} +BOOTSTD_TEST(bootdev_test_order, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check bootdev ordering with the uclass priority */ +static int bootdev_test_prio(struct unit_test_state *uts) +{ + struct bootdev_uc_plat *ucp; + struct bootflow_iter iter; + struct bootflow bflow; + struct udevice *blk; + + /* Start up USB which gives us three additional bootdevs */ + usb_started = false; + ut_assertok(run_command("usb start", 0)); + + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + /* 3 MMC and 3 USB bootdevs: MMC should come before USB */ + console_record_reset_enable(); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(6, iter.num_devs); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("usb_mass_storage.lun0.bootdev", + iter.dev_order[3]->name); + + ut_assertok(bootdev_get_sibling_blk(iter.dev_order[3], &blk)); + ut_asserteq_str("usb_mass_storage.lun0", blk->name); + + /* adjust the priority of the first USB bootdev to the highest */ + ucp = dev_get_uclass_plat(iter.dev_order[3]); + ucp->prio = 1; + + bootflow_iter_uninit(&iter); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(6, iter.num_devs); + ut_asserteq_str("usb_mass_storage.lun0.bootdev", + iter.dev_order[0]->name); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[1]->name); + + return 0; +} +BOOTSTD_TEST(bootdev_test_prio, UT_TESTF_DM | UT_TESTF_SCAN_FDT); diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c new file mode 100644 index 00000000000..1ebb789e97b --- /dev/null +++ b/test/boot/bootflow.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for bootdev functions. All start with 'bootdev' + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "bootstd_common.h" + +/* Check 'bootflow scan/list' commands */ +static int bootflow_cmd(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootdev select 1", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow scan -l", 0)); + ut_assert_nextline("Scanning for bootflows in bootdev 'mmc1.bootdev'"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow list", 0)); + ut_assert_nextline("Showing bootflows for bootdev 'mmc1.bootdev'"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow scan' with a name / label / seq */ +static int bootflow_cmd_label(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootflow scan -l mmc1", 0)); + ut_assert_nextline("Scanning for bootflows in bootdev 'mmc1.bootdev'"); + ut_assert_skip_to_line("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow scan -l mmc0.bootdev", 0)); + ut_assert_nextline("Scanning for bootflows in bootdev 'mmc0.bootdev'"); + ut_assert_skip_to_line("(0 bootflows, 0 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow scan -l 0", 0)); + ut_assert_nextline("Scanning for bootflows in bootdev 'mmc2.bootdev'"); + ut_assert_skip_to_line("(0 bootflows, 0 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_label, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow scan/list' commands using all bootdevs */ +static int bootflow_cmd_glob(struct unit_test_state *uts) +{ + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + console_record_reset_enable(); + ut_assertok(run_command("bootflow scan -l", 0)); + ut_assert_nextline("Scanning for bootflows in all bootdevs"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline("Scanning bootdev 'mmc2.bootdev':"); + ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':"); + ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextline("Scanning bootdev 'mmc0.bootdev':"); + ut_assert_nextline("No more bootdevs"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow list", 0)); + ut_assert_nextline("Showing all bootflows"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_glob, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow scan -e' */ +static int bootflow_cmd_scan_e(struct unit_test_state *uts) +{ + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + console_record_reset_enable(); + ut_assertok(run_command("bootflow scan -ale", 0)); + ut_assert_nextline("Scanning for bootflows in all bootdevs"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline("Scanning bootdev 'mmc2.bootdev':"); + ut_assert_nextline(" 0 syslinux media mmc 0 mmc2.bootdev.whole "); + ut_assert_nextline(" ** No partition found, err=-93"); + ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole "); + ut_assert_nextline(" ** No partition found, err=-93"); + + ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':"); + ut_assert_nextline(" 2 syslinux media mmc 0 mmc1.bootdev.whole "); + ut_assert_nextline(" ** No partition found, err=-2"); + ut_assert_nextline(" 3 efi media mmc 0 mmc1.bootdev.whole "); + ut_assert_nextline(" ** No partition found, err=-2"); + ut_assert_nextline(" 4 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextline(" 5 efi fs mmc 1 mmc1.bootdev.part_1 efi/boot/bootsbox.efi"); + + ut_assert_skip_to_line("Scanning bootdev 'mmc0.bootdev':"); + ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole "); + ut_assert_nextline(" ** No partition found, err=-93"); + ut_assert_nextline("No more bootdevs"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(64 bootflows, 1 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow list", 0)); + ut_assert_nextline("Showing all bootflows"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 syslinux media mmc 0 mmc2.bootdev.whole "); + ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole "); + ut_assert_skip_to_line(" 4 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole "); + ut_assert_nextlinen("---"); + ut_assert_nextline("(64 bootflows, 1 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_scan_e, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow info' */ +static int bootflow_cmd_info(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootdev select 1", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow scan", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow select 0", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow info", 0)); + ut_assert_nextline("Name: mmc1.bootdev.part_1"); + ut_assert_nextline("Device: mmc1.bootdev"); + ut_assert_nextline("Block dev: mmc1.blk"); + ut_assert_nextline("Method: syslinux"); + ut_assert_nextline("State: ready"); + ut_assert_nextline("Partition: 1"); + ut_assert_nextline("Subdir: (none)"); + ut_assert_nextline("Filename: /extlinux/extlinux.conf"); + ut_assert_nextlinen("Buffer: "); + ut_assert_nextline("Size: 253 (595 bytes)"); + ut_assert_nextline("Error: 0"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow info -d", 0)); + ut_assert_nextline("Name: mmc1.bootdev.part_1"); + ut_assert_skip_to_line("Error: 0"); + ut_assert_nextline("Contents:"); + ut_assert_nextline("%s", ""); + ut_assert_nextline("# extlinux.conf generated by appliance-creator"); + ut_assert_skip_to_line(" initrd /initramfs-5.3.7-301.fc31.armv7hl.img"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_info, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow scan -b' to boot the first available bootdev */ +static int bootflow_scan_boot(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootflow scan -b", 0)); + ut_assert_nextline( + "** Booting bootflow 'mmc1.bootdev.part_1' with syslinux"); + ut_assert_nextline("Ignoring unknown command: ui"); + + /* + * We expect it to get through to boot although sandbox always returns + * -EFAULT as it cannot actually boot the kernel + */ + ut_assert_skip_to_line("sandbox: continuing, as we cannot run Linux"); + ut_assert_nextline("Boot failed (err=-14)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_scan_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check iterating through available bootflows */ +static int bootflow_iter(struct unit_test_state *uts) +{ + struct bootflow_iter iter; + struct bootflow bflow; + + bootstd_clear_glob(); + + /* The first device is mmc2.bootdev which has no media */ + ut_asserteq(-EPROTONOSUPPORT, + bootflow_scan_first(&iter, BOOTFLOWF_ALL, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(0, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0, iter.max_part); + ut_asserteq_str("syslinux", iter.method->name); + ut_asserteq(0, bflow.err); + + /* + * This shows MEDIA even though there is none, since int + * bootdev_find_in_blk() we call part_get_info() which returns + * -EPROTONOSUPPORT. Ideally it would return -EEOPNOTSUPP and we would + * know. + */ + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + + ut_asserteq(-EPROTONOSUPPORT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(1, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0, iter.max_part); + ut_asserteq_str("efi", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + /* The next device is mmc1.bootdev - at first we use the whole device */ + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(0, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("syslinux", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(1, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("efi", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + /* Then more to partition 1 where we find something */ + ut_assertok(bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(0, iter.cur_method); + ut_asserteq(1, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("syslinux", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_READY, bflow.state); + bootflow_free(&bflow); + + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(1, iter.cur_method); + ut_asserteq(1, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("efi", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_FS, bflow.state); + bootflow_free(&bflow); + + /* Then more to partition 2 which doesn't exist */ + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(0, iter.cur_method); + ut_asserteq(2, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("syslinux", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + bootflow_iter_uninit(&iter); + + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_iter, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check using the system bootdev */ +static int bootflow_system(struct unit_test_state *uts) +{ + struct udevice *bootstd, *dev; + + /* Add the EFI bootmgr driver */ + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + ut_assertok(device_bind_driver(bootstd, "bootmeth_efi_mgr", "bootmgr", + &dev)); + + /* Add the system bootdev that it uses */ + ut_assertok(device_bind_driver(bootstd, "system_bootdev", + "system-bootdev", &dev)); + + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + /* We should get a single 'bootmgr' method right at the end */ + bootstd_clear_glob(); + console_record_reset_enable(); + ut_assertok(run_command("bootflow scan -l", 0)); + ut_assert_skip_to_line(" 1 bootmgr ready bootstd 0 "); + ut_assert_nextline("No more bootdevs"); + ut_assert_skip_to_line("(2 bootflows, 2 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_system, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check disabling a bootmethod if it requests it */ +static int bootflow_iter_disable(struct unit_test_state *uts) +{ + struct udevice *bootstd, *dev; + struct bootflow_iter iter; + struct bootflow bflow; + int i; + + /* Add the EFI bootmgr driver */ + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + ut_assertok(device_bind_driver(bootstd, "bootmeth_sandbox", "sandbox", + &dev)); + + /* Add the system bootdev that it uses */ + ut_assertok(device_bind_driver(bootstd, "system_bootdev", + "system-bootdev", &dev)); + + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + bootstd_clear_glob(); + ut_assertok(run_command("bootflow scan -lb", 0)); + + /* Try to boot the bootmgr flow, which will fail */ + console_record_reset_enable(); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(3, iter.num_methods); + ut_asserteq_str("sandbox", iter.method->name); + ut_asserteq(-ENOTSUPP, bootflow_run_boot(&iter, &bflow)); + + ut_assert_skip_to_line("Boot method 'sandbox' failed and will not be retried"); + ut_assert_console_end(); + + /* Check that the sandbox bootmeth has been removed */ + ut_asserteq(2, iter.num_methods); + for (i = 0; i < iter.num_methods; i++) + ut_assert(strcmp("sandbox", iter.method_order[i]->name)); + + return 0; +} +BOOTSTD_TEST(bootflow_iter_disable, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow boot' to boot a selected bootflow */ +static int bootflow_cmd_boot(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootdev select 1", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow scan", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow select 0", 0)); + ut_assert_console_end(); + ut_asserteq(1, run_command("bootflow boot", 0)); + ut_assert_nextline( + "** Booting bootflow 'mmc1.bootdev.part_1' with syslinux"); + ut_assert_nextline("Ignoring unknown command: ui"); + + /* + * We expect it to get through to boot although sandbox always returns + * -EFAULT as it cannot actually boot the kernel + */ + ut_assert_skip_to_line("sandbox: continuing, as we cannot run Linux"); + ut_assert_nextline("Boot failed (err=-14)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT); diff --git a/test/boot/bootmeth.c b/test/boot/bootmeth.c new file mode 100644 index 00000000000..07776c5368d --- /dev/null +++ b/test/boot/bootmeth.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for bootdev functions. All start with 'bootdev' + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include "bootstd_common.h" + +/* Check 'bootmeth list' command */ +static int bootmeth_cmd_list(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootmeth list", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device"); + ut_assert_nextline(" 1 1 efi EFI boot from an .efi file"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootmeth_cmd_list, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootmeth order' command */ +static int bootmeth_cmd_order(struct unit_test_state *uts) +{ + /* Select just one bootmethod */ + console_record_reset_enable(); + ut_assertok(run_command("bootmeth order syslinux", 0)); + ut_assert_console_end(); + ut_assertnonnull(env_get("bootmeths")); + ut_asserteq_str("syslinux", env_get("bootmeths")); + + /* Only that one should be listed */ + ut_assertok(run_command("bootmeth list", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootmeth)"); + ut_assert_console_end(); + + /* Check the -a flag, efi should show as not in the order ("-") */ + ut_assertok(run_command("bootmeth list -a", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device"); + ut_assert_nextline(" - 1 efi EFI boot from an .efi file"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assert_console_end(); + + /* Check the -a flag with the reverse order */ + ut_assertok(run_command("bootmeth order \"efi syslinux\"", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootmeth list -a", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 1 0 syslinux Syslinux boot from a block device"); + ut_assert_nextline(" 0 1 efi EFI boot from an .efi file"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assert_console_end(); + + /* Now reset the order to empty, which should show all of them again */ + ut_assertok(run_command("bootmeth order", 0)); + ut_assert_console_end(); + ut_assertnull(env_get("bootmeths")); + ut_assertok(run_command("bootmeth list", 0)); + ut_assert_skip_to_line("(2 bootmeths)"); + + /* Try reverse order */ + ut_assertok(run_command("bootmeth order \"efi syslinux\"", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootmeth list", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 1 efi EFI boot from an .efi file"); + ut_assert_nextline(" 1 0 syslinux Syslinux boot from a block device"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assertnonnull(env_get("bootmeths")); + ut_asserteq_str("efi syslinux", env_get("bootmeths")); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootmeth_cmd_order, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootmeths' env var */ +static int bootmeth_env(struct unit_test_state *uts) +{ + struct bootstd_priv *std; + + ut_assertok(bootstd_get_priv(&std)); + + /* Select just one bootmethod */ + console_record_reset_enable(); + ut_assertok(env_set("bootmeths", "syslinux")); + ut_asserteq(1, std->bootmeth_count); + + /* Select an invalid bootmethod */ + ut_asserteq(1, run_command("setenv bootmeths fred", 0)); + ut_assert_nextline("Unknown bootmeth 'fred'"); + ut_assert_nextlinen("## Error inserting"); + ut_assert_console_end(); + + ut_assertok(env_set("bootmeths", "efi syslinux")); + ut_asserteq(2, std->bootmeth_count); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootmeth_env, UT_TESTF_DM | UT_TESTF_SCAN_FDT); diff --git a/test/boot/bootstd_common.c b/test/boot/bootstd_common.c new file mode 100644 index 00000000000..05347d87106 --- /dev/null +++ b/test/boot/bootstd_common.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for bootdev functions. All start with 'bootdev' + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include "bootstd_common.h" + +int bootstd_test_drop_bootdev_order(struct unit_test_state *uts) +{ + struct bootstd_priv *priv; + struct udevice *bootstd; + + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + priv = dev_get_priv(bootstd); + priv->bootdev_order = NULL; + + return 0; +} + +int do_ut_bootstd(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct unit_test *tests = UNIT_TEST_SUITE_START(bootstd_test); + const int n_ents = UNIT_TEST_SUITE_COUNT(bootstd_test); + + return cmd_ut_category("bootstd", "bootstd_test_", + tests, n_ents, argc, argv); +} diff --git a/test/boot/bootstd_common.h b/test/boot/bootstd_common.h new file mode 100644 index 00000000000..676ef0a57f9 --- /dev/null +++ b/test/boot/bootstd_common.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Common header file for bootdev, bootflow, bootmeth tests + * + * Copyright 2021 Google LLC + * Written by Simon Glass + */ + +#ifndef __bootstd_common_h +#define __bootstd_common_h + +/* Declare a new bootdev test */ +#define BOOTSTD_TEST(_name, _flags) \ + UNIT_TEST(_name, _flags, bootstd_test) + +struct unit_test_state; + +/** + * bootstd_test_drop_bootdev_order() - Remove the existing boot order + * + * Drop the boot order so that all bootdevs are used in their alias order + * + * @uts: Unit test state to use for ut_assert...() functions + */ +int bootstd_test_drop_bootdev_order(struct unit_test_state *uts); + +#endif diff --git a/test/cmd_ut.c b/test/cmd_ut.c index 90b260f72d6..67a13ee32b8 100644 --- a/test/cmd_ut.c +++ b/test/cmd_ut.c @@ -28,6 +28,10 @@ int cmd_ut_category(const char *name, const char *prefix, static struct cmd_tbl cmd_ut_sub[] = { U_BOOT_CMD_MKENT(all, CONFIG_SYS_MAXARGS, 1, do_ut_all, "", ""), +#ifdef CONFIG_BOOTSTD + U_BOOT_CMD_MKENT(bootstd, CONFIG_SYS_MAXARGS, 1, do_ut_bootstd, + "", ""), +#endif U_BOOT_CMD_MKENT(common, CONFIG_SYS_MAXARGS, 1, do_ut_common, "", ""), #if defined(CONFIG_UT_DM) U_BOOT_CMD_MKENT(dm, CONFIG_SYS_MAXARGS, 1, do_ut_dm, "", ""), @@ -115,6 +119,9 @@ static char ut_help_text[] = "ut bloblist - Test bloblist implementation\n" "ut compression - Test compressors and bootm decompression\n" #endif +#ifdef CONFIG_BOOTSTD + "ut bootstd - Test standard boot implementation\n" +#endif #ifdef CONFIG_UT_DM "ut dm [test-name]\n" #endif -- cgit v1.3.1