Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: hwinfo: shell command "hwinfo devid" output ignores endianness #23444

Closed
sslupsky opened this issue Mar 13, 2020 · 13 comments · Fixed by #24203
Closed

drivers: hwinfo: shell command "hwinfo devid" output ignores endianness #23444

sslupsky opened this issue Mar 13, 2020 · 13 comments · Fixed by #24203
Assignees
Labels
area: Drivers bug The issue is a bug, or the PR is fixing a bug priority: low Low impact/importance bug

Comments

@sslupsky
Copy link
Contributor

Describe the bug
When using the "hwinfo devid" shell command to display the device id, the command outputs the device id but the bytes of each word comprising the ID appear to print in the wrong order. I believe this is because the print statements do not appear to consider endianness.

To Reproduce
Steps to reproduce the behavior:
Compile app with CONFIG_HWINFO and CONFIG_HWINFO_SHELL

Step A: From main on sam0 arch, print the device id:

u32_t dev_id[4];
length = hwinfo_get_device_id((u8_t *) dev_id, sizeof(dev_id));
LOG_INF("Device ID: 0x%08x%08x%08x%08x", dev_id[0], dev_id[1], dev_id[2], dev_id[3]);

Step B: Invoke the shell command from the console:
hwinfo devid

Step C: Compare results.

Expected behavior
Step A on sam0 produces:
Device ID: 0x2f89032e5050323339202020ff0a0c0f

Step B on sam0 produces:
Length: 16
ID: 0x2e03892f33325050202020390f0c0aff

Comparison shows the 32 bit words that comprise the ID are in the correct order. However, the 4 bytes that comprise each word are reversed.

Impact
Minor annoyance but could be a major issue for anyone using the ID as a UID for something like a firmware update, networking, etc.

Screenshots or console output
The code that outputs the id is:

for (i = 0 ; i < length ; i++) {
shell_fprintf(shell, SHELL_NORMAL, "%02x", dev_id[i]);
}

The SAMD21 data sheet describes the ID as follows:
Each device has a unique 128-bit serial number which is a concatenation of four 32-bit words

From the datasheet description, does this suggest the ID should be output using Big Endian format?

@sslupsky sslupsky added the bug The issue is a bug, or the PR is fixing a bug label Mar 13, 2020
@alexanderwachter
Copy link
Member

IMO it is not an issue. The Zephyr device-id is a sequence of bytes. We make a 1:1 copy from the registers in the flash module. You should interpret the device ID as a byte sequence because other devices don't interpret them as u32_t.

@sslupsky
Copy link
Contributor Author

I agree with your assertion that the device ID should be interpreted as a byte sequence. However, that is not how the data is being read from the hardware. Rather, the bytes are read from the registers as integer values (u32_t) which inherently have endianness and then cast as a sequence of byte which potentially breaks the endianness of the integers.

Here are a couple examples of this taken from the sam0 and nordic drivers:

