[dt] Provide dt_ioremap() to map device registers

Devicetree devices encode register address ranges within the "reg"
property, with the number of cells used for addresses and for sizes
determined by the #address-cells and #size-cells properties of the
immediate parent device.

Record the number of address and size cells for each device, and
provide a dt_ioremap() function to allow drivers to map a specified
range without having to directly handle the "reg" property.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2025-04-15 20:19:17 +01:00
parent 99322fd3b3
commit eeec6442d9
2 changed files with 89 additions and 0 deletions

View File

@@ -34,6 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <errno.h>
#include <ipxe/device.h>
#include <ipxe/fdt.h>
#include <ipxe/iomap.h>
#include <ipxe/devtree.h>
static struct dt_driver dt_node_driver __dt_driver;
@@ -41,6 +42,70 @@ struct root_device dt_root_device __root_device;
static void dt_remove_children ( struct dt_device *parent );
/**
* Map devicetree range
*
* @v dt Devicetree device
* @v offset Starting node offset
* @v index Region index
* @v len Length to map, or 0 to map whole region
* @ret io_addr I/O address, or NULL on error
*/
void * dt_ioremap ( struct dt_device *dt, unsigned int offset,
unsigned int index, size_t len ) {
struct dt_device *parent =
container_of ( dt->dev.parent, struct dt_device, dev );
uint64_t address;
uint64_t size;
unsigned int cell;
void *io_addr;
int rc;
/* Read address */
cell = ( index * ( parent->address_cells + parent->size_cells ) );
if ( ( rc = fdt_cells ( &sysfdt, offset, "reg", cell,
parent->address_cells, &address ) ) != 0 ) {
DBGC ( dt, "DT %s could not read region %d address: %s\n",
dt->path, index, strerror ( rc ) );
return NULL;
}
cell += parent->address_cells;
/* Read size (or assume sufficient, if tree specifies no sizes) */
size = len;
if ( parent->size_cells &&
( rc = fdt_cells ( &sysfdt, offset, "reg", cell,
parent->size_cells, &size ) ) != 0 ) {
DBGC ( dt, "DT %s could not read region %d size: %s\n",
dt->path, index, strerror ( rc ) );
return NULL;
}
/* Use region size as length if not specified */
if ( ! len )
len = size;
DBGC ( dt, "DT %s region %d at %#08llx+%#04llx\n",
dt->path, index, ( ( unsigned long long ) address ),
( ( unsigned long long ) size ) );
/* Verify size */
if ( len > size ) {
DBGC ( dt, "DT %s region %d is too small (%#llx/%#zx bytes)\n",
dt->path, index, ( ( unsigned long long ) size ), len );
return NULL;
}
/* Map region */
io_addr = ioremap ( address, len );
if ( ! io_addr ) {
DBGC ( dt, "DT %s could not map region %d\n",
dt->path, index );
return NULL;
}
return io_addr;
}
/**
* Find devicetree driver
*
@@ -158,6 +223,16 @@ static int dt_probe_node ( struct dt_device *parent, unsigned int offset,
INIT_LIST_HEAD ( &dt->dev.children );
list_add_tail ( &dt->dev.siblings, &dt->dev.parent->children );
/* Read #address-cells and #size-cells, if present */
if ( ( rc = fdt_u32 ( &sysfdt, offset, "#address-cells",
&dt->address_cells ) ) != 0 ) {
dt->address_cells = DT_DEFAULT_ADDRESS_CELLS;
}
if ( ( rc = fdt_u32 ( &sysfdt, offset, "#size-cells",
&dt->size_cells ) ) != 0 ) {
dt->size_cells = DT_DEFAULT_SIZE_CELLS;
}
/* Probe device */
if ( ( rc = dt_probe ( dt, offset ) ) != 0 )
goto err_probe;

View File

@@ -21,8 +21,19 @@ struct dt_device {
struct dt_driver *driver;
/** Driver-private data */
void *priv;
/** Number of address cells for child devices */
uint32_t address_cells;
/** Number of size cells for child devices */
uint32_t size_cells;
};
/** Default number of address cells, if not specified */
#define DT_DEFAULT_ADDRESS_CELLS 2
/** Default number of size cells, if not specified */
#define DT_DEFAULT_SIZE_CELLS 1
/** A devicetree driver */
struct dt_driver {
/** Driver name */
@@ -73,4 +84,7 @@ static inline void * dt_get_drvdata ( struct dt_device *dt ) {
return dt->priv;
}
extern void * dt_ioremap ( struct dt_device *dt, unsigned int offset,
unsigned int index, size_t len );
#endif /* _IPXE_DEVTREE_H */