建立工程,成功创建两个虚拟串口

This commit is contained in:
ranchuan
2023-06-21 18:00:56 +08:00
commit 3604192d8f
872 changed files with 428764 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
collect (PROJECT_LIB_HEADERS alloc.h)
collect (PROJECT_LIB_HEADERS assert.h)
collect (PROJECT_LIB_HEADERS cache.h)
collect (PROJECT_LIB_HEADERS condition.h)
collect (PROJECT_LIB_HEADERS io.h)
collect (PROJECT_LIB_HEADERS irq.h)
collect (PROJECT_LIB_HEADERS log.h)
collect (PROJECT_LIB_HEADERS mutex.h)
collect (PROJECT_LIB_HEADERS sleep.h)
collect (PROJECT_LIB_HEADERS sys.h)
collect (PROJECT_LIB_SOURCES condition.c)
collect (PROJECT_LIB_SOURCES device.c)
collect (PROJECT_LIB_SOURCES init.c)
collect (PROJECT_LIB_SOURCES irq.c)
collect (PROJECT_LIB_SOURCES shmem.c)
collect (PROJECT_LIB_SOURCES time.c)
collect (PROJECT_LIB_SOURCES utilities.c)
# vim: expandtab:ts=2:sw=2:smartindent

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2016, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/alloc.h
* @brief linux memory allocattion definitions.
*/
#ifndef __METAL_ALLOC__H__
#error "Include metal/alloc.h instead of metal/linux/alloc.h"
#endif
#ifndef __METAL_LINUX_ALLOC__H__
#define __METAL_LINUX_ALLOC__H__
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
static inline void *metal_allocate_memory(unsigned int size)
{
return (malloc(size));
}
static inline void metal_free_memory(void *ptr)
{
free(ptr);
}
#ifdef __cplusplus
}
#endif
#endif /* __METAL_LINUX_ALLOC__H__ */

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2018, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file assert.h
* @brief Linux assertion support.
*/
#ifndef __METAL_ASSERT__H__
#error "Include metal/assert.h instead of metal/linux/assert.h"
#endif
#ifndef __METAL_LINUX_ASSERT__H__
#define __METAL_LINUX_ASSERT__H__
#include <assert.h>
/**
* @brief Assertion macro for Linux-based applications.
* @param cond Condition to evaluate.
*/
#define metal_sys_assert(cond) assert(cond)
#endif /* __METAL_LINUX_ASSERT__H__ */

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2018, Linaro Limited. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/cache.h
* @brief Linux cache operation primitives for libmetal.
*/
#ifndef __METAL_CACHE__H__
#error "Include metal/cache.h instead of metal/linux/cache.h"
#endif
#ifndef __METAL_LINUX_CACHE__H__
#define __METAL_LINUX_CACHE__H__
#include <metal/utilities.h>
#ifdef __cplusplus
extern "C" {
#endif
static inline void __metal_cache_flush(void *addr, unsigned int len)
{
/** Do nothing.
* Do not flush cache from Linux userspace.
*/
metal_unused(addr);
metal_unused(len);
return;
}
static inline void __metal_cache_invalidate(void *addr, unsigned int len)
{
/** Do nothing.
* Do not flush cache from Linux userspace.
*/
metal_unused(addr);
metal_unused(len);
return;
}
#ifdef __cplusplus
}
#endif
#endif /* __METAL_LINUX_CACHE__H__ */

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2016, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file generic/condition.c
* @brief Generic libmetal condition variable handling.
*/
#include <metal/condition.h>
int metal_condition_wait(struct metal_condition *cv,
metal_mutex_t *m)
{
metal_mutex_t *tmpm = 0;
int v = 0;
/* Check if the mutex has been acquired */
if (!cv || !m || !metal_mutex_is_acquired(m))
return -EINVAL;
if (!atomic_compare_exchange_strong(&cv->m, &tmpm, m)) {
if (m != tmpm)
return -EINVAL;
}
v = atomic_load(&cv->wakeups);
atomic_fetch_add(&cv->waiters, 1);
/* Release the mutex before sleeping. */
metal_mutex_release(m);
syscall(SYS_futex, &cv->wakeups, FUTEX_WAIT, v, NULL, NULL, 0);
atomic_fetch_sub(&cv->waiters, 1);
/* Acquire the mutex after it's waken up. */
metal_mutex_acquire(m);
return 0;
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2016, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/condition.h
* @brief Linux condition variable primitives for libmetal.
*/
#ifndef __METAL_CONDITION__H__
#error "Include metal/condition.h instead of metal/linux/condition.h"
#endif
#ifndef __METAL_LINUX_CONDITION__H__
#define __METAL_LINUX_CONDITION__H__
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <metal/atomic.h>
#include <stdint.h>
#include <limits.h>
#include <metal/errno.h>
#ifdef __cplusplus
extern "C" {
#endif
struct metal_condition {
metal_mutex_t *m; /**< mutex.
The condition variable is attached to
this mutex when it is waiting.
It is also used to check correctness
in case there are multiple waiters. */
atomic_int waiters; /**< number of waiters. */
atomic_int wakeups; /**< number of wakeups. */
};
/** Static metal condition variable initialization. */
#define METAL_CONDITION_INIT { NULL, ATOMIC_VAR_INIT(0), ATOMIC_VAR_INIT(0) }
static inline void metal_condition_init(struct metal_condition *cv)
{
cv->m = NULL;
atomic_init(&cv->waiters, 0);
atomic_init(&cv->wakeups, 0);
}
static inline int metal_condition_signal(struct metal_condition *cv)
{
if (!cv)
return -EINVAL;
atomic_fetch_add(&cv->wakeups, 1);
if (atomic_load(&cv->waiters) > 0)
syscall(SYS_futex, &cv->wakeups, FUTEX_WAKE, 1, NULL, NULL, 0);
return 0;
}
static inline int metal_condition_broadcast(struct metal_condition *cv)
{
if (!cv)
return -EINVAL;
atomic_fetch_add(&cv->wakeups, 1);
if (atomic_load(&cv->waiters) > 0)
syscall(SYS_futex, &cv->wakeups, FUTEX_WAKE, INT_MAX, NULL, NULL, 0);
return 0;
}
#endif /* __METAL_LINUX_CONDITION__H__ */

View File

@@ -0,0 +1,669 @@
/*
* 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;
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/init.c
* @brief Linux libmetal initialization.
*/
#include <sys/types.h>
#include <metal/sys.h>
#include <metal/utilities.h>
struct metal_state _metal;
extern int metal_linux_irq_init();
extern void metal_linux_irq_shutdown();
/** Sort function for page size array. */
static int metal_pagesize_compare(const void *_a, const void *_b)
{
const struct metal_page_size *a = _a, *b = _b;
long diff = a->page_size - b->page_size;
return metal_sign(diff);
}
static int metal_add_page_size(const char *path, int shift, int mmap_flags)
{
int index = _metal.num_page_sizes;
unsigned long size = 1UL << shift;
if (index >= MAX_PAGE_SIZES) {
metal_log(METAL_LOG_WARNING, "skipped page size %ld - overflow\n",
size);
return -EOVERFLOW;
}
if (!path || shift <= 0) {
metal_log(METAL_LOG_WARNING, "skipped page size %ld - invalid args\n",
size);
return -EINVAL;
}
_metal.page_sizes[index].page_shift = shift;
_metal.page_sizes[index].page_size = size;
_metal.page_sizes[index].mmap_flags = mmap_flags;
strncpy(_metal.page_sizes[index].path, path, PATH_MAX);
_metal.num_page_sizes ++;
metal_log(METAL_LOG_DEBUG, "added page size %ld @%s\n", size, path);
return 0;
}
static int metal_init_page_sizes(void)
{
const int max_sizes = MAX_PAGE_SIZES - 1;
long sizes[max_sizes];
/* Determine system page size. */
sizes[0] = getpagesize();
if (sizes[0] <= 0) {
metal_log(METAL_LOG_ERROR, "failed to get page size\n");
return -ENOSYS;
}
_metal.page_size = sizes[0];
_metal.page_shift = metal_log2(sizes[0]);
metal_add_page_size(_metal.tmp_path, _metal.page_shift, 0);
#ifdef HAVE_HUGETLBFS_H
#ifndef MAP_HUGE_SHIFT
/* System does not support multiple huge page sizes. */
sizes[0] = gethugepagesize();
if (sizes[0] > 0) {
metal_add_page_size(hugetlbfs_find_path(),
metal_log2(sizes[0]),
MAP_HUGETLB);
}
#else
if (gethugepagesize() >= 0) {
int i, count;
/* System supports multiple huge page sizes. */
count = gethugepagesizes(sizes, max_sizes);
for (i = 0; i < count; i++) {
int shift = metal_log2(sizes[i]);
if ((shift & MAP_HUGE_MASK) != shift)
continue;
metal_add_page_size(
hugetlbfs_find_path_for_size(sizes[i]),
shift, (MAP_HUGETLB |
(shift << MAP_HUGE_SHIFT)));
}
}
#endif
#endif
/* Finally sort the resulting array by size. */
qsort(_metal.page_sizes, _metal.num_page_sizes,
sizeof(struct metal_page_size), metal_pagesize_compare);
return 0;
}
int metal_sys_init(const struct metal_init_params *params)
{
static char sysfs_path[SYSFS_PATH_MAX];
const char *tmp_path;
unsigned int seed;
FILE* urandom;
int result;
/* Determine sysfs mount point. */
result = sysfs_get_mnt_path(sysfs_path, sizeof(sysfs_path));
if (result) {
metal_log(METAL_LOG_ERROR, "failed to get sysfs path (%s)\n",
strerror(-result));
return result;
}
_metal.sysfs_path = sysfs_path;
/* Find the temporary directory location. */
tmp_path = getenv("TMPDIR");
if (!tmp_path)
tmp_path = "/tmp";
_metal.tmp_path = tmp_path;
/* Initialize the pseudo-random number generator. */
urandom = fopen("/dev/urandom", "r");
if (!urandom) {
metal_log(METAL_LOG_ERROR, "failed to open /dev/urandom (%s)\n",
strerror(errno));
return -errno;
}
if (fread(&seed, 1, sizeof(seed), urandom) <= 0) {
metal_log(METAL_LOG_DEBUG, "Failed fread /dev/urandom\n");
}
fclose(urandom);
srand(seed);
result = metal_init_page_sizes();
if (result < 0)
return result;
result = metal_linux_bus_init();
if (result < 0)
return result;
result = open("/proc/self/pagemap", O_RDONLY | O_CLOEXEC);
if (result < 0) {
metal_log(METAL_LOG_DEBUG, "Failed pagemap open - %s\n",
strerror(errno));
}
_metal.pagemap_fd = result;
metal_unused(params);
/* Initialize IRQ handling */
metal_linux_irq_init();
return 0;
}
void metal_sys_finish(void)
{
/* Shutdown IRQ handling */
metal_linux_irq_shutdown();
metal_linux_bus_finish();
close(_metal.pagemap_fd);
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2017, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/io.h
* @brief Linux specific io definitions.
*/
#ifndef __METAL_IO__H__
#error "Include metal/io.h instead of metal/linux/io.h"
#endif
#ifndef __METAL_LINUX_IO__H__
#define __METAL_LINUX_IO__H__
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef METAL_INTERNAL
#define metal_sys_io_mem_map(...)
#endif
#ifdef __cplusplus
}
#endif
#endif /* __METAL_LINUX_IO__H__ */

View File

@@ -0,0 +1,374 @@
/*
* Copyright (c) 2016, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* @file linux/irq.c
* @brief Linux libmetal irq operations
*/
#include <pthread.h>
#include <sched.h>
#include <metal/device.h>
#include <metal/irq.h>
#include <metal/sys.h>
#include <metal/mutex.h>
#include <metal/list.h>
#include <metal/utilities.h>
#include <metal/alloc.h>
#include <sys/time.h>
#include <sys/eventfd.h>
#include <stdint.h>
#include <metal/errno.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
#define MAX_IRQS FD_SETSIZE /**< maximum number of irqs */
#define METAL_IRQ_STOP 0xFFFFFFFF /**< stop interrupts handling thread */
/** IRQ handler descriptor structure */
struct metal_irq_hddesc {
metal_irq_handler hd; /**< irq handler */
struct metal_device *dev; /**< metal device */
void *drv_id; /**< id to identify the driver
of the irq handler*/
struct metal_list list; /**< handler list container */
};
struct metal_irqs_state {
struct metal_irq_hddesc hds[MAX_IRQS]; /**< irqs handlers descriptor */
signed char irq_reg_stat[MAX_IRQS]; /**< irqs registration statistics.
It restore how many handlers have
been registered for each IRQ. */
int irq_reg_fd; /**< irqs registration notification file
descriptor */
metal_mutex_t irq_lock; /**< irq handling lock */
unsigned int irq_state; /**< global irq handling state */
pthread_t irq_pthread; /**< irq handling thread id */
};
struct metal_irqs_state _irqs;
int metal_irq_register(int irq,
metal_irq_handler hd,
struct metal_device *dev,
void *drv_id)
{
uint64_t val = 1;
struct metal_irq_hddesc *hd_desc;
struct metal_list *h_node;
int ret;
if ((irq < 0) || (irq >= MAX_IRQS)) {
metal_log(METAL_LOG_ERROR,
"%s: irq %d is larger than the max supported %d.\n",
__func__, irq, MAX_IRQS - 1);
return -EINVAL;
}
if ((drv_id == NULL) || (hd == NULL)) {
metal_log(METAL_LOG_ERROR, "%s: irq %d need drv_id and hd.\n",
__func__, irq);
return -EINVAL;
}
metal_mutex_acquire(&_irqs.irq_lock);
if (_irqs.irq_state == METAL_IRQ_STOP) {
metal_log(METAL_LOG_ERROR,
"%s: failed. metal IRQ handling has stopped.\n",
__func__);
metal_mutex_release(&_irqs.irq_lock);
return -EINVAL;
}
metal_list_for_each(&_irqs.hds[irq].list, h_node) {
hd_desc = metal_container_of(h_node, struct metal_irq_hddesc, list);
/* if drv_id already exist reject */
if ((hd_desc->drv_id == drv_id) &&
((dev == NULL) || (hd_desc->dev == dev))) {
metal_log(METAL_LOG_ERROR, "%s: irq %d already registered."
"Will not register again.\n",
__func__, irq);
metal_mutex_release(&_irqs.irq_lock);
return -EINVAL;
}
/* drv_id not used, get out of metal_list_for_each */
break;
}
/* Add to the end */
hd_desc = metal_allocate_memory(sizeof(struct metal_irq_hddesc));
if (hd_desc == NULL) {
metal_log(METAL_LOG_ERROR,
"%s: irq %d cannot allocate mem for drv_id %d.\n",
__func__, irq, drv_id);
metal_mutex_release(&_irqs.irq_lock);
return -ENOMEM;
}
hd_desc->hd = hd;
hd_desc->drv_id = drv_id;
hd_desc->dev = dev;
metal_list_add_tail(&_irqs.hds[irq].list, &hd_desc->list);
_irqs.irq_reg_stat[irq]++;
metal_mutex_release(&_irqs.irq_lock);
ret = write(_irqs.irq_reg_fd, &val, sizeof(val));
if (ret < 0) {
metal_log(METAL_LOG_DEBUG, "%s: write failed IRQ %d\n", __func__, irq);
}
metal_log(METAL_LOG_DEBUG, "%s: registered IRQ %d\n", __func__, irq);
return 0;
}
int metal_irq_unregister(int irq,
metal_irq_handler hd,
struct metal_device *dev,
void *drv_id)
{
uint64_t val = 1;
struct metal_irq_hddesc *hd_desc;
struct metal_list *h_node;
int ret;
unsigned int delete_count = 0;
if ((irq < 0) || (irq >= MAX_IRQS)) {
metal_log(METAL_LOG_ERROR,
"%s: irq %d is larger than the max supported %d.\n",
__func__, irq, MAX_IRQS);
return -EINVAL;
}
metal_mutex_acquire(&_irqs.irq_lock);
if (_irqs.irq_state == METAL_IRQ_STOP) {
metal_log(METAL_LOG_ERROR,
"%s: failed. metal IRQ handling has stopped.\n", __func__);
metal_mutex_release(&_irqs.irq_lock);
return -EINVAL;
}
if (!hd && !drv_id && !dev) {
if (0 == _irqs.irq_reg_stat[irq])
goto no_entry;
_irqs.irq_reg_stat[irq] = 0;
goto out;
}
/* Search through handlers */
metal_list_for_each(&_irqs.hds[irq].list, h_node) {
hd_desc = metal_container_of(h_node, struct metal_irq_hddesc, list);
if (((hd == NULL) || (hd_desc->hd == hd)) &&
((drv_id == NULL) || (hd_desc->drv_id == drv_id)) &&
((dev == NULL) || (hd_desc->dev == dev))) {
if (_irqs.irq_reg_stat[irq] > 0)
_irqs.irq_reg_stat[irq]--;
h_node = h_node->prev;
metal_list_del(h_node->next);
metal_free_memory(hd_desc);
delete_count++;
}
}
if (delete_count)
goto out;
no_entry:
metal_log(METAL_LOG_DEBUG, "%s: No matching entry.\n", __func__);
metal_mutex_release(&_irqs.irq_lock);
return -ENOENT;
out:
metal_mutex_release(&_irqs.irq_lock);
ret = write(_irqs.irq_reg_fd, &val, sizeof(val));
if (ret < 0) {
metal_log(METAL_LOG_DEBUG, "%s: write failed IRQ %d\n", __func__, irq);
}
metal_log(METAL_LOG_DEBUG, "%s: unregistered IRQ %d (%d)\n", __func__, irq, delete_count);
return 0;
}
unsigned int metal_irq_save_disable()
{
metal_mutex_acquire(&_irqs.irq_lock);
return 0;
}
void metal_irq_restore_enable(unsigned flags)
{
(void)flags;
metal_mutex_release(&_irqs.irq_lock);
}
void metal_irq_enable(unsigned int vector)
{
(void)vector;
}
void metal_irq_disable(unsigned int vector)
{
(void)vector;
}
/**
* @brief IRQ handler
* @param[in] args not used. required for pthread.
*/
static void *metal_linux_irq_handling(void *args)
{
struct sched_param param;
uint64_t val;
int ret;
int i, j, pfds_total;
struct pollfd *pfds;
(void) args;
pfds = (struct pollfd *)malloc(MAX_IRQS * sizeof(struct pollfd));
if (!pfds) {
metal_log(METAL_LOG_ERROR, "%s: failed to allocate irq fds mem.\n",
__func__);
return NULL;
}
param.sched_priority = sched_get_priority_max(SCHED_FIFO);
/* Ignore the set scheduler error */
ret = sched_setscheduler(0, SCHED_FIFO, &param);
if (ret) {
metal_log(METAL_LOG_WARNING, "%s: Failed to set scheduler: %d.\n",
__func__, ret);
}
while (1) {
metal_mutex_acquire(&_irqs.irq_lock);
if (_irqs.irq_state == METAL_IRQ_STOP) {
/* Killing this IRQ handling thread */
metal_mutex_release(&_irqs.irq_lock);
break;
}
/* Get the fdset */
memset(pfds, 0, MAX_IRQS * sizeof(struct pollfd));
pfds[0].fd = _irqs.irq_reg_fd;
pfds[0].events = POLLIN;
for(i = 0, j = 1; i < MAX_IRQS && j < MAX_IRQS; i++) {
if (_irqs.irq_reg_stat[i] > 0) {
pfds[j].fd = i;
pfds[j].events = POLLIN;
j++;
}
}
metal_mutex_release(&_irqs.irq_lock);
/* Wait for interrupt */
ret = poll(pfds, j, -1);
if (ret < 0) {
metal_log(METAL_LOG_ERROR, "%s: poll() failed: %s.\n",
__func__, strerror(errno));
break;
}
/* Waken up from interrupt */
pfds_total = j;
for (i = 0; i < pfds_total; i++) {
if ( (pfds[i].fd == _irqs.irq_reg_fd) &&
(pfds[i].revents & (POLLIN | POLLRDNORM))) {
/* IRQ registration change notification */
if (read(pfds[i].fd, (void*)&val, sizeof(uint64_t)) < 0)
metal_log(METAL_LOG_ERROR,
"%s, read irq fd %d failed.\n",
__func__, pfds[i].fd);
} else if ((pfds[i].revents & (POLLIN | POLLRDNORM))) {
struct metal_irq_hddesc *hd_desc; /**< irq handler descriptor */
struct metal_device *dev = NULL; /**< metal device IRQ belongs to */
int irq_handled = 0; /**< flag to indicate if irq is handled */
struct metal_list *h_node;
metal_list_for_each(&_irqs.hds[pfds[i].fd].list, h_node) {
hd_desc = metal_container_of(h_node, struct metal_irq_hddesc, list);
metal_mutex_acquire(&_irqs.irq_lock);
if (!dev)
dev = hd_desc->dev;
metal_mutex_release(&_irqs.irq_lock);
if ((hd_desc->hd)(pfds[i].fd, hd_desc->drv_id) == METAL_IRQ_HANDLED)
irq_handled = 1;
}
if (irq_handled) {
if (dev && dev->bus->ops.dev_irq_ack)
dev->bus->ops.dev_irq_ack(dev->bus, dev, i);
}
} else if (pfds[i].revents) {
metal_log(METAL_LOG_DEBUG,
"%s: poll unexpected. fd %d: %d\n",
__func__, pfds[i].fd, pfds[i].revents);
}
}
}
free(pfds);
return NULL;
}
/**
* @brief irq handling initialization
* @return 0 on sucess, non-zero on failure
*/
int metal_linux_irq_init()
{
int ret, irq;
memset(&_irqs, 0, sizeof(_irqs));
/* init handlers list for each interrupt in table */
for (irq=0; irq < MAX_IRQS; irq++) {
metal_list_init(&_irqs.hds[irq].list);
}
_irqs.irq_reg_fd = eventfd(0,0);
if (_irqs.irq_reg_fd < 0) {
metal_log(METAL_LOG_ERROR, "Failed to create eventfd for IRQ handling.\n");
return -EAGAIN;
}
metal_mutex_init(&_irqs.irq_lock);
ret = pthread_create(&_irqs.irq_pthread, NULL,
metal_linux_irq_handling, NULL);
if (ret != 0) {
metal_log(METAL_LOG_ERROR, "Failed to create IRQ thread: %d.\n", ret);
return -EAGAIN;
}
return 0;
}
/**
* @brief irq handling shutdown
*/
void metal_linux_irq_shutdown()
{
int ret;
uint64_t val = 1;
metal_log(METAL_LOG_DEBUG, "%s\n", __func__);
metal_mutex_acquire(&_irqs.irq_lock);
_irqs.irq_state = METAL_IRQ_STOP;
metal_mutex_release(&_irqs.irq_lock);
ret = write (_irqs.irq_reg_fd, &val, sizeof(val));
if (ret < 0) {
metal_log(METAL_LOG_ERROR, "Failed to write.\n");
}
ret = pthread_join(_irqs.irq_pthread, NULL);
if (ret) {
metal_log(METAL_LOG_ERROR, "Failed to join IRQ thread: %d.\n", ret);
}
close(_irqs.irq_reg_fd);
metal_mutex_deinit(&_irqs.irq_lock);
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2016, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/irq.h
* @brief Linux libmetal irq definitions.
*/
#ifndef __METAL_IRQ__H__
#error "Include metal/irq.h instead of metal/linux/irq.h"
#endif
#ifndef __METAL_LINUX_IRQ__H__
#define __METAL_LINUX_IRQ__H__
#endif /* __METAL_LINUX_IRQ__H__ */

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2018, Linaro Limited. and Contributors. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of Linaro nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* @file linux/log.h
* @brief Linux libmetal log handler definition.
*/
#ifndef __METAL_METAL_LOG__H__
#error "Include metal/log.h instead of metal/linux/log.h"
#endif
#ifndef __METAL_LINUX_LOG__H__
#define __METAL_LINUX_LOG__H__
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* __METAL_LINUX_LOG__H__ */

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/mutex.h
* @brief Linux mutex primitives for libmetal.
*/
#ifndef __METAL_MUTEX__H__
#error "Include metal/mutex.h instead of metal/linux/mutex.h"
#endif
#ifndef __METAL_LINUX_MUTEX__H__
#define __METAL_LINUX_MUTEX__H__
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <metal/atomic.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
atomic_int v;
} metal_mutex_t;
/*
* METAL_MUTEX_INIT - used for initializing an mutex elmenet in a static struct
* or global
*/
#define METAL_MUTEX_INIT(m) { ATOMIC_VAR_INIT(0) }
/*
* METAL_MUTEX_DEFINE - used for defining and initializing a global or
* static singleton mutex
*/
#define METAL_MUTEX_DEFINE(m) metal_mutex_t m = METAL_MUTEX_INIT(m)
static inline int __metal_mutex_cmpxchg(metal_mutex_t *mutex,
int exp, int val)
{
atomic_compare_exchange_strong(&mutex->v, (int *)&exp, val);
return exp;
}
static inline void __metal_mutex_init(metal_mutex_t *mutex)
{
atomic_store(&mutex->v, 0);
}
static inline void __metal_mutex_deinit(metal_mutex_t *mutex)
{
(void)mutex;
}
static inline int __metal_mutex_try_acquire(metal_mutex_t *mutex)
{
int val = 0;
return atomic_compare_exchange_strong(&mutex->v, &val, 1);
}
static inline void __metal_mutex_acquire(metal_mutex_t *mutex)
{
int c = 0;
if (atomic_compare_exchange_strong(&mutex->v, &c, 1))
return;
if (c != 2)
c = atomic_exchange(&mutex->v, 2);
while (c != 0) {
syscall(SYS_futex, &mutex->v, FUTEX_WAIT, 2, NULL, NULL, 0);
c = atomic_exchange(&mutex->v, 2);
}
}
static inline void __metal_mutex_release(metal_mutex_t *mutex)
{
if (atomic_fetch_sub(&mutex->v, 1) != 1) {
atomic_store(&mutex->v, 0);
syscall(SYS_futex, &mutex->v, FUTEX_WAKE, 1, NULL, NULL, 0);
}
}
static inline int __metal_mutex_is_acquired(metal_mutex_t *mutex)
{
return atomic_load(&mutex->v);
}
#ifdef __cplusplus
}
#endif
#endif /* __METAL_LINUX_MUTEX__H__ */

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/shmem.c
* @brief Linux libmetal shared memory handling.
*/
#include <metal/shmem.h>
#include <metal/sys.h>
#include <metal/utilities.h>
struct metal_shmem {
struct metal_io_region io;
metal_phys_addr_t *phys;
};
static void metal_shmem_io_close(struct metal_io_region *io)
{
metal_unmap(io->virt, io->size);
free((void *)io->physmap);
}
static const struct metal_io_ops metal_shmem_io_ops = {
NULL, NULL, NULL, NULL, NULL, metal_shmem_io_close
};
static int metal_shmem_try_map(struct metal_page_size *ps, int fd, size_t size,
struct metal_io_region **result)
{
size_t pages, page, phys_size;
struct metal_io_region *io;
metal_phys_addr_t *phys;
uint8_t *virt;
void *mem;
int error;
size = metal_align_up(size, ps->page_size);
pages = size / ps->page_size;
error = metal_map(fd, 0, size, 1, ps->mmap_flags, &mem);
if (error) {
metal_log(METAL_LOG_ERROR, "failed to mmap shmem - %s\n",
strerror(-error));
return error;
}
error = metal_mlock(mem, size);
if (error) {
metal_log(METAL_LOG_WARNING, "failed to mlock shmem - %s\n",
strerror(-error));
}
phys_size = sizeof(*phys) * pages;
phys = malloc(phys_size);
if (!phys) {
metal_unmap(mem, size);
return -ENOMEM;
}
io = malloc(sizeof(*io));
if (!io) {
free(phys);
metal_unmap(mem, size);
return -ENOMEM;
}
if (_metal.pagemap_fd < 0) {
phys[0] = 0;
metal_log(METAL_LOG_WARNING,
"shmem - failed to get va2pa mapping. use offset as pa.\n");
metal_io_init(io, mem, phys, size, -1, 0, &metal_shmem_io_ops);
} else {
for (virt = mem, page = 0; page < pages; page++) {
size_t offset = page * ps->page_size;
error = metal_virt2phys(virt + offset, &phys[page]);
if (error < 0)
phys[page] = METAL_BAD_OFFSET;
}
metal_io_init(io, mem, phys, size, ps->page_shift, 0,
&metal_shmem_io_ops);
}
*result = io;
return 0;
}
int metal_shmem_open(const char *name, size_t size,
struct metal_io_region **result)
{
struct metal_page_size *ps;
int fd, error;
error = metal_shmem_open_generic(name, size, result);
if (!error)
return error;
error = metal_open(name, 1);
if (error < 0) {
metal_log(METAL_LOG_ERROR, "Failed to open shmem file :%s\n", name);
return error;
}
fd = error;
/* Iterate through page sizes in decreasing order. */
metal_for_each_page_size_down(ps) {
if (ps->page_size > 2 * size)
continue;
error = metal_shmem_try_map(ps, fd, size, result);
if (!error)
break;
}
close(fd);
return error;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2018, Linaro Limited. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/sleep.h
* @brief linux sleep primitives for libmetal.
*/
#ifndef __METAL_SLEEP__H__
#error "Include metal/sleep.h instead of metal/linux/sleep.h"
#endif
#ifndef __METAL_LINUX_SLEEP__H__
#define __METAL_LINUX_SLEEP__H__
#include <unistd.h>
#ifdef __cplusplus
extern "C" {
#endif
static inline int __metal_sleep_usec(unsigned int usec)
{
return usleep(usec);
}
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* __METAL_LINUX_SLEEP__H__ */

View File

@@ -0,0 +1,145 @@
/*
* Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/sys.h
* @brief Linux system primitives for libmetal.
*/
#ifndef __METAL_SYS__H__
#error "Include metal/sys.h instead of metal/linux/sys.h"
#endif
#ifndef __METAL_LINUX_SYS__H__
#define __METAL_LINUX_SYS__H__
#include <metal/errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/futex.h>
#include <sysfs/libsysfs.h>
#ifdef HAVE_HUGETLBFS_H
#include <hugetlbfs.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define METAL_INVALID_VADDR NULL
#define MAX_PAGE_SIZES 32
struct metal_device;
/** Structure of shared page or hugepage sized data. */
struct metal_page_size {
/** Page size. */
size_t page_size;
/** Page shift. */
unsigned long page_shift;
/** Path to hugetlbfs (or tmpfs) mount point. */
char path[PATH_MAX];
/** Flags to use for mmap. */
int mmap_flags;
};
/** Structure of linux specific libmetal runtime state. */
struct metal_state {
/** Common (system independent) data. */
struct metal_common_state common;
/** file descriptor for shared data. */
int data_fd;
/** system page size. */
unsigned long page_size;
/** system page shift. */
unsigned long page_shift;
/** sysfs mount point. */
const char *sysfs_path;
/** sysfs mount point. */
const char *tmp_path;
/** available page sizes. */
struct metal_page_size page_sizes[MAX_PAGE_SIZES];
/** number of available page sizes. */
int num_page_sizes;
/** File descriptor for /proc/self/pagemap (or -1). */
int pagemap_fd;
};
#ifdef METAL_INTERNAL
extern int metal_linux_bus_init(void);
extern void metal_linux_bus_finish(void);
extern int metal_open(const char *path, int shm);
extern int metal_open_unlinked(const char *path, int shm);
extern int metal_mktemp(char *template, int fifo);
extern int metal_mktemp_unlinked(char *template);
extern int metal_map(int fd, off_t offset, size_t size, int expand,
int flags, void **result);
extern int metal_unmap(void *mem, size_t size);
extern int metal_mlock(void *mem, size_t size);
extern void metal_randomize_string(char *template);
extern void metal_mktemp_template(char template[PATH_MAX],
const char *name);
extern int metal_virt2phys(void *addr, unsigned long *phys);
/**
* @brief Read a device tree property of a device
*
* @param[in] device metal_device of the intended DT node
* @param[in] property_name name of the property to be read
* @param[out] output output buffer to store read data
* @param[in] len number of bytes to be read
* @return 0 on success, or -errno on error.
*/
extern int metal_linux_get_device_property(struct metal_device *device,
const char *property_name,
void *output, int len);
#define metal_for_each_page_size_up(ps) \
for ((ps) = &_metal.page_sizes[0]; \
(ps) <= &_metal.page_sizes[_metal.num_page_sizes - 1]; \
(ps)++)
#define metal_for_each_page_size_down(ps) \
for ((ps) = &_metal.page_sizes[_metal.num_page_sizes - 1]; \
(ps) >= &_metal.page_sizes[0]; \
(ps)--)
#endif
#ifdef __cplusplus
}
#endif
#endif /* __METAL_LINUX_SYS__H__ */

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2016, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file linux/time.c
* @brief Linux libmetal time handling.
*/
#include <unistd.h>
#include <time.h>
#include <metal/time.h>
#define NS_PER_S (1000 * 1000 * 1000)
unsigned long long metal_get_timestamp(void)
{
unsigned long long t = 0;
struct timespec tp;
int r;
r = clock_gettime(CLOCK_MONOTONIC, &tp);
if (r == -1) {
metal_log(METAL_LOG_ERROR,"clock_gettime failed!\n");
return t;
} else {
t = tp.tv_sec * (NS_PER_S);
t += tp.tv_nsec;
}
return t;
}

View File

@@ -0,0 +1,288 @@
/*
* Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* @file utils.c
* @brief Linux libmetal utility functions.
*/
#include <metal/utilities.h>
#include <metal/sys.h>
/**
* @brief Open (or create) a file.
*
* This function opens or creates a file with read/write permissions and the
* O_CLOEXEC flag set.
*
* @param[in] path File path to open.
* @param[in] shm Open shared memory (via shm_open) if non-zero.
* @return File descriptor.
*/
int metal_open(const char *path, int shm)
{
const int flags = O_RDWR | O_CREAT | O_CLOEXEC;
const int mode = S_IRUSR | S_IWUSR;
int fd;
if (!path || !strlen(path))
return -EINVAL;
fd = shm ? shm_open(path, flags, mode) : open(path, flags, mode);
return fd < 0 ? -errno : fd;
}
/**
* @brief Open (or create) and unlink a file.
*
* This function opens or creates a file with read/write permissions and the
* O_CLOEXEC flag set. This file is then unlinked to ensure that it is removed
* when the last reference to the file is dropped. This ensures that libmetal
* shared maps are released appropriately once all users of the shared data
* exit.
*
* @param[in] path File path to open.
* @param[in] shm Open shared memory (via shm_open) if non-zero.
* @return File descriptor.
*/
int metal_open_unlinked(const char *path, int shm)
{
int fd, error;
fd = metal_open(path, shm);
if (fd < 0)
return fd;
error = shm ? shm_unlink(path) : unlink(path);
error = error < 0 ? -errno : 0;
if (error)
close(fd);
return error ? error : fd;
}
/**
* @brief Randomize a string.
*
* This function randomizes the contents of a string.
*
* @param[in] template String to be randomized.
*/
void metal_randomize_string(char *template)
{
const char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-=";
while (*template) {
*template++ = chars[rand() % 64];
}
}
/**
* @brief Create a file name template suitable for use with metal_mktemp.
*
* @param[out] template Template string to be filled in.
* @param[in] name Module/user identifier used as part of the
* generated template.
*/
void metal_mktemp_template(char template[PATH_MAX], const char *name)
{
snprintf(template, PATH_MAX, "%s/metal-%s-XXXXXX",
_metal.tmp_path, name);
}
/**
* @brief Create a temporary file or fifo.
*
* This function creates a temporary file or fifo, and sets the O_CLOEXEC flag
* on it.
*
* @param[in] template File name template (the last 6 characters must
* be XXXXXX per mkstemp requirements).
* @param[in] fifo Non-zero to create a FIFO instead of a regular
* file.
* @return File descriptor.
*/
int metal_mktemp(char *template, int fifo)
{
const int mode = S_IRUSR | S_IWUSR;
int result, len, flags;
char *suffix;
if (!template)
return -EINVAL;
len = strlen(template);
suffix = &template[len - 6];
if (len < 6 || strcmp(suffix, "XXXXXX")) {
metal_log(METAL_LOG_ERROR, "template %s has no trailing pattern\n",
template);
return -EINVAL;
}
flags = (fifo
? O_RDONLY | O_NONBLOCK | O_CLOEXEC
: O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC);
for (;;) {
metal_randomize_string(suffix);
if (fifo) {
result = mkfifo(template, mode);
if (result < 0) {
if (errno == EEXIST)
continue;
metal_log(METAL_LOG_ERROR, "mkfifo(%s) failed (%s)\n",
template, strerror(errno));
return -errno;
}
}
result = open(template, flags, mode);
if (result < 0) {
if (fifo)
unlink(template);
if (errno == EEXIST)
continue;
metal_log(METAL_LOG_ERROR, "open() failed (%s)\n",
strerror(errno));
return -errno;
}
return result;
}
}
/**
* @brief Open (or create) and unlink a temporary file.
*
* This function creates a temporary file, and sets the O_CLOEXEC flag on it.
* O_CLOEXEC flag set. This file is then unlinked to ensure that it is removed
* when the last reference to the file is dropped. This ensures that libmetal
* shared maps are released appropriately once all users of the shared data
* exit.
*
* @param[in] template File name template (the last 6 characters must
* be XXXXXX per mkstemp requirements).
* @return File descriptor.
*/
int metal_mktemp_unlinked(char *template)
{
int fd, error;
fd = metal_mktemp(template, 0);
if (fd < 0)
return fd;
error = unlink(template) < 0 ? -errno : 0;
if (error)
close(fd);
return error ? error : fd;
}
/**
* @brief Map a segment of a file/device.
*
* This function maps a segment of a file or device into the process address
* space, after optionally expanding the file if necessary. If required, the
* file is expanded to hold the requested map area. This is done under and
* advisory lock, and therefore the called must not have an advisory lock on
* the file being mmapped.
*
* @param[in] fd File descriptor to map.
* @param[in] offset Offset in file to map.
* @param[in] size Size of region to map.
* @param[in] expand Allow file expansion via ftruncate if non-zero.
* @param[in] flags Flags for mmap(), MAP_SHARED included implicitly.
* @param[out] result Returned pointer to new memory map.
* @return 0 on success, or -errno on error.
*/
int metal_map(int fd, off_t offset, size_t size, int expand, int flags,
void **result)
{
int prot = PROT_READ | PROT_WRITE, error;
void *mem;
flags |= MAP_SHARED;
if (fd < 0) {
fd = -1;
flags = MAP_PRIVATE | MAP_ANONYMOUS;
} else if (expand) {
off_t reqsize = offset + size;
struct stat stat;
error = flock(fd, LOCK_EX) < 0 ? -errno : 0;
if (!error)
error = fstat(fd, &stat);
if (!error && stat.st_size < reqsize)
error = ftruncate(fd, reqsize);
if (!error)
flock(fd, LOCK_UN);
if (error)
return -errno;
}
mem = mmap(NULL, size, prot, flags, fd, offset);
if (mem == MAP_FAILED)
return -errno;
*result = mem;
return 0;
}
/**
* @brief Unmap a segment of the process address space.
*
* This function unmaps a segment of the process address space.
*
* @param[in] mem Segment to unmap.
* @param[in] size Size of region to unmap.
* @return 0 on success, or -errno on error.
*/
int metal_unmap(void *mem, size_t size)
{
return munmap(mem, size) < 0 ? -errno : 0;
}
/**
* @brief Lock in a region of the process address space.
*
* @param[in] mem Pointer to start of region.
* @param[in] size Size of region.
* @return 0 on success, or -errno on error.
*/
int metal_mlock(void *mem, size_t size)
{
return mlock(mem, size) ? -errno : 0;
}
int metal_virt2phys(void *addr, unsigned long *phys)
{
off_t offset;
uint64_t entry;
int error;
if (_metal.pagemap_fd < 0)
return -ENOSYS;
offset = ((uintptr_t)addr >> _metal.page_shift) * sizeof(entry);
error = pread(_metal.pagemap_fd, &entry, sizeof(entry), offset);
if (error < 0) {
metal_log(METAL_LOG_ERROR, "failed pagemap pread (offset %llx) - %s\n",
(unsigned long long)offset, strerror(errno));
return -errno;
}
/* Check page present and not swapped. */
if ((entry >> 62) != 2) {
metal_log(METAL_LOG_ERROR, "pagemap page not present, %llx -> %llx\n",
(unsigned long long)offset, (unsigned long long)entry);
return -ENOENT;
}
*phys = (entry & ((1ULL << 54) - 1)) << _metal.page_shift;
return 0;
}