struct sam0_uid {
u32_t id[4];
};
ssize_t z_impl_hwinfo_get_device_id(u8_t *buffer, size_t length)
{
struct sam0_uid dev_id;
dev_id.id[0] = *(const u32_t *) DT_INST_0_ATMEL_SAM0_ID_BASE_ADDRESS_0;
dev_id.id[1] = *(const u32_t *) DT_INST_0_ATMEL_SAM0_ID_BASE_ADDRESS_1;
dev_id.id[2] = *(const u32_t *) DT_INST_0_ATMEL_SAM0_ID_BASE_ADDRESS_2;
dev_id.id[3] = *(const u32_t *) DT_INST_0_ATMEL_SAM0_ID_BASE_ADDRESS_3;

struct nrf_uid {
u32_t id[2];
};
ssize_t z_impl_hwinfo_get_device_id(u8_t *buffer, size_t length)
{
struct nrf_uid dev_id;
dev_id.id[0] = nrf_ficr_deviceid_get(NRF_FICR, 0);
dev_id.id[1] = nrf_ficr_deviceid_get(NRF_FICR, 1);

The data structure receiving contents of the hardware registers is declared as a struct with u32_t members. Each one of those members has endianness. When you simply copy them using memcpy, endianness is not accounted for. In this case, ARM is little endian so the words are byte reversed.

@alexanderwachter
Copy link
Member

Using memcpy on the struct just makes a 1:1 copy. It does not change the endianness. The registers are interpreted as they were in a contiguous memory region somewhere in the registers.
You can choose how to interpret them, for example, pass a struct that is the same as in the driver and then you will interpret them with endianness. IMO we should not make an assumption on how to interpret the bytes in the shell.

@sslupsky
Copy link
Contributor Author

Correct, memcpy makes a copy of the struct. However, the data types that comprise the struct have endianness.

@carlescufi
Copy link
Member

@alexanderwachter I think @sslupsky is right, the fields should be in the CPU endianness, will you send a patch? or @sslupsky would you consider sending one?

@pabigot
Copy link
Collaborator

pabigot commented Apr 7, 2020

@carlescufi I think that if the vendor has a preferred expression of the hardware identifier as formed from the raw data that should be followed. IOW if the hardware identifier for Nordic is low-word then high-word little-endian, maybe there's some swapping to be done to get an octet sequence that matches the intended one, e.g. for Bluetooth addresses.

I agree with @alexanderwachter that a memcpy of the raw bytes from the starting address is a legitimate interpretation of the hardware id, unless the vendor specifies that the value has a canonical representation. I don't know how Atmel expects people to interpret the hardware identifier values.

But for the purposes of this API the interpretation should be a sequence of octets. It should not be reversed when creating a representation, as noted in #24103.

@sslupsky
Copy link
Contributor Author

sslupsky commented Apr 8, 2020

I agree with @alexanderwachter that a memcpy of the raw bytes from the starting address is a legitimate interpretation of the hardware id, unless the vendor specifies that the value has a canonical representation.

@pabigot My point is what you described is not what is happening here. The driver does not use memcpy to read the raw bytes from the starting address. It first reads the data as u32_t and then uses memcpy to copy the data. This approach ensures there is endianness in the data.

@pabigot
Copy link
Collaborator

pabigot commented Apr 8, 2020

@pabigot My point is what you described is not what is happening here. The driver does not use memcpy to read the raw bytes from the starting address. It first reads the data as u32_t and then uses memcpy to copy the data. This approach ensures there is endianness in the data.

If it reads and writes u32_t in CPU endianness and then copies the bytes, it's equivalent to copying the bytes without involving u32_t. It may be the read is 32-bit only because the registers don't support byte access.

Nonetheless, I think we agree that if Nordic wants hwinfo to correctly represent the fact that the hardware identifier in this is a 64-bit value that is stored in a little-endian representation, then it's the responsibility of the SOC hwinfo implementation to reverse it so the unswapped byte order represents the expected byte sequence. Consumers of hwinfo data cannot be expected to do transformations on the bytes based on vendor-intended interpretations.

@sslupsky
Copy link
Contributor Author

sslupsky commented Apr 8, 2020

Agreed, consumers of the hwinfo data should not be expected to do transformations on the bytes. That is at the core of what I am trying to get to consensus with on this issue. The consensus on this determines if the root cause of the problem is:

  1. the underlying driver(s), or
  2. how the information is output with hwinfo_shell.c.

Your suggestion, and I believe @alexanderwachter said this too, is that the data structure should be a "sequence of bytes". I agree.

So, that confirmation then suggests that the driver(s) should be responsible for ensuring that the data structure is a sequence of bytes. That is not what the current sam0 and nordic drivers do. The drivers read the data as u32_t and then hwinfo_shell outputs the data as a sequence of bytes, which of course then has endianness of the underlying MCU, which in this case is Cortex M0 which is little endian.

This issue likely extends to all the other drivers in the tree where the MCU is little endian.

@sslupsky
Copy link
Contributor Author

sslupsky commented Apr 8, 2020

I think the change can be done efficiently using sys/byteorder.h as follows:
sam0:

diff --git a/drivers/hwinfo/hwinfo_sam0.c b/drivers/hwinfo/hwinfo_sam0.c
index 309989ad24..b3275035f0 100644
--- a/drivers/hwinfo/hwinfo_sam0.c
+++ b/drivers/hwinfo/hwinfo_sam0.c
@@ -9,6 +9,7 @@
 #include <soc.h>
 #include <drivers/hwinfo.h>
 #include <string.h>
+#include <sys/byteorder.h>
 
 struct sam0_uid {
        u32_t id[4];
@@ -18,10 +19,10 @@ ssize_t z_impl_hwinfo_get_device_id(u8_t *buffer, size_t length)
 {
        struct sam0_uid dev_id;
 
-       dev_id.id[0] = *(const u32_t *) DT_INST_REG_ADDR_BY_IDX(0, 0);
-       dev_id.id[1] = *(const u32_t *) DT_INST_REG_ADDR_BY_IDX(0, 1);
-       dev_id.id[2] = *(const u32_t *) DT_INST_REG_ADDR_BY_IDX(0, 2);
-       dev_id.id[3] = *(const u32_t *) DT_INST_REG_ADDR_BY_IDX(0, 3);
+       dev_id.id[0] = sys_cpu_to_be32(*(const u32_t *) DT_INST_REG_ADDR_BY_IDX(0, 0));
+       dev_id.id[1] = sys_cpu_to_be32(*(const u32_t *) DT_INST_REG_ADDR_BY_IDX(0, 1));
+       dev_id.id[2] = sys_cpu_to_be32(*(const u32_t *) DT_INST_REG_ADDR_BY_IDX(0, 2));
+       dev_id.id[3] = sys_cpu_to_be32(*(const u32_t *) DT_INST_REG_ADDR_BY_IDX(0, 3));
 
        if (length > sizeof(dev_id.id)) {
                length = sizeof(dev_id.id);

nordic:

diff --git a/drivers/hwinfo/hwinfo_nrf.c b/drivers/hwinfo/hwinfo_nrf.c
index 6c2822d891..d29334635f 100644
--- a/drivers/hwinfo/hwinfo_nrf.c
+++ b/drivers/hwinfo/hwinfo_nrf.c
@@ -8,6 +8,7 @@
 #include <drivers/hwinfo.h>
 #include <string.h>
 #include <hal/nrf_ficr.h>
+#include <sys/byteorder.h>
 
 struct nrf_uid {
        u32_t id[2];
@@ -17,8 +18,8 @@ ssize_t z_impl_hwinfo_get_device_id(u8_t *buffer, size_t length)
 {
        struct nrf_uid dev_id;
 
-       dev_id.id[0] = nrf_ficr_deviceid_get(NRF_FICR, 0);
-       dev_id.id[1] = nrf_ficr_deviceid_get(NRF_FICR, 1);
+       dev_id.id[0] = sys_cpu_to_be32(nrf_ficr_deviceid_get(NRF_FICR, 0));
+       dev_id.id[1] = sys_cpu_to_be32(nrf_ficr_deviceid_get(NRF_FICR, 1));
 
        if (length > sizeof(dev_id.id)) {
                length = sizeof(dev_id.id);

I have tested the sam0 driver and it works fine. Can you please test the nordic patch and let me know if it provides what you want? Do the words need to be swapped as well in the nordic driver?

@pabigot
Copy link
Collaborator

pabigot commented Apr 8, 2020

I believe (without testing) that Nordic must be:

+       dev_id.id[0] = sys_cpu_to_be32(nrf_ficr_deviceid_get(NRF_FICR, 1));
+       dev_id.id[1] = sys_cpu_to_be32(nrf_ficr_deviceid_get(NRF_FICR, 0));

as the stored value is a little-endian 64-bit integer. That's why reversing the entire 8-byte sequence is being done for USB.

@sslupsky
Copy link
Contributor Author

sslupsky commented Apr 8, 2020

@carlescufi submitted a PR for this:
#24203

@sslupsky
Copy link
Contributor Author

sslupsky commented Apr 8, 2020

I tested the sam0 but cannot test the nordic change. I conferred with @pabigot about the change to the nordic driver and it looks ok. It was a slightly different change than the sam0 driver because of the word size.

sslupsky added a commit to sslupsky/zephyr that referenced this issue Apr 9, 2020
HWINFO drivers should be responsible for ensuring that
the data structure is a sequence of bytes. That is not
what the current sam0 and nordic drivers do. The drivers
read the data as u32_t and then memcpy the data to a
buffer. This ensures the data has the endianness of the
underlying MCU, which in this case is Cortex M0 which
is little endian.

This commit fixes the endianness so the data can be
interpreted as a "left to right sequence of bytes".

This commit updates the API doc to provide clarification
of the data structure.
Add to 2.3 release notes.

Fixes zephyrproject-rtos#23444, zephyrproject-rtos#24103

Signed-off-by: Steven Slupsky <[email protected]>
carlescufi pushed a commit that referenced this issue Apr 9, 2020
HWINFO drivers should be responsible for ensuring that
the data structure is a sequence of bytes. That is not
what the current sam0 and nordic drivers do. The drivers
read the data as u32_t and then memcpy the data to a
buffer. This ensures the data has the endianness of the
underlying MCU, which in this case is Cortex M0 which
is little endian.

This commit fixes the endianness so the data can be
interpreted as a "left to right sequence of bytes".

This commit updates the API doc to provide clarification
of the data structure.
Add to 2.3 release notes.

Fixes #23444, #24103

Signed-off-by: Steven Slupsky <[email protected]>
avisconti pushed a commit to avisconti/zephyr that referenced this issue Apr 15, 2020
HWINFO drivers should be responsible for ensuring that
the data structure is a sequence of bytes. That is not
what the current sam0 and nordic drivers do. The drivers
read the data as u32_t and then memcpy the data to a
buffer. This ensures the data has the endianness of the
underlying MCU, which in this case is Cortex M0 which
is little endian.

This commit fixes the endianness so the data can be
interpreted as a "left to right sequence of bytes".

This commit updates the API doc to provide clarification
of the data structure.
Add to 2.3 release notes.

Fixes zephyrproject-rtos#23444, zephyrproject-rtos#24103

Signed-off-by: Steven Slupsky <[email protected]>
ghost pushed a commit to endiantechnologies/zephyr that referenced this issue Apr 20, 2020
HWINFO drivers should be responsible for ensuring that
the data structure is a sequence of bytes. That is not
what the current sam0 and nordic drivers do. The drivers
read the data as u32_t and then memcpy the data to a
buffer. This ensures the data has the endianness of the
underlying MCU, which in this case is Cortex M0 which
is little endian.

This commit fixes the endianness so the data can be
interpreted as a "left to right sequence of bytes".

This commit updates the API doc to provide clarification
of the data structure.
Add to 2.3 release notes.

Fixes zephyrproject-rtos#23444, zephyrproject-rtos#24103

Signed-off-by: Steven Slupsky <[email protected]>
hakehuang pushed a commit to hakehuang/zephyr that referenced this issue Jun 20, 2020
HWINFO drivers should be responsible for ensuring that
the data structure is a sequence of bytes. That is not
what the current sam0 and nordic drivers do. The drivers
read the data as u32_t and then memcpy the data to a
buffer. This ensures the data has the endianness of the
underlying MCU, which in this case is Cortex M0 which
is little endian.

This commit fixes the endianness so the data can be
interpreted as a "left to right sequence of bytes".

This commit updates the API doc to provide clarification
of the data structure.
Add to 2.3 release notes.

Fixes zephyrproject-rtos#23444, zephyrproject-rtos#24103

Signed-off-by: Steven Slupsky <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Drivers bug The issue is a bug, or the PR is fixing a bug priority: low Low impact/importance bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants