670 lines
16 KiB
C
670 lines
16 KiB
C
/*
|
|
* Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
/*
|
|
* @file linux/device.c
|
|
* @brief Linux libmetal device operations.
|
|
*/
|
|
|
|
#include <metal/device.h>
|
|
#include <metal/sys.h>
|
|
#include <metal/utilities.h>
|
|
#include <metal/irq.h>
|
|
|
|
#define MAX_DRIVERS 64
|
|
|
|
struct linux_bus;
|
|
struct linux_device;
|
|
|
|
struct linux_driver {
|
|
const char *drv_name;
|
|
const char *mod_name;
|
|
const char *cls_name;
|
|
struct sysfs_driver *sdrv;
|
|
int (*dev_open)(struct linux_bus *lbus,
|
|
struct linux_device *ldev);
|
|
void (*dev_close)(struct linux_bus *lbus,
|
|
struct linux_device *ldev);
|
|
void (*dev_irq_ack)(struct linux_bus *lbus,
|
|
struct linux_device *ldev,
|
|
int irq);
|
|
int (*dev_dma_map)(struct linux_bus *lbus,
|
|
struct linux_device *ldev,
|
|
uint32_t dir,
|
|
struct metal_sg *sg_in,
|
|
int nents_in,
|
|
struct metal_sg *sg_out);
|
|
void (*dev_dma_unmap)(struct linux_bus *lbus,
|
|
struct linux_device *ldev,
|
|
uint32_t dir,
|
|
struct metal_sg *sg,
|
|
int nents);
|
|
};
|
|
|
|
struct linux_bus {
|
|
struct metal_bus bus;
|
|
const char *bus_name;
|
|
struct linux_driver drivers[MAX_DRIVERS];
|
|
struct sysfs_bus *sbus;
|
|
};
|
|
|
|
struct linux_device {
|
|
struct metal_device device;
|
|
char dev_name[PATH_MAX];
|
|
char dev_path[PATH_MAX];
|
|
char cls_path[PATH_MAX];
|
|
metal_phys_addr_t region_phys[METAL_MAX_DEVICE_REGIONS];
|
|
struct linux_driver *ldrv;
|
|
struct sysfs_device *sdev;
|
|
struct sysfs_attribute *override;
|
|
int fd;
|
|
};
|
|
|
|
static struct linux_bus *to_linux_bus(struct metal_bus *bus)
|
|
{
|
|
return metal_container_of(bus, struct linux_bus, bus);
|
|
}
|
|
|
|
static struct linux_device *to_linux_device(struct metal_device *device)
|
|
{
|
|
return metal_container_of(device, struct linux_device, device);
|
|
}
|
|
|
|
static int metal_uio_read_map_attr(struct linux_device *ldev, unsigned index,
|
|
const char *name, unsigned long *value)
|
|
{
|
|
const char *cls = ldev->cls_path;
|
|
struct sysfs_attribute *attr;
|
|
char path[SYSFS_PATH_MAX];
|
|
int result;
|
|
|
|
result = snprintf(path, sizeof(path), "%s/maps/map%u/%s", cls, index, name);
|
|
if (result >= (int)sizeof(path))
|
|
return -EOVERFLOW;
|
|
attr = sysfs_open_attribute(path);
|
|
if (!attr || sysfs_read_attribute(attr) != 0)
|
|
return -errno;
|
|
|
|
*value = strtoul(attr->value, NULL, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int metal_uio_dev_bind(struct linux_device *ldev,
|
|
struct linux_driver *ldrv)
|
|
{
|
|
struct sysfs_attribute *attr;
|
|
int result;
|
|
|
|
if (strcmp(ldev->sdev->driver_name, ldrv->drv_name) == 0)
|
|
return 0;
|
|
|
|
if (strcmp(ldev->sdev->driver_name, SYSFS_UNKNOWN) != 0) {
|
|
metal_log(METAL_LOG_INFO, "device %s in use by driver %s\n",
|
|
ldev->dev_name, ldev->sdev->driver_name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
attr = sysfs_get_device_attr(ldev->sdev, "driver_override");
|
|
if (!attr) {
|
|
metal_log(METAL_LOG_ERROR, "device %s has no override\n",
|
|
ldev->dev_name);
|
|
return -errno;
|
|
}
|
|
|
|
result = sysfs_write_attribute(attr, ldrv->drv_name,
|
|
strlen(ldrv->drv_name));
|
|
if (result) {
|
|
metal_log(METAL_LOG_ERROR, "failed to set override on %s\n",
|
|
ldev->dev_name);
|
|
return -errno;
|
|
}
|
|
ldev->override = attr;
|
|
|
|
attr = sysfs_get_driver_attr(ldrv->sdrv, "bind");
|
|
if (!attr) {
|
|
metal_log(METAL_LOG_ERROR, "driver %s has no bind\n", ldrv->drv_name);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
result = sysfs_write_attribute(attr, ldev->dev_name,
|
|
strlen(ldev->dev_name));
|
|
if (result) {
|
|
metal_log(METAL_LOG_ERROR, "failed to bind %s to %s\n",
|
|
ldev->dev_name, ldrv->drv_name);
|
|
return -errno;
|
|
}
|
|
|
|
metal_log(METAL_LOG_DEBUG, "bound device %s to driver %s\n",
|
|
ldev->dev_name, ldrv->drv_name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int metal_uio_dev_open(struct linux_bus *lbus, struct linux_device *ldev)
|
|
{
|
|
char *instance, path[SYSFS_PATH_MAX];
|
|
struct linux_driver *ldrv = ldev->ldrv;
|
|
unsigned long *phys, offset=0, size=0;
|
|
struct metal_io_region *io;
|
|
struct dlist *dlist;
|
|
int result, i;
|
|
void *virt;
|
|
int irq_info;
|
|
|
|
|
|
ldev->fd = -1;
|
|
|
|
ldev->sdev = sysfs_open_device(lbus->bus_name, ldev->dev_name);
|
|
if (!ldev->sdev) {
|
|
metal_log(METAL_LOG_ERROR, "device %s:%s not found\n",
|
|
lbus->bus_name, ldev->dev_name);
|
|
return -ENODEV;
|
|
}
|
|
metal_log(METAL_LOG_DEBUG, "opened sysfs device %s:%s\n",
|
|
lbus->bus_name, ldev->dev_name);
|
|
|
|
result = metal_uio_dev_bind(ldev, ldrv);
|
|
if (result)
|
|
return result;
|
|
|
|
result = snprintf(path, sizeof(path), "%s/uio", ldev->sdev->path);
|
|
if (result >= (int)sizeof(path))
|
|
return -EOVERFLOW;
|
|
dlist = sysfs_open_directory_list(path);
|
|
if (!dlist) {
|
|
metal_log(METAL_LOG_ERROR, "failed to scan class path %s\n",
|
|
path);
|
|
return -errno;
|
|
}
|
|
|
|
dlist_for_each_data(dlist, instance, char) {
|
|
result = snprintf(ldev->cls_path, sizeof(ldev->cls_path),
|
|
"%s/%s", path, instance);
|
|
if (result >= (int)sizeof(ldev->cls_path))
|
|
return -EOVERFLOW;
|
|
result = snprintf(ldev->dev_path, sizeof(ldev->dev_path),
|
|
"/dev/%s", instance);
|
|
if (result >= (int)sizeof(ldev->dev_path))
|
|
return -EOVERFLOW;
|
|
break;
|
|
}
|
|
sysfs_close_list(dlist);
|
|
|
|
if (sysfs_path_is_dir(ldev->cls_path) != 0) {
|
|
metal_log(METAL_LOG_ERROR, "invalid device class path %s\n",
|
|
ldev->cls_path);
|
|
return -ENODEV;
|
|
}
|
|
|
|
i = 0;
|
|
do {
|
|
if (!access(ldev->dev_path, F_OK))
|
|
break;
|
|
usleep(10);
|
|
i++;
|
|
} while (i < 1000);
|
|
if (i >= 1000) {
|
|
metal_log(METAL_LOG_ERROR, "failed to open file %s, timeout.\n",
|
|
ldev->dev_path);
|
|
return -ENODEV;
|
|
}
|
|
result = metal_open(ldev->dev_path, 0);
|
|
if (result < 0) {
|
|
metal_log(METAL_LOG_ERROR, "failed to open device %s\n",
|
|
ldev->dev_path, strerror(-result));
|
|
return result;
|
|
}
|
|
ldev->fd = result;
|
|
|
|
metal_log(METAL_LOG_DEBUG, "opened %s:%s as %s\n",
|
|
lbus->bus_name, ldev->dev_name, ldev->dev_path);
|
|
|
|
for (i = 0, result = 0; !result && i < METAL_MAX_DEVICE_REGIONS; i++) {
|
|
phys = &ldev->region_phys[ldev->device.num_regions];
|
|
result = (result ? result :
|
|
metal_uio_read_map_attr(ldev, i, "offset", &offset));
|
|
result = (result ? result :
|
|
metal_uio_read_map_attr(ldev, i, "addr", phys));
|
|
result = (result ? result :
|
|
metal_uio_read_map_attr(ldev, i, "size", &size));
|
|
result = (result ? result :
|
|
metal_map(ldev->fd, offset, size, 0, 0, &virt));
|
|
if (!result) {
|
|
io = &ldev->device.regions[ldev->device.num_regions];
|
|
metal_io_init(io, virt, phys, size, -1, 0, NULL);
|
|
ldev->device.num_regions++;
|
|
}
|
|
}
|
|
|
|
irq_info = 1;
|
|
if (write(ldev->fd, &irq_info, sizeof(irq_info)) <= 0) {
|
|
metal_log(METAL_LOG_INFO,
|
|
"%s: No IRQ for device %s.\n",
|
|
__func__, ldev->dev_name);
|
|
ldev->device.irq_num = 0;
|
|
ldev->device.irq_info = (void *)-1;
|
|
} else {
|
|
ldev->device.irq_num = 1;
|
|
ldev->device.irq_info = (void *)(intptr_t)ldev->fd;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void metal_uio_dev_close(struct linux_bus *lbus,
|
|
struct linux_device *ldev)
|
|
{
|
|
(void)lbus;
|
|
|
|
if ((intptr_t)ldev->device.irq_info >= 0)
|
|
/* Normally this call would not be needed, and is added as precaution.
|
|
Also for uio there is only 1 interrupt associated to the fd/device,
|
|
we therefore do not need to specify a particular device */
|
|
metal_irq_unregister(ldev->fd, NULL, NULL, NULL);
|
|
|
|
if (ldev->override) {
|
|
sysfs_write_attribute(ldev->override, "", 1);
|
|
ldev->override = NULL;
|
|
}
|
|
if (ldev->sdev) {
|
|
sysfs_close_device(ldev->sdev);
|
|
ldev->sdev = NULL;
|
|
}
|
|
if (ldev->fd >= 0) {
|
|
close(ldev->fd);
|
|
}
|
|
}
|
|
|
|
static void metal_uio_dev_irq_ack(struct linux_bus *lbus,
|
|
struct linux_device *ldev,
|
|
int irq)
|
|
{
|
|
(void)lbus;
|
|
(void)irq;
|
|
int irq_info = 1;
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
ret = read(ldev->fd, (void *)&val, sizeof(val));
|
|
if (ret < 0) {
|
|
metal_log(METAL_LOG_ERROR, "%s, read uio irq fd %d failed: %d.\n",
|
|
__func__, ldev->fd, ret);
|
|
return;
|
|
}
|
|
ret = write(ldev->fd, &irq_info, sizeof(irq_info));
|
|
if (ret < 0) {
|
|
metal_log(METAL_LOG_ERROR, "%s, write uio irq fd %d failed: %d.\n",
|
|
__func__, ldev->fd, errno);
|
|
}
|
|
}
|
|
|
|
static int metal_uio_dev_dma_map(struct linux_bus *lbus,
|
|
struct linux_device *ldev,
|
|
uint32_t dir,
|
|
struct metal_sg *sg_in,
|
|
int nents_in,
|
|
struct metal_sg *sg_out)
|
|
{
|
|
int i, j;
|
|
void *vaddr_sg_lo, *vaddr_sg_hi, *vaddr_lo, *vaddr_hi;
|
|
struct metal_io_region *io;
|
|
|
|
(void)lbus;
|
|
(void)dir;
|
|
|
|
/* Check if the the input virt address is MMIO address */
|
|
for (i = 0; i < nents_in; i++) {
|
|
vaddr_sg_lo = sg_in[i].virt;
|
|
vaddr_sg_hi = vaddr_sg_lo + sg_in[i].len;
|
|
for (j = 0, io = ldev->device.regions;
|
|
j < (int)ldev->device.num_regions; j++, io++) {
|
|
vaddr_lo = io->virt;
|
|
vaddr_hi = vaddr_lo + io->size;
|
|
if (vaddr_sg_lo >= vaddr_lo &&
|
|
vaddr_sg_hi <= vaddr_hi) {
|
|
break;
|
|
}
|
|
}
|
|
if (j == (int)ldev->device.num_regions) {
|
|
metal_log(METAL_LOG_WARNING,
|
|
"%s,%s: input address isn't MMIO addr: 0x%x,%d.\n",
|
|
__func__, ldev->dev_name, vaddr_sg_lo, sg_in[i].len);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (sg_out != sg_in)
|
|
memcpy(sg_out, sg_in, nents_in*(sizeof(struct metal_sg)));
|
|
return nents_in;
|
|
}
|
|
|
|
static void metal_uio_dev_dma_unmap(struct linux_bus *lbus,
|
|
struct linux_device *ldev,
|
|
uint32_t dir,
|
|
struct metal_sg *sg,
|
|
int nents)
|
|
{
|
|
(void) lbus;
|
|
(void) ldev;
|
|
(void) dir;
|
|
(void) sg;
|
|
(void) nents;
|
|
return;
|
|
}
|
|
|
|
static struct linux_bus linux_bus[] = {
|
|
{
|
|
.bus_name = "platform",
|
|
.drivers = {
|
|
{
|
|
.drv_name = "uio_pdrv_genirq",
|
|
.mod_name = "uio_pdrv_genirq",
|
|
.cls_name = "uio",
|
|
.dev_open = metal_uio_dev_open,
|
|
.dev_close = metal_uio_dev_close,
|
|
.dev_irq_ack = metal_uio_dev_irq_ack,
|
|
.dev_dma_map = metal_uio_dev_dma_map,
|
|
.dev_dma_unmap = metal_uio_dev_dma_unmap,
|
|
},
|
|
{
|
|
.drv_name = "uio_dmem_genirq",
|
|
.mod_name = "uio_dmem_genirq",
|
|
.cls_name = "uio",
|
|
.dev_open = metal_uio_dev_open,
|
|
.dev_close = metal_uio_dev_close,
|
|
.dev_irq_ack = metal_uio_dev_irq_ack,
|
|
.dev_dma_map = metal_uio_dev_dma_map,
|
|
.dev_dma_unmap = metal_uio_dev_dma_unmap,
|
|
},
|
|
{ 0 /* sentinel */ }
|
|
}
|
|
},
|
|
{
|
|
.bus_name = "pci",
|
|
.drivers = {
|
|
{
|
|
.drv_name = "vfio-pci",
|
|
.mod_name = "vfio-pci",
|
|
},
|
|
{
|
|
.drv_name = "uio_pci_generic",
|
|
.mod_name = "uio_pci_generic",
|
|
.cls_name = "uio",
|
|
.dev_open = metal_uio_dev_open,
|
|
.dev_close = metal_uio_dev_close,
|
|
.dev_irq_ack = metal_uio_dev_irq_ack,
|
|
.dev_dma_map = metal_uio_dev_dma_map,
|
|
.dev_dma_unmap = metal_uio_dev_dma_unmap,
|
|
},
|
|
{ 0 /* sentinel */ }
|
|
}
|
|
},
|
|
{
|
|
/* sentinel */
|
|
.bus_name = NULL,
|
|
},
|
|
};
|
|
|
|
#define for_each_linux_bus(lbus) \
|
|
for ((lbus) = linux_bus; (lbus)->bus_name; (lbus)++)
|
|
#define for_each_linux_driver(lbus, ldrv) \
|
|
for ((ldrv) = lbus->drivers; (ldrv)->drv_name; (ldrv)++)
|
|
|
|
|
|
static int metal_linux_dev_open(struct metal_bus *bus,
|
|
const char *dev_name,
|
|
struct metal_device **device)
|
|
{
|
|
struct linux_bus *lbus = to_linux_bus(bus);
|
|
struct linux_device *ldev = NULL;
|
|
struct linux_driver *ldrv;
|
|
int error;
|
|
|
|
ldev = malloc(sizeof(*ldev));
|
|
if (!ldev)
|
|
return -ENOMEM;
|
|
|
|
for_each_linux_driver(lbus, ldrv) {
|
|
|
|
/* Check if we have a viable driver. */
|
|
if (!ldrv->sdrv || !ldrv->dev_open)
|
|
continue;
|
|
|
|
/* Allocate a linux device if we haven't already. */
|
|
if (!ldev)
|
|
ldev = malloc(sizeof(*ldev));
|
|
if (!ldev)
|
|
return -ENOMEM;
|
|
|
|
/* Reset device data. */
|
|
memset(ldev, 0, sizeof(*ldev));
|
|
strncpy(ldev->dev_name, dev_name, sizeof(ldev->dev_name) - 1);
|
|
ldev->fd = -1;
|
|
ldev->ldrv = ldrv;
|
|
ldev->device.bus = bus;
|
|
|
|
/* Try and open the device. */
|
|
error = ldrv->dev_open(lbus, ldev);
|
|
if (error) {
|
|
ldrv->dev_close(lbus, ldev);
|
|
continue;
|
|
}
|
|
|
|
*device = &ldev->device;
|
|
(*device)->name = ldev->dev_name;
|
|
|
|
metal_list_add_tail(&bus->devices, &(*device)->node);
|
|
return 0;
|
|
}
|
|
|
|
if (ldev)
|
|
free(ldev);
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static void metal_linux_dev_close(struct metal_bus *bus,
|
|
struct metal_device *device)
|
|
{
|
|
struct linux_device *ldev = to_linux_device(device);
|
|
struct linux_bus *lbus = to_linux_bus(bus);
|
|
|
|
ldev->ldrv->dev_close(lbus, ldev);
|
|
metal_list_del(&device->node);
|
|
free(ldev);
|
|
}
|
|
|
|
static void metal_linux_bus_close(struct metal_bus *bus)
|
|
{
|
|
struct linux_bus *lbus = to_linux_bus(bus);
|
|
struct linux_driver *ldrv;
|
|
|
|
for_each_linux_driver(lbus, ldrv) {
|
|
if (ldrv->sdrv)
|
|
sysfs_close_driver(ldrv->sdrv);
|
|
ldrv->sdrv = NULL;
|
|
}
|
|
|
|
sysfs_close_bus(lbus->sbus);
|
|
lbus->sbus = NULL;
|
|
}
|
|
|
|
static void metal_linux_dev_irq_ack(struct metal_bus *bus,
|
|
struct metal_device *device,
|
|
int irq)
|
|
{
|
|
struct linux_device *ldev = to_linux_device(device);
|
|
struct linux_bus *lbus = to_linux_bus(bus);
|
|
|
|
return ldev->ldrv->dev_irq_ack(lbus, ldev, irq);
|
|
}
|
|
|
|
static int metal_linux_dev_dma_map(struct metal_bus *bus,
|
|
struct metal_device *device,
|
|
uint32_t dir,
|
|
struct metal_sg *sg_in,
|
|
int nents_in,
|
|
struct metal_sg *sg_out)
|
|
{
|
|
struct linux_device *ldev = to_linux_device(device);
|
|
struct linux_bus *lbus = to_linux_bus(bus);
|
|
|
|
return ldev->ldrv->dev_dma_map(lbus, ldev, dir, sg_in,
|
|
nents_in, sg_out);
|
|
}
|
|
|
|
static void metal_linux_dev_dma_unmap(struct metal_bus *bus,
|
|
struct metal_device *device,
|
|
uint32_t dir,
|
|
struct metal_sg *sg,
|
|
int nents)
|
|
{
|
|
struct linux_device *ldev = to_linux_device(device);
|
|
struct linux_bus *lbus = to_linux_bus(bus);
|
|
|
|
ldev->ldrv->dev_dma_unmap(lbus, ldev, dir, sg,
|
|
nents);
|
|
}
|
|
|
|
static const struct metal_bus_ops metal_linux_bus_ops = {
|
|
.bus_close = metal_linux_bus_close,
|
|
.dev_open = metal_linux_dev_open,
|
|
.dev_close = metal_linux_dev_close,
|
|
.dev_irq_ack = metal_linux_dev_irq_ack,
|
|
.dev_dma_map = metal_linux_dev_dma_map,
|
|
.dev_dma_unmap = metal_linux_dev_dma_unmap,
|
|
};
|
|
|
|
static int metal_linux_register_bus(struct linux_bus *lbus)
|
|
{
|
|
lbus->bus.name = lbus->bus_name;
|
|
lbus->bus.ops = metal_linux_bus_ops;
|
|
return metal_bus_register(&lbus->bus);
|
|
}
|
|
|
|
static int metal_linux_probe_driver(struct linux_bus *lbus,
|
|
struct linux_driver *ldrv)
|
|
{
|
|
char command[256];
|
|
int ret;
|
|
|
|
ldrv->sdrv = sysfs_open_driver(lbus->bus_name, ldrv->drv_name);
|
|
|
|
/* Try probing the module and then open the driver. */
|
|
if (!ldrv->sdrv) {
|
|
ret = snprintf(command, sizeof(command),
|
|
"modprobe %s > /dev/null 2>&1", ldrv->mod_name);
|
|
if (ret >= (int)sizeof(command))
|
|
return -EOVERFLOW;
|
|
ret = system(command);
|
|
if (ret < 0) {
|
|
metal_log(METAL_LOG_WARNING,
|
|
"%s: executing system command '%s' failed.\n",
|
|
__func__, command);
|
|
}
|
|
ldrv->sdrv = sysfs_open_driver(lbus->bus_name, ldrv->drv_name);
|
|
}
|
|
|
|
/* Try sudo probing the module and then open the driver. */
|
|
if (!ldrv->sdrv) {
|
|
ret = snprintf(command, sizeof(command),
|
|
"sudo modprobe %s > /dev/null 2>&1", ldrv->mod_name);
|
|
if (ret >= (int)sizeof(command))
|
|
return -EOVERFLOW;
|
|
ret = system(command);
|
|
if (ret < 0) {
|
|
metal_log(METAL_LOG_WARNING,
|
|
"%s: executing system command '%s' failed.\n",
|
|
__func__, command);
|
|
}
|
|
ldrv->sdrv = sysfs_open_driver(lbus->bus_name, ldrv->drv_name);
|
|
}
|
|
|
|
/* If all else fails... */
|
|
return ldrv->sdrv ? 0 : -ENODEV;
|
|
}
|
|
|
|
static int metal_linux_probe_bus(struct linux_bus *lbus)
|
|
{
|
|
struct linux_driver *ldrv;
|
|
int error = -ENODEV;
|
|
|
|
lbus->sbus = sysfs_open_bus(lbus->bus_name);
|
|
if (!lbus->sbus)
|
|
return -ENODEV;
|
|
|
|
for_each_linux_driver(lbus, ldrv) {
|
|
error = metal_linux_probe_driver(lbus, ldrv);
|
|
if (!error)
|
|
break;
|
|
}
|
|
|
|
if (error) {
|
|
sysfs_close_bus(lbus->sbus);
|
|
lbus->sbus = NULL;
|
|
return error;
|
|
}
|
|
|
|
error = metal_linux_register_bus(lbus);
|
|
if (error) {
|
|
sysfs_close_driver(ldrv->sdrv);
|
|
ldrv->sdrv = NULL;
|
|
sysfs_close_bus(lbus->sbus);
|
|
lbus->sbus = NULL;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int metal_linux_bus_init(void)
|
|
{
|
|
struct linux_bus *lbus;
|
|
int valid = 0;
|
|
|
|
for_each_linux_bus(lbus)
|
|
valid += metal_linux_probe_bus(lbus) ? 0 : 1;
|
|
|
|
return valid ? 0 : -ENODEV;
|
|
}
|
|
|
|
void metal_linux_bus_finish(void)
|
|
{
|
|
struct linux_bus *lbus;
|
|
struct metal_bus *bus;
|
|
|
|
for_each_linux_bus(lbus) {
|
|
if (metal_bus_find(lbus->bus_name, &bus) == 0)
|
|
metal_bus_unregister(bus);
|
|
}
|
|
}
|
|
|
|
int metal_generic_dev_sys_open(struct metal_device *dev)
|
|
{
|
|
(void)dev;
|
|
return 0;
|
|
}
|
|
|
|
int metal_linux_get_device_property(struct metal_device *device,
|
|
const char *property_name,
|
|
void *output, int len)
|
|
{
|
|
int fd = 0;
|
|
int status = 0;
|
|
const int flags = O_RDONLY;
|
|
const int mode = S_IRUSR | S_IRGRP | S_IROTH;
|
|
struct linux_device *ldev = to_linux_device(device);
|
|
char path[PATH_MAX];
|
|
|
|
snprintf(path, sizeof(path), "%s/of_node/%s",
|
|
ldev->sdev->path, property_name);
|
|
fd = open(path, flags, mode);
|
|
if (fd < 0)
|
|
return -errno;
|
|
status = read(fd, output, len);
|
|
|
|
return status < 0 ? -errno : 0;
|
|
}
|
|
|