According to U-Boot's README.enetaddr "Usage" section:
If the hardware design mandates that the MAC address is stored in some special place (like EEPROM etc...), then the board specific init code (such as the board-specific misc_init_r() function) is responsible for locating the MAC address(es) and initializing the respective environment variable(s) from it. Note that this shall be done if, and only if, the environment does not already contain these environment variables, i.e. existing variable definitions must not be overwritten.
During runtime, the ethernet layer will use the environment variables to sync the MAC addresses to the ethernet structures. All ethernet driver code should then only use the enetaddr member of the eth_device structure. This is done on every network command, so the ethernet copies will stay in sync.
Let us suppose a device has an EEPROM containing a universally administered MAC address and that the device's misc_init_r() function reads that MAC address and writes it to an environment variable (e.g. "ethaddr") if and only if the environment variable doesn't exist. Let us also suppose that the environment is later saved by the U-Boot saveenv command, and moreover, that the whole system is running off a removable storage device such as an SD card, where the U-Boot environment is also saved.
EDIT
In my case, I am in the above situation for a new custom board which has a "MAC address EEPROM" from Micronix containing a unique, universally administered MAC address, and the board's only available storage location for a saved U-Boot environment is the SD card from which it boots. I have the following code for the board-specific misc_init_r() function:
#include <common.h>
#ifndef CONFIG_SPL_BUILD
static int my_board_read_mac(uchar *mac)
{
int ret;
/* EEPROM is at bus 0. */
ret = i2c_set_bus_num(0);
if (ret) {
printf("Cannot select EEPROM I2C bus - err %d\n", ret);
return ret;
}
/* EEPROM is at address 0x50. MAC address is at offset 0xfa. */
ret = eeprom_read(0x50, 0xfa, mac, 6);
if (ret) {
printf("Cannot read I2C EEPROM - err %d\n", ret);
return ret;
}
return 0;
}
static int my_board_do_mac(void)
{
int ret;
uchar mac[6];
ret = my_board_read_mac(mac);
if (ret) {
printf("Failed to read MAC address - err %d\n", ret);
return ret;
}
if (!is_valid_ethaddr(mac)) {
printf("Read invalid MAC address %pM\n", mac);
return -EINVAL;
}
if (!getenv("ethaddr")) {
return eth_setenv_enetaddr("ethaddr", mac);
}
return 0;
}
int misc_init_r(void)
{
int ret;
ret = my_board_do_mac();
if (ret) {
printf("Failed to set MAC address - err %d\n", ret);
}
return 0;
}
#endif
My question is: If the SD card is removed from the original device and placed into a similar device (with a different universally administered MAC address in its EEPROM), will the new device use the original device's MAC address rather than it's own, unique MAC address? If so, what is the best way to prevent that from happening?
EDIT2
What I'm looking for is some way to set the MAC address automatically, but not to "pollute" any copy of the environment saved on the SD card with this board specific MAC address. I.e. I want the MAC address to remain tied to the board, rather than to the SD card.
The answer is that this becomes a policy question. The non-default environment (so, what gets saved to someplace when you use saveenv) is intended to be instance-specific. This is why generally speaking board code will see if ethaddr is set, and if so, not try and derive one.
When you move from board to board you can use env default -f -a to restore the running environment to the built-in defaults.
If you want to share the same SD card between multiple boards then you need to come up with a policy to manage this situation.
Related
I'm seeing a weird case in a simple linux driver test(arm64).
The user program calls ioctl of a device driver and passes array 'arg' of uint64_t as argument. By the way, arg[2] contains a pointer to a variable in the app. Below is the code snippet.
case SetRunParameters:
copy_from_user(args, (void __user *)arg, 8*3);
offs = args[2] % PAGE_SIZE;
down_read(¤t->mm->mmap_sem);
res = get_user_pages( (unsigned long)args[2], 1, 1, &pages, NULL);
if (res) {
kv_page_addr = kmap(pages);
kv_addr = ((unsigned long long int)(kv_page_addr)+offs);
args[2] = page_to_phys(pages) + offset; // args[2] changed to physical
}
else {
printk("get_user_pages failed!\n");
}
up_read(¤t->mm->mmap_sem);
*(vaddr + REG_IOCTL_ARG/4) = virt_to_phys(args); // from axpu_regs.h
printk("ldd:writing %x at %px\n",cmdx,vaddr + REG_IOCTL_CMD/4); // <== line 248. not ok w/o this printk line why?..
*(vaddr + REG_IOCTL_CMD/4) = cmdx; // this command is different from ioctl cmd!
put_page(pages); //page_cache_release(page);
break;
case ...
I have marked line 248 in above code. If I comment out the printk there, a trap occurs and the virtual machine collapses(I'm doing this on a qemu virtual machine). The cmdx is a integer value set according to the ioctl command from the app, and vaddr is the virtual address of the device (obtained from ioremap). If I keep the printk, it works as I expect. What case can make this happen? (cache or tlb?)
Accessing memory-mapped registers by simple C constructs such as *(vaddr + REG_IOCTL_ARG/4) is a bad idea. You might get away with it on some platforms if the access is volatile-qualified, but it won't work reliably or at all on some platforms. The proper way to access memory-mapped registers is via the functions declared by #include <asm/io.h> or #include <linux/io.h>. These will take care of any arch-specific requirements to ensure that writes are properly ordered as far as the CPU is concerned1.
The functions for memory-mapped register access are described in the Linux kernel documentation under Bus-Independent Device Accesses.
This code:
*(vaddr + REG_IOCTL_ARG/4) = virt_to_phys(args);
*(vaddr + REG_IOCTL_CMD/4) = cmdx;
can be rewritten as:
writel(virt_to_phys(args), vaddr + REG_IOCTL_ARG/4);
writel(cmdx, vaddr + REG_IOCTL_CMD/4);
1 Write-ordering for specific bus types such as PCI may need extra code to read a register inbetween writes to different registers if the ordering of the register writes is important. That is because writes are "posted" asynchronously to the PCI bus, and the PCI device may process writes to different registers out of order. An intermediate register read will not be handled by the device until all preceding writes have been handled, so it can be used to enforce ordering of posted writes.
in a linux device driver, in the init function for the device, I tried reading an address (which is SMMUv3 device for arm64) like below.
uint8_t *addr1;
addr1 = ioremap(0x09050000, 0x20000);
printk("SMMU_AIDR : 0x%X\n", *(addr1 + 0x1c));
but I get Internal error: synchronous external abort: 96000010 [#1] SMP error.
Is it not permitted to map an address to virtual address using ioremap and just reading that address?
I gave a fixed value 0x78789a9a to SMMU IDR[2] register. (at offset 0x8, 32 bit register. This is possible because it's qemu.)
SMMU starts at 0x09050000 and it has address space 0x20000.
__iomem uint32_t *addr1 = NULL;
static int __init my_driver_init(void)
{
...
addr1 = ioremap(0x09050000, 0x20000); // smmuv3
printk("SMMU_IDR[2] : 0x%X\n", readl(addr1 +0x08/4));
..}
This is the output when the driver is initialized.(The value is read ok)
[ 453.207261] SMMU_IDR[2] : 0x78789A9A
The first problem was that the access width was wrong for that address. Before, it was defined as uint8_t *addr1; and I used printk("SMMU_AIDR : 0x%X\n", *(addr1 + 0x1c)) so it was reading byte when it was not allowed by the SMMU model.
Second problem (I think this didn't cause the trap because arm64 provides memory mapped io) was that I used memory access(pointer dereferencing) for memory mapped IO registers. As people commented, I should have used readl function. (Mainly because to make the code portable. readl works also for iomap platforms like x86_64. using the mmio adderss as pointer will not work on such platforms. I later found that readl function takes care of the memory barrier problem too).
ADD : I fixed volatile to __iomem for variable addr1.(thanks #0andriy)
I'm having trouble reading the Raspberry Pi 4 system timer.
My understanding is that the LO 32 bits should be at address 0x7e003004.
My reads always return -1.
Here's how I am trying:
int fd;
unsigned char* start;
uint32_t* t4lo;
fd = open("/dev/mem", O_RDONLY);
if (fd == -1)
{
perror("open /dev/mem");
exit(1);
}
start = (unsigned char*)mmap(0, getpagesize(), PROT_READ, MAP_SHARED,
fd, 0x7e003000);
t4lo = (unsigned int *)(start + 0x04);
...
uint32_t Rpi::readTimer(void)
{
return *t4lo;
}
I should be checking the value of start, but gdb tells me it's reasonable so I don't think that's the problem.
(gdb) p t4lo
$4 = (uint32_t *) 0xb6f3a004
and gdb won't let me access *t4lo. Any ideas?
Edit: clock_gettime() is fulfilling my needs, but I'm still curious.
A closer look at https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/rpi_DATA_2711_1p0.pdf
figure 1 on page 5 shows that addresses vary depending upon who's looking at things. If you start with 0x7c00_0000 on the left side and follow it over to the right, it's apparent that it shows up at 0xfc00_0000 to the processor. So changing the timer base address to 0xfe00_3000 fixed the problem.
The secret is hidden in section 1.2.4:
So a peripheral described in this document as being at legacy address 0x7Enn_nnnn
is available in the 35-bit address space at 0x4_7Enn_nnnn, and visible to the ARM
at 0x0_FEnn_nnnn if Low Peripheral mode is enabled.
The address of the BCM2711 ARM Peripherals is the bus address which is not the same as the physical address in most systems. The bus address is easily used by DMA(Direct Memory Access) controller. mmap creates a new mapping from physical address to virtual address not bus address. So you can't use mmap funtion with parameter 0x7e003000. The rich answer is right.
So changing the timer base address to 0xfe00_3000 fixed the problem.
In addtion, your program run in the User space, only virtual address can you directly use.
How can I configure Keil uVision5 to redirect printf output from STM32F4xx out through MCU's USB interface? Then, USB will connect to a Windows computer, virtual port driver, and a terminal program.
I cannot find an example uVision5 project that configures printf to output through the STM32F4xx MCU's USB interface.
The USB of the STM32F4xx MCU has to implement the USB CDC as device class or interface class (https://en.wikipedia.org/wiki/USB_communications_device_class) and in host mode (USB OTG) in its firmware. On the PC's USB has to be created a virtual COM port (driver) to process the RS232 protocol that the STM32F4 sends over the USB CDC (in host mode). When the CDC firmware on the STM32F4 is working correctly, on linux the STM32F4 appears i.e. as /dev/ttyACM0 and this can be accessed with standard RS232 terminal programs.
basics -> http://www.keil.com/support/man/docs/rlarm/rlarm_usb_create_cdc_acm.htm
Here is a basic example how to send serial data over USB CDC (that on pc's USB appears as a Virtual COM port i.e. /dev/ttyACM0): http://visualgdb.com/tutorials/arm/stm32/usb/
See also this http://stm32f4-discovery.net/2014/08/library-24-virtual-com-port-vcp-stm32f4xx/
Follow or adapt the tutorial above to linux (http://visualgdb.com/tutorials/arm/stm32/usb/).
To redirect printf() to USB CDC override the standard I/O routines like in the following. The ARM version of printf() invokes these routines then, see this https://mcuoneclipse.com/2014/07/11/printf-and-scanf-with-gnu-arm-libraries/ (the ARM version of printf() is very different from the x86/x64 version of printf()):
#include<sys/stat.h>
extern"C"
{
int _fstat (int fd, struct stat *pStat)
{
pStat->st_mode = S_IFCHR;
return 0;
}
int _close(int)
{
return -1;
}
int _write (int fd, char *pBuffer, int size)
{
return VCP_write(pBuffer, size);
}
int _isatty (int fd)
{
return 1;
}
int _lseek(int, int, int)
{
return -1;
}
int _read (int fd, char *pBuffer, int size)
{
for (;;)
{
int done = VCP_read(pBuffer, size);
if (done)
return done;
}
}
}
Or use the ARM semihosting interface (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0471c/Bgbjhiea.html). It will provide the functionality automatically to redirect printf() to USB CDC
More information:
http://www.keil.com/forum/60565/ (usbd_STM32F4xx_FS.c)
http://www.beyondlogic.org/usbnutshell/usb1.shtml
This is an example how to implement a USB-RS232 adapter: http://www.wolinlabs.com/blog/stm32f4.virtual.com.port.html
The git also contains many interesting files git clone https://github.com/rowol/stm32_discovery_arm_gcc
Can anyone tell me how a Char Driver is bind to the corresponding physical device?
Also, I would like to know where inside a char driver we are specifying the physical device related information, which can be used by kernel to do the binding.
Thanks !!
A global array — bdev_map for block and cdev_map for character devices — is used to implement a hash table, which employs the device major number as hash key.
while registering for char driver following calls get in invoked to get major and minor numbers.
int register_chrdev_region(dev_t from, unsigned count, const char *name)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
After a device number range has been obtained, the device needs to be activated by adding it to the character device database.
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
Here on cdev structure initialize with file operation and respected character device.
Whenever a device file is opened, the various filesystem implementations invoke the init_special_inode function to create the inode for a block or character device file.
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
}
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",
mode);
}
now the default_chr_fpos chrdev_open() method will get invoked. which will look up for the inode->rdev device in cdev_map array and will get a instance of cdev structure. with the reference to cdev it will bind the file->f_op to cdev file operation and invoke the open method for character driver.
In a character driver like I2C client driver, We specify the slave address in the client structure's "addr" field and then call i2c_master_send() or i2c_master_receive() on this client . This calls will ultimately go to the main adapter controlling that line and the adapter then communicates with the device specified by the slave address.
And the binding of drivers operations is done mainly with cdev_init() and cdev_add() functions.
Also driver may choose to provide probe() function and let kernel find and bind all the devices which this driver is capable of supporting.