Burn eFuses for MAC address on iMX8MP

Posted by Marcus Folkesson on Saturday, October 28, 2023

Burn eFuses for MAC address on iMX8MP

The iMX (iMX6, iMX7, iMX8) has a similiar OCOTP (On-Chip One Time Programmable) module that store, for example the MAC addresses for the internal ethernet controllers.

The reference manual is not clear either on the byte order or which bytes belong to which MAC address when there are several. In fact, I had to look at the U-boot implementation [1] to know for sure how these fuses is used:

 1void imx_get_mac_from_fuse(int dev_id, unsigned char *mac)
 2{
 3	struct imx_mac_fuse *fuse;
 4	u32 offset;
 5	bool has_second_mac;
 6
 7	offset = is_mx6() ? MAC_FUSE_MX6_OFFSET : MAC_FUSE_MX7_OFFSET;
 8	fuse = (struct imx_mac_fuse *)(ulong)(OCOTP_BASE_ADDR + offset);
 9	has_second_mac = is_mx7() || is_mx6sx() || is_mx6ul() || is_mx6ull() || is_imx8mp();
10
11	if (has_second_mac && dev_id == 1) {
12		u32 value = readl(&fuse->mac_addr2);
13
14		mac[0] = value >> 24;
15		mac[1] = value >> 16;
16		mac[2] = value >> 8;
17		mac[3] = value;
18
19		value = readl(&fuse->mac_addr1);
20		mac[4] = value >> 24;
21		mac[5] = value >> 16;
22
23	} else {
24		u32 value = readl(&fuse->mac_addr1);
25
26		mac[0] = value >> 8;
27		mac[1] = value;
28
29		value = readl(&fuse->mac_addr0);
30		mac[2] = value >> 24;
31		mac[3] = value >> 16;
32		mac[4] = value >> 8;
33		mac[5] = value;
34	}
35}

OCOTP Layout

The fuses related to MAC addresses starts at offset 0x640 for iMX7 and iMX8MP, and at offset 0x620 for all iMX6 processors.

The MAC fuses belongs to fuse bank 9 as seen in the table below:

/media/imx8-mac-fuse-layout.png

Burn fuses

There are several ways to burn the fuses nowadays. A few years ago, the only way (that I'm aware of) was by the non-mainlined fsl_otp-driver provided in the Freescale kernel tree. I'm not going to describe how to use since it should not be used anyway.

The fuses are mapped to the MAC address as described in this picture:

/media/imx8-mac-fuse-example.png

The iMX8MP has two MACs and we will assign the MAC address 00:bb:cc:dd:ee:ff for MAC0 and 00:22:33:44:55:66 for MAC1.

Via U-boot

With the CONFIG_CMD_FUSE config set, U-boot are able to burn and sense eFuses via the fuse command:

 1u-boot=> fuse
 2fuse - Fuse sub-system
 3
 4Usage:
 5fuse read <bank> <word> [<cnt>] - read 1 or 'cnt' fuse words,
 6    starting at 'word'
 7fuse sense <bank> <word> [<cnt>] - sense 1 or 'cnt' fuse words,
 8    starting at 'word'
 9fuse prog [-y] <bank> <word> <hexval> [<hexval>...] - program 1 or
10    several fuse words, starting at 'word' (PERMANENT)
11fuse override <bank> <word> <hexval> [<hexval>...] - override 1 or
12    several fuse words, starting at 'word'

Burn the fuses with fuse prog:

1fuse prog -y 9 0 0xccddeeff
2fuse prog -y 9 1 0x556600bb
3fuse prog -y 9 2 0x00223344

And read it back with fuse sense:

 1u-boot=> fuse sense 9 0
 2Sensing bank 9:
 3
 4Word 0x00000000: ccddeeff
 5u-boot=> fuse sense 9 1
 6Sensing bank 9:
 7
 8Word 0x00000001: 556600bb
 9u-boot=> fuse sense 9 2
10Sensing bank 9:
11
12Word 0x00000002: 00223344

As it's a U-boot command, it is also possible to burn the fuses with UUU [2] (Universal Update Utility) via the SDP protocol. It could be handy e.g. in production.

Example on a uuu-script:

 1$ cat imx8mplus-emmc-all.uuu 
 2uuu_version 1.2.39
 3
 4# This script will flash u-boot to mmc on bus 1
 5# Usage: uuu <script>
 6
 7SDPS: boot -f ../imx-boot
 8
 9#Burn fuses
10FB: ucmd fuse prog -y 9 0 0xccddeeff
11FB: ucmd fuse prog -y 9 1 0x556600bb
12FB: ucmd fuse prog -y 9 2 0x00223344
13
14#Burn image
15FB: ucmd setenv emmc_dev 2
16FB: ucmd setenv emmc_ack 1
17FB: ucmd setenv fastboot_dev mmc
18FB: ucmd setenv mmcdev ${emmc_dev}
19FB: ucmd mmc dev ${emmc_dev}
20FB: flash -raw2sparse all ../distro-image-dev-imx8mp.wic
21FB: ucmd mmc partconf ${emmc_dev} ${emmc_ack} 1 0
22FB: done

Via nvmem-imx-ocotp

The OCOTP-module is exposed by the nvmem-imx-ocotp (CONFIG_NVMEM_IMX_OCOTP) driver and the fuses could be read and written to via the sysfs entry /sys/devices/platform/soc@0/30000000.bus/30350000.efuse/imx-ocotp0/nvmem.

Note that it's not the full OCOTP module but only the eFuses that are exposed this way, so MAC_ADDR0 is placed at offset 0x90, not 0x640!

We could read out our MAC addresses at offset 0x90:

 1root@imx8mp:~# hexdump /sys/devices/platform/soc@0/30000000.bus/30350000.efuse/imx-ocotp0/nvmem 
 20000000 a9eb ffaf aaff 0002 52bb ea35 6000 1119
 30000010 4591 2002 0000 0100 007f 0000 2000 9800
 40000020 0000 0000 0000 0000 0000 0000 0000 0000
 5*
 60000040 bada bada bada bada bada bada bada bada
 7*
 80000060 0000 0000 0000 0000 0000 0000 0000 0000
 9*
100000080 0000 0000 0000 0000 0000 0000 0004 0000
110000090 eeff ccdd 00bb 5566 3344 0022 0000 0000

We can also see we have the expected MAC addresses set for our interfaces:

1root@imx8mp:~# ip a
24: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
3    link/ether 00:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff
45: eth1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
5    link/ether 00:22:33:44:55:66 brd ff:ff:ff:ff:ff:ff