Commit 58e29b61 authored by Miguel Jimenez Lopez's avatar Miguel Jimenez Lopez

sw: First version for the fmc-dio driver and related tools.

parent a76d4009
# include parent_common.mk for buildsystem's defines
#use absolute path for REPO_PARENT
REPO_PARENT=$(shell /bin/pwd)/..
-include $(REPO_PARENT)/parent_common.mk
# export git tree for subdirs
GIT_VERSION ?= $(shell git describe --dirty --long --tags)
export GIT_VERSION
# by default use the fmc-bus within the repository
FMC_BUS ?= fmc-bus
# FMC_BUS_ABS has to be absolut path, due to beeing passed to the Kbuild
FMC_BUS_ABS ?= $(abspath $(FMC_BUS) )
export FMC_BUS_ABS
DIRS = $(FMC_BUS_ABS) kernel tools
.PHONY: all clean modules install modules_install $(DIRS)
all clean modules install modules_install: $(DIRS)
clean: TARGET = clean
modules: TARGET = modules
install: TARGET = install
modules_install: TARGET = modules_install
$(DIRS):
$(MAKE) -C $@ $(TARGET)
$(FMC_BUS_ABS): fmc-bus-init_repo
# init submodule if missing
fmc-bus-init_repo:
@test -d $(FMC_BUS_ABS)/doc || ( echo "Checking out submodule $(FMC_BUS_ABS)"; git submodule update --init $(FMC_BUS_ABS) )
kernel: $(FMC_BUS_ABS)
KBUILD_EXTRA_SYMBOLS := $(FMC_BUS_ABS)/kernel/Module.symvers
# add versions of supermodule. It is useful when spec-sw is included as sub-module
# of a bigger project that we want to track
ifdef CONFIG_SUPER_REPO
ifdef CONFIG_SUPER_REPO_VERSION
SUBMODULE_VERSIONS += MODULE_INFO(version_$(CONFIG_SUPER_REPO),\"$(CONFIG_SUPER_REPO_VERSION)\");
endif
endif
# add versions of used submodules
SUBMODULE_VERSIONS += MODULE_INFO(version_fmc_bus,\"$(FMC_BUS_VERSION)\");
ccflags-y += -DADDITIONAL_VERSIONS="$(SUBMODULE_VERSIONS)"
# The library includes <sdb.h>, so point -I directtly there
# include our header before to avoid conflicts with the kernel
LINUXINCLUDE := -I$(FMC_BUS_ABS)/kernel/include -I$(src)/include/linux $(LINUXINCLUDE)
ccflags-y += -I$(src)/include
ccflags-y += -DGIT_VERSION=\"$(GIT_VERSION)\"
obj-m += wr-dio.o
wr-dio-y = fmc-dio.o
wr-dio-y += fmc-dio-internal.o
wr-dio-y += fmc-dio-mdev.o
wr-dio-y += fmc-dio-gpio.o
\ No newline at end of file
# include parent_common.mk for buildsystem's defines
#use absolute path for REPO_PARENT
REPO_PARENT=$(shell /bin/pwd)/../..
-include $(REPO_PARENT)/parent_common.mk
LINUX ?= /lib/modules/$(shell uname -r)/build
# by default use the fmc-bus within the repository
FMC_BUS ?= ../fmc-bus/
# FMC_BUS_ABS has to be absolut path, due to beeing passed to the Kbuild
FMC_BUS_ABS ?= $(abspath $(FMC_BUS) )
GIT_VERSION ?= $(shell git describe --dirty --long --tags)
export GIT_VERSION
FMC_BUS_VERSION ?= $(shell cd $(FMC_BUS_ABS); git describe --always --dirty --long --tags)
export FMC_BUS_VERSION
all modules:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) FMC_BUS_ABS=$(FMC_BUS_ABS) modules
install modules_install:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) modules_install
# be able to run the "clean" rule even if $(LINUX) is not valid
clean:
rm -rf *.o *~ .*.cmd *.ko *.mod.c .tmp_versions Module.symvers \
Module.markers modules.order
\ No newline at end of file
/*
* Copyright (C) 2019 CERN (www.sevensols.com)
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/slab.h>
#include <linux/fmc.h>
#include "fmc-dio.h"
static int fmc_dio_gpio_input(struct gpio_chip *chip, unsigned offset)
{
return -EAGAIN;
}
static int fmc_dio_gpio_output(struct gpio_chip *chip, unsigned offset, int value)
{
return -EAGAIN;
}
int fmc_dio_gpio_get(struct gpio_chip *chip, unsigned offset)
{
return -EAGAIN;
}
void fmc_dio_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
return;
}
static const char *fmc_dio_gpio_names[] = {
"dire", "fare", "baciare", "lettera", "testamento"
};
static struct gpio_chip fmc_dio_gpio_template = {
.label = "fmc-dio",
.owner = THIS_MODULE,
/* FIXME: request, free, for multi-function operation */
.direction_input = fmc_dio_gpio_input,
.direction_output = fmc_dio_gpio_output,
.get = fmc_dio_gpio_get,
.set = fmc_dio_gpio_set,
.base = -1, /* request dynamic */
.ngpio = 5,
.names = fmc_dio_gpio_names,
};
int fmc_dio_gpio_init(struct fmc_dio *dev)
{
struct fmc_device *fmc = dev->fmc;
struct gpio_chip *gc;
int ret;
gc = devm_kzalloc(&dev->fmc->dev, sizeof(*gc), GFP_KERNEL);
if (!gc)
return -ENOMEM;
*gc = fmc_dio_gpio_template;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
gc->dev = &fmc->dev;
#else
gc->parent = &fmc->dev;
#endif
ret = gpiochip_add(gc);
if (ret < 0)
goto out_free;
dev->gc = gc;
/* FIXME: program the DAC for each port (sysfs attributes?) */
return 0;
out_free:
kfree(gc);
return ret;
}
void fmc_dio_gpio_exit(struct fmc_dio *dev)
{
struct gpio_chip *gc = dev->gc;
#if LINUX_VERSION_CODE > KERNEL_VERSION(3,17,0)
gpiochip_remove(gc);
#else
int ret;
ret = gpiochip_remove(gc);
if (ret)
dev_err(&dio->pdev->dev, "DANGER %i! gpio chip can't be removed\n",
ret);
#endif
return;
}
This diff is collapsed.
/*
* Copyright (C) 2019 CERN (www.sevensols.com)
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#include <linux/module.h>
#include <linux/version.h>
#include "fmc-dio.h"
static int fmc_dio_mdev_open(struct inode *inode, struct file *file)
{
struct miscdevice *mdev_ptr = file->private_data;
// WARN: Update the private_data pointer to point to fmc_dio structure
file->private_data = container_of(mdev_ptr, struct fmc_dio, mdev);
return 0;
}
static int fmc_dio_mdev_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t fmc_dio_mdev_write(struct file *file, const char __user *buf,
size_t count, loff_t *offp)
{
return 0;
}
static ssize_t fmc_dio_mdev_read (struct file *file, char __user *buf,
size_t count, loff_t *offp)
{
return 0;
}
static long fmc_dio_mdev_unlocked_ioctl (struct file *file, unsigned int cmd,
unsigned long arg)
{
struct fmc_dio *dio = file->private_data;
return fmc_dio_int_ioctl(dio,cmd,arg);
}
static const struct file_operations fmc_dio_mdev_fops = {
.owner = THIS_MODULE,
.open = fmc_dio_mdev_open,
.release = fmc_dio_mdev_release,
.write = fmc_dio_mdev_write,
.read = fmc_dio_mdev_read,
.unlocked_ioctl = fmc_dio_mdev_unlocked_ioctl,
};
int fmc_dio_mdev_create(struct fmc_dio *dio)
{
dio->mdev.minor = MISC_DYNAMIC_MINOR;
dio->mdev.fops = &fmc_dio_mdev_fops;
dio->mdev.name = dio->name;
return misc_register(&dio->mdev);
}
void fmc_dio_mdev_destroy(struct fmc_dio *dio)
{
misc_deregister(&dio->mdev);
}
/*
* Copyright (C) 2019 Seven Solutions (www.sevensols.com)
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/delay.h>
#include <linux/fmc.h>
#include <linux/fmc-sdb.h>
#include "fmc-dio.h"
/* Temporary pointer to the last FMC DIO */
static struct fmc_dio *last_dio;
/**
* FMC DIO Interrupt Service Routing (ISR)
*
* @param irq IRQ number
* @param dev_id Device identifier for the ISR
*
* @return IRQ_HANDLED if interrupt was handled properly and IRQ_NONE otherwise
*/
irqreturn_t fmc_dio_interrupt(int irq, void *dev_id)
{
irqreturn_t ret;
ret = fmc_dio_int_interrupt(dev_id);
return ret;
}
// Macros for the memory-mapped hardware resources
#define FMC_DIO_RESOURCE_DIO 0
#define FMC_DIO_RESOURCE_GPIO 1
#define FMC_DIO_RESOURCE_PPSG 2
/**
* This function creates the hardware resources for the FMC DIO.
*
* @param dio FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_resource_map(struct fmc_dio *dio)
{
int i,err;
struct platform_device *dev = dio->pdev;
struct fmc_device *fmc = dio->fmc;
struct resource *r;
void __iomem *mem;
// Map the resources
for(i = 0 ; i < dev->num_resources ; i++) {
r = platform_get_resource(dev, IORESOURCE_MEM, i);
if (!r || !r->start)
continue;
mem = ioremap(r->start, r->end + 1 - r->start);
if (!mem) {
dev_err(&dev->dev, "Remap for res %i %pr failed\n",
i, r);
return -ENOMEM;
}
switch(i)
{
case FMC_DIO_RESOURCE_DIO:
dio->dio = mem;
break;
case FMC_DIO_RESOURCE_GPIO:
dio->gpio = mem;
break;
case FMC_DIO_RESOURCE_PPSG:
dio->ppsg = mem;
break;
default:
/* Ugh? What is this? */
break;
};
}
// If some hardware resource is not detected, fail
if(!dio->dio || !dio->gpio || !dio->ppsg) {
printk("%s: ERROR in FPGA base pointers initialization. \n",__func__);
return -EINVAL;
}
// Map the IRQ
dio->irq = platform_get_irq(dev,0);
if(dio->irq < 0) {
printk("%s: ERROR in FMC DIO IRQ extraction. \n",__func__);
return -EINVAL;
}
err = request_any_context_irq(dio->irq, fmc_dio_interrupt,
IRQF_TRIGGER_HIGH, dio->name, dio);
if(err < 0) {
printk("%s: ERROR in request IRQ (%d). \n",__func__,err);
return err;
}
return 0;
}
/**
* This function releases the hardware resources for the FMC DIO.
*
* @param dio FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_resource_release(struct fmc_dio *dio)
{
// Release IRQ for FMC DIO
free_irq(dio->irq, dio);
return 0;
}
/**
* FMC DIO probe function for the platform_device
*
* @param dev FMC DIO platform device
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_pl_probe(struct platform_device *dev)
{
/* NOTE: fmc_device is created by the same driver than platform_device
* and it had filled the private data structure pointer of the
* platform_device before registering it.
*/
struct fmc_device *fmc;
struct fmc_dio *dio;
int ret = 0;
unsigned int devfn;
unsigned char busn;
dio = last_dio;
if(!dio) {
printk("%s: DIO internal structure is NULL \n",__func__);
ret = -EINVAL;
goto exit;
}
fmc = dio->fmc;
if(!fmc) {
printk("%s: FMC device is NULL \n",__func__);
ret = -EINVAL;
goto exit;
}
/* Set up the FMC DIO name for the Linux Miscdevice */
busn = (unsigned char) (fmc->device_id >> 8);
devfn = (unsigned int) (fmc->device_id & 0xFF);
snprintf(dio->name,FMC_DIO_NAME_MAX,
FMC_DIO_NAME_PATTERN,
busn,devfn);
// Specific board info
dio->board = dev->id_entry->driver_data;
dio->pdev = dev;
/* Map the resources for the FMC DIO */
ret = fmc_dio_resource_map(dio);
if(ret)
goto exit;
ret = fmc_dio_internal_create(dio);
if(ret)
goto exit;
/* Create a Linux Miscdevice for the FMC DIO */
ret = fmc_dio_mdev_create(dio);
if(ret)
goto exit;
/* Create Linux GPIO for the FMC DIO */
ret = fmc_dio_gpio_init(dio);
if(ret)
goto exit;
exit:
return ret;
}
/**
* FMC DIO remove function for the platform_device
*
* @param dev FMC DIO platform device
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_pl_remove(struct platform_device *dev)
{
struct fmc_device *fmc;
struct fmc_dio *dio;
int ret = 0;
dio = last_dio;
fmc = dio->fmc;
/* Release Linux GPIO for the FMC DIO */
fmc_dio_gpio_exit(dio);
/* Destroy the Linux Miscdevice for the FMC DIO */
fmc_dio_mdev_destroy(dio);
/* Destroy internal DIO structure */
fmc_dio_internal_destroy(dio);
/* Release the resources for the FMC DIO */
ret = fmc_dio_resource_release(dio);
if(ret)
return ret;
return ret;
}
/* Forward declarations */
int fmc_dio_probe(struct fmc_device *fmc);
int fmc_dio_remove(struct fmc_device *fmc);
/**
* List of supported platform
*/
static const struct platform_device_id fmc_dio_id_table[] = {
{ /* SPEC compatible */
.name = "fmc-dio-spec",
.driver_data = FMC_DIO_BOARD_SPEC,
},
{},
};
// FMC DIO platform driver
static struct platform_driver fmc_dio_pl_drv = {
.probe = fmc_dio_pl_probe,
.remove = fmc_dio_pl_remove,
.id_table = fmc_dio_id_table,
/* No suspend or resume by now */
.driver = {
.name = "fmc-dio",
.owner = THIS_MODULE,
},
};
// FMC DIO FRU identifier
static struct fmc_fru_id fd_fru_id[] = {
{
.product_name = "FmcDio5cha",
},
};
// FMC DIO driver structure (FMC)
static struct fmc_driver fmc_dio_drv = {
.version = FMC_VERSION,
.driver.name = KBUILD_MODNAME,
.probe = fmc_dio_probe,
.remove = fmc_dio_remove,
.id_table = {
.fru_id = fd_fru_id,
.fru_id_nr = ARRAY_SIZE(fd_fru_id),
},
};
/**
* FMC DIO probe function
*
* This function initializes the FMC DIO data structures.
*
* @param fmc FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
int fmc_dio_probe(struct fmc_device *fmc)
{
struct fmc_dio *dio;
int ret = 0;
/* Driver data */
dio = devm_kzalloc(&fmc->dev, sizeof(*dio), GFP_KERNEL);
if (!dio)
return -ENOMEM;
fmc_set_drvdata(fmc, dio);
dio->fmc = fmc;
/* FIXME: Recover the DIO internal structure pointer for a global temporary var */
last_dio = dio;
/* Now register the platform_driver */
ret = platform_driver_register(&fmc_dio_pl_drv);
if (ret < 0) {
fmc_driver_unregister(&fmc_dio_drv);
}
return ret;
}
/**
* FMC DIO remove function
*
* This function releases the FMC DIO data structures.
*
* @param fmc FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
int fmc_dio_remove(struct fmc_device *fmc)
{
//struct fmc_dio *dio = fmc_get_drvdata(fmc);
/* Free FMC DIO internal structure */
/* NOTE: Here is not necessary to free the memory because
* we use devm_kmalloc in the probe function
*/
platform_driver_unregister(&fmc_dio_pl_drv);
return 0;
}
/**
* FMC DIO init function
*
* This function is the entry point for the FMC DIO driver.
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_init(void)
{
int ret;
ret = fmc_driver_register(&fmc_dio_drv);
return ret;
}
/**
* FMC DIO init function
*
* This function is the exit point for the FMC DIO driver.
*
*/
static void fmc_dio_exit(void)
{
fmc_driver_unregister(&fmc_dio_drv);
}
module_init(fmc_dio_init);
module_exit(fmc_dio_exit);
/* If no gpio lib is there, this weak applies */
/**
* FMC DIO GPIO init function
*
* @param dev FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
int __weak fmc_dio_gpio_init(struct fmc_dio *dev)
{
return 0;
}
/**
* FMC DIO GPIO exit function
*
* @param dev FMC DIO device
*/
void __weak fmc_dio_gpio_exit(struct fmc_dio *dev)
{
return;
}
MODULE_VERSION(GIT_VERSION);
MODULE_LICENSE("GPL");
/*
* Copyright (C) 2019 CERN (www.sevensols.com)
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#ifdef __KERNEL__
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/irqreturn.h>
#include <linux/fmc.h>
//FIXME: Back hack to maintain the IOCTL command numbers
#include <linux/sockios.h>
#include "hw/wr-dio-regs.h"
#include "hw/ppsg-regs.h"
/* Forward declaration */
struct fmc_dio;
/* This is somehow generic, but I find no better place at this time */
#ifndef SET_HI32
# if BITS_PER_LONG > 32
# define SET_HI32(var, value) ((var) |= (long)(value) << 32)
# define GET_HI32(var) ((var) >> 32)
# else
# define SET_HI32(var, value) ((var) |= 0)
# define GET_HI32(var) 0
# endif
#endif
/* For GPIO we have no wb-gen header */
struct fmc_dio_gpio_block {
uint32_t clear;
uint32_t set;
uint32_t dir;
uint32_t status;
};
/* And this is our bit mapping */
#define FMC_DIO_GPIO_VALUE(bit) (1 << ((4 * (bit)) + 0))
/* FMC DIO name string pattern */
#define FMC_DIO_NAME_PATTERN "fmc-dio-%d:%d"
/* Size of the name field of the fmc_dio structure */
#define FMC_DIO_NAME_MAX 20
/* Board constants */
#define FMC_DIO_BOARD_SPEC 0
/**
* @brief FMC DIO structure
*
* This contains information regarding FMC DIO.
*/
struct fmc_dio {
char name[FMC_DIO_NAME_MAX]; /**< Name of the FMC DIO */
struct miscdevice mdev; /**< Linux Miscdevice for the FMC DIO */
struct platform_device *pdev; /**< Linux platform_device for the FMC DIO */
struct fmc_device *fmc; /** FMC device for the FMC DIO */
struct gpio_chip *gc; /**< Linux GPIO chip */
void __iomem *dio; /**< DIO IP block pointer */
void __iomem *gpio; /**< GPIO IP block pointer */
void __iomem *ppsg; /**< PPSG IP block pointer */
const char *irqdomain_name; /**< IRQ domain name */
int irq; /**< IRQ number for DIO FMC */
int board; /**< Board info */
void *priv; /**< Private data for FMC DIO */
};
// From fmc-dio-internal.c
extern irqreturn_t fmc_dio_int_interrupt(struct fmc_dio *dev);
extern int fmc_dio_int_ioctl(struct fmc_dio *dev, unsigned int ioctlcmd,
unsigned long arg);
extern int fmc_dio_internal_create(struct fmc_dio *dev);
extern void fmc_dio_internal_destroy(struct fmc_dio *dev);
// From fmc-dio-mdev.c
extern int fmc_dio_mdev_create(struct fmc_dio *dio);
extern void fmc_dio_mdev_destroy(struct fmc_dio *dio);
// From fmc-dio-gpio.c
extern int fmc_dio_gpio_init(struct fmc_dio *dev);
extern void fmc_dio_gpio_exit(struct fmc_dio *dev);
#endif /* __KERNEL__ */
// FIXME: Move IOCTL command numbers?
#define PRIV_MEZZANINE_ID (SIOCDEVPRIVATE + 14)
#define PRIV_MEZZANINE_CMD (SIOCDEVPRIVATE + 15)
// FMC DIO Commands
enum wr_dio_cmd_name {
WR_DIO_CMD_PULSE,
WR_DIO_CMD_STAMP,
WR_DIO_CMD_DAC,
WR_DIO_CMD_INOUT,
};
/*
* This is how parameters are used (K == reply from kernel):
*
* CMD_PULSE:
* cmd->flags: F_NOW, F_REL, F_LOOP
* cmd->channel: the channel or the mask
* cmd->t[]: either 2 or 3 values (start, duration, loop)
* cmd->value: count of loops (0 to turn off)
*
* CMD_STAMP:
* cmd->flags: F_MASK, F_WAIT
* cmd->channel: the channel or the mask
* K: cmd->channel: the channel where we had stamps
* K: cmd->nstamp: number of valid stamps
* K: cmd->t[]: the stamps
*
* CMD_DAC:
* cmd->flags: none
* cmd->channel: which one
* cmd->value: the value
*
* CMD_INOUT:
* cmd->flags: F_MASK
* cmd->channel: the channel or the mask
* cmd->value: bits 0..4: WR-DIO, 8..12 value, 16..20 OEN, 24..28 term
*
*/
#define WR_DIO_INOUT_DIO (1 << 0)
#define WR_DIO_INOUT_VALUE (1 << 8)
#define WR_DIO_INOUT_OUTPUT (1 << 16)
#define WR_DIO_INOUT_TERM (1 << 24)
#define WR_DIO_N_STAMP 16 /* At least 5 * 3 */
struct wr_dio_cmd {
uint16_t command; /* from user */
uint16_t channel; /* 0..4 or mask from user */
uint32_t value; /* for DAC or I/O */
uint32_t flags;
uint32_t nstamp; /* from kernel, if IN_STAMP */
struct timespec t[WR_DIO_N_STAMP]; /* may be from user */
};
#define WR_DIO_F_NOW 0x01 /* Output is now, t[0] ignored */
#define WR_DIO_F_REL 0x02 /* t[0].tv_sec is relative */
#define WR_DIO_F_MASK 0x04 /* Channel is 0x00..0x1f */
#define WR_DIO_F_LOOP 0x08 /* Output should loop: t[2] is looping*/
#define WR_DIO_F_WAIT 0x10 /* Wait for event */
# This Makefile is used to reproduce the headers from svn checkout.
# You need to have "wbgen2" in your command search path and the white-rabbit
# repos (wr-cores and wr-switch-hdl) Git checkout in $REPOS.
# Since this is only meant to be used by me (or Tom) no serious checking is done.
# List of input files in Git checkout
MODULES_WRS ?= $(REPOS)/wr-switch-hdl/modules
MODULES_WRC ?= $(REPOS)/wr-cores/modules
#SPECS = $(HW_REPO)/trunk/documentation/specifications
WB_PPSG = $(MODULES_WRC)/wr_pps_gen/pps_gen_wb.wb
HEADERS = ppsg-regs.h
WBINPUT = $(HEADERS:.h=wb)
# No default, for people who types "make" everywhere (like me)
all:
@echo "This is for developer's use, see Makefile for details"
exit 1
# The headers rule regenerates headers from local wb files
headers: $(HEADERS)
%.h: %.wb
wbgen2 --cstyle=struct --co=$@ $<
sed -i 's,#include <inttypes.h>,#ifdef __KERNEL__\n#include <linux/types.h>\n#else\n#include <stdint.h>\n#endif\n,' $@
sed -i '/ Created *: .*20[0-9][0-9]$$/ d' $@
sed -i 's/-REGS_WB//' $@
# The wbinput rule just copies here stuff from svn.
# Do it silent so errors stand out
wbinput:
@cp $(WB_ENDPOINT) endpoint-regs.wb
@cp $(WB_MDIO) endpoint-mdio.wb
@cp $(WB_PPSG) ppsg-regs.wb
@cp $(WB_TSTAMP) tstamp-regs.wb
@cp $(WB_RTU) rtu-regs.wb
@cp $(WB_NIC) nic-regs.wb
@cp $(WB_SOFTPLL) softpll-regs.wb
@echo "Copied input files from subversions to local directory"
The headers have been derived from what is in svn:
documentation/specifications/hdlspec/memory_map/
In that directory you find the html generated from the wb files.
Here I import the relevant headers. The overall register
map is in ../nic/nic-hardware.h .
The .wb files whence the headers are generated come from different
plases in the white rabbit svn. To ease myself I wrote this in
the Makefile. You can "make wbinput" to get a fresh copy of them,
provided you have SVN set in your environment (point to the root
checkout, before "trunk" subdirectory). If unset or wrong, cp fails.
With "make headers" you can regenerate the headers from the wb input
files. Each generated file is postprocesses with sed to fix these
issues:
* generated files include <inttypes.h> as they use uint32_t. We want
<linux/types.h> instead, as no <inttypes.h> nor <stdint.h> is there
* generated files include the date of creation. This brings to noise
in the "git diff" or plain "diff", so I'd better have no date.
* creation of "#ifdef __THIS_HEADER__" fails on the dash, so I remove
the dash and trailing part with sed (as of writing, it has issues with
pathnames too, this is one the reasons why I copy the wb here first).
/*
Register definitions for slave core: WR Switch PPS generator and RTC
* File : ppsg-regs.h
* Author : auto-generated by wbgen2 from ppsg-regs.wb
* Standard : ANSI C
THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE ppsg-regs.wb
DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY!
*/
#ifndef __WBGEN2_REGDEFS_PPSG
#define __WBGEN2_REGDEFS_PPSG
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <stdint.h>
#endif
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
#else
#error "Unsupported compiler?"
#endif
#ifndef __WBGEN2_MACROS_DEFINED__
#define __WBGEN2_MACROS_DEFINED__
#define WBGEN2_GEN_MASK(offset, size) (((1<<(size))-1) << (offset))
#define WBGEN2_GEN_WRITE(value, offset, size) (((value) & ((1<<(size))-1)) << (offset))
#define WBGEN2_GEN_READ(reg, offset, size) (((reg) >> (offset)) & ((1<<(size))-1))
#define WBGEN2_SIGN_EXTEND(value, bits) (((value) & (1<<bits) ? ~((1<<(bits))-1): 0 ) | (value))
#endif
/* definitions for register: Control Register */
/* definitions for field: Reset counter in reg: Control Register */
#define PPSG_CR_CNT_RST WBGEN2_GEN_MASK(0, 1)
/* definitions for field: Enable counter in reg: Control Register */
#define PPSG_CR_CNT_EN WBGEN2_GEN_MASK(1, 1)
/* definitions for field: Adjust offset in reg: Control Register */
#define PPSG_CR_CNT_ADJ WBGEN2_GEN_MASK(2, 1)
/* definitions for field: Set time in reg: Control Register */
#define PPSG_CR_CNT_SET WBGEN2_GEN_MASK(3, 1)
/* definitions for field: PPS Pulse width in reg: Control Register */
#define PPSG_CR_PWIDTH_MASK WBGEN2_GEN_MASK(4, 28)
#define PPSG_CR_PWIDTH_SHIFT 4
#define PPSG_CR_PWIDTH_W(value) WBGEN2_GEN_WRITE(value, 4, 28)
#define PPSG_CR_PWIDTH_R(reg) WBGEN2_GEN_READ(reg, 4, 28)
/* definitions for register: Nanosecond counter register */
/* definitions for register: UTC Counter register (least-significant part) */
/* definitions for register: UTC Counter register (most-significant part) */
/* definitions for register: Nanosecond adjustment register */
/* definitions for register: UTC Adjustment register (least-significant part) */
/* definitions for register: UTC Adjustment register (most-significant part) */
/* definitions for register: External sync control register */
/* definitions for field: Sync to external PPS input in reg: External sync control register */
#define PPSG_ESCR_SYNC WBGEN2_GEN_MASK(0, 1)
/* definitions for field: PPS output valid in reg: External sync control register */
#define PPSG_ESCR_PPS_VALID WBGEN2_GEN_MASK(1, 1)
/* definitions for field: Timecode output(UTC+cycles) valid in reg: External sync control register */
#define PPSG_ESCR_TM_VALID WBGEN2_GEN_MASK(2, 1)
PACKED struct PPSG_WB {
/* [0x0]: REG Control Register */
uint32_t CR;
/* [0x4]: REG Nanosecond counter register */
uint32_t CNTR_NSEC;
/* [0x8]: REG UTC Counter register (least-significant part) */
uint32_t CNTR_UTCLO;
/* [0xc]: REG UTC Counter register (most-significant part) */
uint32_t CNTR_UTCHI;
/* [0x10]: REG Nanosecond adjustment register */
uint32_t ADJ_NSEC;
/* [0x14]: REG UTC Adjustment register (least-significant part) */
uint32_t ADJ_UTCLO;
/* [0x18]: REG UTC Adjustment register (most-significant part) */
uint32_t ADJ_UTCHI;
/* [0x1c]: REG External sync control register */
uint32_t ESCR;
};
#endif
-- -*- Mode: LUA; tab-width: 2 -*-
peripheral {
name = "WR Switch PPS generator and RTC";
description = "Unit generating PPS signals and acting as a UTC real-time clock";
hdl_entity = "pps_gen_wb";
prefix = "ppsg";
reg {
name = "Control Register";
prefix = "CR";
field {
name = "Reset counter";
description = "write 1: resets the counter\
write 0: no effect";
prefix = "CNT_RST";
type = MONOSTABLE;
clock = "refclk_i";
};
field {
name = "Enable counter";
description = "1: PPS counter is enabled";
prefix = "CNT_EN";
type = BIT;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
clock = "refclk_i";
};
field {
name = "Adjust offset";
description = "write 1: Starts adjusting PPS/UTC offsets by adding the values taken from ADJ_NSEC, ADJ_UTCLO, ADJ_UTCHI registers to the current PPS counter value. These registers need to be programmed prior to update.\
write 0: no effect\
read 0: adjustment operation is done\
read 1: adjustment operation is in progress";
prefix = "CNT_ADJ";
type = BIT;
access_bus = READ_WRITE;
access_dev = READ_WRITE;
load = LOAD_EXT;
clock = "refclk_i";
};
field {
name = "Set time";
description = "write 1: Sets the UTC/PPS counter to values taken from ADJ_NSEC, ADJ_UTCLO, ADJ_UTCHI registers";
prefix = "CNT_SET";
type = MONOSTABLE;
clock = "refclk_i";
};
field {
name = "PPS Pulse width";
description = "Width of generated PPS pulses in 125 MHz refernce clock cycles";
prefix = "PWIDTH";
size = 28;
type = SLV;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
clock= "refclk_i";
};
};
reg {
name = "Nanosecond counter register";
description = "Nanosecond part of current time, expressed as number of 125 MHz reference clock cycles";
prefix = "CNTR_NSEC";
field {
name = "Nanosecond counter";
type = SLV;
size = 28;
access_bus = READ_ONLY;
access_dev = WRITE_ONLY;
clock = "refclk_i";
};
};
reg {
name = "UTC Counter register (least-significant part)";
description = "Lower 32 bits of current UTC time";
prefix = "CNTR_UTCLO";
field {
name = "UTC Counter";
type = SLV;
size = 32;
access_bus = READ_ONLY;
access_dev = WRITE_ONLY;
clock = "refclk_i";
};
};
reg {
name = "UTC Counter register (most-significant part)";
description = "Highest 8 bits of current UTC time";
prefix = "CNTR_UTCHI";
field {
name = "UTC Counter";
type = SLV;
size = 8;
access_bus = READ_ONLY;
access_dev = WRITE_ONLY;
clock = "refclk_i";
};
};
reg {
name = "Nanosecond adjustment register";
description = "Adjustment value for nanosecond counter";
prefix = "ADJ_NSEC";
field {
name = "Nanosecond adjustment";
type = PASS_THROUGH;
size = 28;
};
};
reg {
name = "UTC Adjustment register (least-significant part)";
description = "Lower 32 bits of adjustment value for UTC";
prefix = "ADJ_UTCLO";
field {
name = "UTC Counter adjustment";
type = PASS_THROUGH;
size = 32;
};
};
reg {
name = "UTC Adjustment register (most-significant part)";
description = "Highest 8 bits of adjustment value for UTC";
prefix = "ADJ_UTCHI";
field {
name = "UTC Counter adjustment";
type = PASS_THROUGH;
size = 8;
};
};
reg {
name = "External sync control register";
prefix = "ESCR";
field {
name = "Sync to external PPS input";
description = "write 1: Waits until a pulse on external PPS input arrives and re-synchronizes the PPS counter to it\
write 0: no effect\
read 1: external synchronization done\
read 0: external synchronization in progress";
type = BIT;
prefix = "SYNC";
access_bus = READ_WRITE;
access_dev = READ_WRITE;
load = LOAD_EXT;
clock = "refclk_i";
};
field {
name = "PPS output valid";
description = "write 1: PPS output provides reliable 1-PPS signal\
write 0: PPS output is invalid";
prefix = "PPS_VALID";
type = BIT;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
clock = "refclk_i";
};
field {
name = "Timecode output(UTC+cycles) valid";
description = "write 1: Timecode output provides valid time\
write 0: Timecode output does not provide valid time";
prefix = "TM_VALID";
type = BIT;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
clock = "refclk_i";
};
};
};
This diff is collapsed.
This diff is collapsed.
libspec.a
libspec.so
spec-cl
specmem
spec-fwloader
spec-vuart
wr-dio-cmd
wr-dio-pps
wr-dio-agent
wr-dio-ruler
stamp-frame
Makefile.specific
\ No newline at end of file
# If it exists includes Makefile.specific. In this Makefile, you should put
# specific Makefile code that you want to run before this. For example,
# build a particular environment.
-include Makefile.specific
# include parent_common.mk for buildsystem's defines
REPO_PARENT=../..
-include $(REPO_PARENT)/parent_common.mk
GIT_VERSION ?= $(shell git describe --dirty --long --tags)
CFLAGS += -ggdb -Wall -fPIC -I../kernel $(EXTRACFLAGS)
CFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\"
INSTALL = install
DESTDIR =
PREFIX = /usr/local
BINDIR = $(PREFIX)/sbin
LIBDIR = $(PREFIX)/lib
PROGS += wr-dio-cmd wr-dio-pps wr-dio-agent wr-dio-ruler
PROGS += stamp-frame
all: $(LIB) $(PROGS) $(LIBSHARED)
$(PROGS):
install:
$(INSTALL) -d $(DESTDIR)$(BINDIR)
$(INSTALL) -m 0755 -t $(DESTDIR)$(BINDIR) $(PROGS)
clean:
rm -f *.o $(PROGS) *~
# add the other unused targets, so the rule in ../Makefile works
modules modules_install:
/*
* Userspace API for hardware time stamping of network packets
*
* Copyright (C) 2008,2009 Intel Corporation
* Author: Patrick Ohly <patrick.ohly@intel.com>
*
*/
#ifndef _NET_TIMESTAMPING_H
#define _NET_TIMESTAMPING_H
#include <linux/socket.h> /* for SO_TIMESTAMPING */
/* SO_TIMESTAMPING gets an integer bit field comprised of these values */
enum {
SOF_TIMESTAMPING_TX_HARDWARE = (1<<0),
SOF_TIMESTAMPING_TX_SOFTWARE = (1<<1),
SOF_TIMESTAMPING_RX_HARDWARE = (1<<2),
SOF_TIMESTAMPING_RX_SOFTWARE = (1<<3),
SOF_TIMESTAMPING_SOFTWARE = (1<<4),
SOF_TIMESTAMPING_SYS_HARDWARE = (1<<5),
SOF_TIMESTAMPING_RAW_HARDWARE = (1<<6),
SOF_TIMESTAMPING_MASK =
(SOF_TIMESTAMPING_RAW_HARDWARE - 1) |
SOF_TIMESTAMPING_RAW_HARDWARE
};
/**
* struct hwtstamp_config - %SIOCSHWTSTAMP parameter
*
* @flags: no flags defined right now, must be zero
* @tx_type: one of HWTSTAMP_TX_*
* @rx_type: one of one of HWTSTAMP_FILTER_*
*
* %SIOCSHWTSTAMP expects a &struct ifreq with a ifr_data pointer to
* this structure. dev_ifsioc() in the kernel takes care of the
* translation between 32 bit userspace and 64 bit kernel. The
* structure is intentionally chosen so that it has the same layout on
* 32 and 64 bit systems, don't break this!
*/
struct hwtstamp_config {
int flags;
int tx_type;
int rx_filter;
};
/* possible values for hwtstamp_config->tx_type */
enum hwtstamp_tx_types {
/*
* No outgoing packet will need hardware time stamping;
* should a packet arrive which asks for it, no hardware
* time stamping will be done.
*/
HWTSTAMP_TX_OFF,
/*
* Enables hardware time stamping for outgoing packets;
* the sender of the packet decides which are to be
* time stamped by setting %SOF_TIMESTAMPING_TX_SOFTWARE
* before sending the packet.
*/
HWTSTAMP_TX_ON,
/*
* Enables time stamping for outgoing packets just as
* HWTSTAMP_TX_ON does, but also enables time stamp insertion
* directly into Sync packets. In this case, transmitted Sync
* packets will not received a time stamp via the socket error
* queue.
*/
HWTSTAMP_TX_ONESTEP_SYNC,
};
/* possible values for hwtstamp_config->rx_filter */
enum hwtstamp_rx_filters {
/* time stamp no incoming packet at all */
HWTSTAMP_FILTER_NONE,
/* time stamp any incoming packet */
HWTSTAMP_FILTER_ALL,
/* return value: time stamp all packets requested plus some others */
HWTSTAMP_FILTER_SOME,
/* PTP v1, UDP, any kind of event packet */
HWTSTAMP_FILTER_PTP_V1_L4_EVENT,
/* PTP v1, UDP, Sync packet */
HWTSTAMP_FILTER_PTP_V1_L4_SYNC,
/* PTP v1, UDP, Delay_req packet */
HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ,
/* PTP v2, UDP, any kind of event packet */
HWTSTAMP_FILTER_PTP_V2_L4_EVENT,
/* PTP v2, UDP, Sync packet */
HWTSTAMP_FILTER_PTP_V2_L4_SYNC,
/* PTP v2, UDP, Delay_req packet */
HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ,
/* 802.AS1, Ethernet, any kind of event packet */
HWTSTAMP_FILTER_PTP_V2_L2_EVENT,
/* 802.AS1, Ethernet, Sync packet */
HWTSTAMP_FILTER_PTP_V2_L2_SYNC,
/* 802.AS1, Ethernet, Delay_req packet */
HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ,
/* PTP v2/802.AS1, any layer, any kind of event packet */
HWTSTAMP_FILTER_PTP_V2_EVENT,
/* PTP v2/802.AS1, any layer, Sync packet */
HWTSTAMP_FILTER_PTP_V2_SYNC,
/* PTP v2/802.AS1, any layer, Delay_req packet */
HWTSTAMP_FILTER_PTP_V2_DELAY_REQ,
};
#endif /* _NET_TIMESTAMPING_H */
This diff is collapsed.
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Copyright (C) 2019 Seven Solutions (sevensols.com)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released to the public domain as sample code to be customized.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
/* Typical use: "wr-dio-agent wr1 /dev/fmc-dio1" */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include "fmc-dio.h"
static char git_version[] = "version: " GIT_VERSION;
#define RULER_PROTO 0x5752 /* WR */
/*
* Lazily, use global variables, so the code has less parameter passing.
* Everything in this file is using "agent_" as a prefix, to ease the
* reader -- and remember that <TAB> aligns at 8 spaces, not 4.
*/
char *agent_prgname;
int agent_sock;
char *agent_ifname;
int agent_fd_dio;
char *agent_dio_name;
struct ifreq agent_ifr;
/* Boring network stuff extracted from main function */
static int agent_open_wr_sock(char *ifname, char *dio_name)
{
struct sockaddr_ll addr;
int sock, fd_dio, ifindex;
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0) {
fprintf(stderr, "%s: socket(): %s\n",
agent_prgname, strerror(errno));
return -1;
}
fd_dio = open(dio_name, O_RDWR);
if(fd_dio < 0) {
fprintf(stderr, "%s: open(): %s\n",
agent_prgname, strerror(errno));
close(sock);
return -1;
}
memset(&agent_ifr, 0, sizeof(agent_ifr));
strncpy(agent_ifr.ifr_name, ifname, sizeof(agent_ifr.ifr_name));
if (ioctl(fd_dio, PRIV_MEZZANINE_ID, (unsigned long) dio_name) < 0
/* EAGAIN is special: it means we have no ID to check yet */
&& errno != EAGAIN) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_ID(%s)): %s\n",
agent_prgname, dio_name, strerror(errno));
close(sock);
return -1;
}
/* Retieve the interfaceindex */
if (ioctl(sock, SIOCGIFINDEX, &agent_ifr) < 0) {
fprintf(stderr, "%s: SIOCGIFINDEX(%s): %s\n", agent_prgname,
ifname, strerror(errno));
close(sock);
return -1;
}
ifindex = agent_ifr.ifr_ifindex;
/* Bind to the interface, so to be able to receive */
memset(&addr, 0, sizeof(addr));
addr.sll_family = AF_PACKET;
addr.sll_protocol = htons(RULER_PROTO);
addr.sll_ifindex = ifindex;
addr.sll_pkttype = PACKET_BROADCAST; /* that's what ruler sends */
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
fprintf(stderr, "%s: bind(%s): %s\n", agent_prgname,
ifname, strerror(errno));
close(sock);
return -1;
}
/* save in globals for later */
agent_sock = sock;
agent_ifname = ifname;
agent_fd_dio = fd_dio;
agent_dio_name = dio_name;
return 0;
}
static void print_version(char *pname)
{
printf("%s %s\n", pname, git_version);
}
/* And a simple main with the loop inside */
int main(int argc, char **argv)
{
int len;
/* We are receiving stuff in this frame */
static struct frame {
struct ether_header h;
unsigned char pad[2];
struct wr_dio_cmd cmd;
} f;
if ((argc == 2) && (!strcmp(argv[1], "-V"))) {
print_version(argv[0]);
exit(0);
}
if (argc != 3) {
fprintf(stderr, "%s: Use \"%s [-V] <wr-if> <dio-dev>\"\n",
argv[0], argv[0]);
exit(1);
}
agent_prgname = argv[0];
/* All functions print error messages by themselves, so just exit */
if (agent_open_wr_sock(argv[1],argv[2]) < 0)
exit(1);
while (1) {
len = recv(agent_sock, &f, sizeof(f), MSG_TRUNC);
if (len != sizeof(f)) {
fprintf(stderr, "%s: recevied unexpected frame length"
" (%i instead of %zu)\n", agent_prgname, len,
sizeof(f));
continue;
}
if (ntohs(f.h.ether_type) != RULER_PROTO) {
fprintf(stderr, "%s: received unexpected eth type"
" (%04x instead of %04x)\n", agent_prgname,
ntohs(f.h.ether_type), RULER_PROTO);
continue;
}
if (0)
printf("command %i, ch %i, t %li.%09li\n",
f.cmd.command, f.cmd.channel, f.cmd.t[0].tv_sec,
f.cmd.t[0].tv_nsec);
/* Then simply pass it to the hardware */
if (ioctl(agent_fd_dio, PRIV_MEZZANINE_CMD, (unsigned long)&f.cmd) < 0) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_CMD(%s)): "
"%s\n", agent_prgname, agent_dio_name,
strerror(errno));
return -1;
}
}
exit(0);
}
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Copyright (C) 2019 Seven Solutions (sevensols.com)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released to the public domain as sample code to be customized.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "fmc-dio.h"
static char git_version[] = "version: " GIT_VERSION;
char *prgname;
char c;
int fd;
char *devname;
// FIXME: Remove it if not used
void *last_cmd;
struct wr_dio_cmd _cmd;
struct wr_dio_cmd *cmd = &_cmd;
static int parse_ts(char *s, struct timespec *ts)
{
int i, n;
unsigned long nano;
char c;
/*
* Hairy: if we scan "%ld%lf", the 0.009999 will become 9998 micro.
* Thus, scan as integer and string, so we can count leading zeros
*/
nano = 0;
ts->tv_sec = 0;
ts->tv_nsec = 0;
if ( (i = sscanf(s, "%ld.%ld%c", &ts->tv_sec, &nano, &c)) == 1)
return 0; /* seconds only */
if (i == 3)
return -1; /* trailing crap */
if (i == 0)
if (sscanf(s, ".%ld%c", &nano, &c) != 1)
return -1; /* leading or trailing crap */
s = strchr(s, '.') + 1;
n = strlen(s);
if (n > 9)
return -1; /* too many decimals */
while (n < 9) {
nano *= 10;
n++;
}
ts->tv_nsec = nano;
return 0;
}
static int scan_pulse(int argc, char **argv)
{
char c;
if (argc != 4 && argc != 6) {
fprintf(stderr, "%s: %s: wrong number of arguments\n",
prgname, argv[0]);
fprintf(stderr, " Use: %s <channel> <duration> <when> "
"[<period> <count>]\n", argv[0]);
return -1;
}
if (sscanf(argv[1], "%hi%c", &cmd->channel, &c) != 1
|| cmd->channel > 4) {
fprintf(stderr, "%s: %s: not a channel number \"%s\"\n",
prgname, argv[0], argv[1]);
return -1;
}
/* Duration is first time argument but position 1 for ioctl */
if (parse_ts(argv[2], cmd->t + 1) < 0) {
fprintf(stderr, "%s: %s: invalid time \"%s\"\n",
prgname, argv[0], argv[2]);
return -1;
}
if (cmd->t[1].tv_sec) {
fprintf(stderr, "%s: %s: duration must be < 1s (got \"%s\")\n",
prgname, argv[0], argv[2]);
return -1;
}
/* Next argument is the "when", position 0 in ioctl timestamp array */
if (!strcmp(argv[3], "now")) {
cmd->flags |= WR_DIO_F_NOW;
} else {
char *s2 = argv[3];
if (s2[0] == '+') {
cmd->flags |= WR_DIO_F_REL;
s2++;
}
if (parse_ts(s2, cmd->t) < 0) {
fprintf(stderr, "%s: %s: invalid time \"%s\"\n",
prgname, argv[0], argv[3]);
return -1;
}
}
/* If argc is 6, we have period and count */
if (argc == 6) {
cmd->flags |= WR_DIO_F_LOOP;
if (parse_ts(argv[4], cmd->t + 2) < 0) {
fprintf(stderr, "%s: %s: invalid time \"%s\"\n",
prgname, argv[0], argv[4]);
return -1;
}
if (sscanf(argv[5], "%i%c", &cmd->value, &c) != 1) {
fprintf(stderr, "%s: %s: invalid count \"%s\"\n",
prgname, argv[0], argv[5]);
return -1;
}
}
if (ioctl(fd, PRIV_MEZZANINE_CMD, (unsigned long) cmd) < 0) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_CMD(%s)): %s\n",
prgname, devname, strerror(errno));
return -1;
}
return 0;
}
static int scan_stamp(int argc, char **argv, int ismask)
{
int i, ch;
char c;
cmd->flags = 0;
if (argc == 3 && !strcmp(argv[2], "wait")) {
cmd->flags = WR_DIO_F_WAIT;
argc = 2;
}
if (argc == 1) {
ismask = 1;
ch = 0x1f;
} else if (argc == 2) {
if (sscanf(argv[1], "%i%c", &ch, &c) != 1) {
fprintf(stderr, "%s: %s: not a channel \"%s\"\n",
prgname, argv[0], argv[1]);
exit(1);
}
if (ch < 0 || ch > 31 || (!ismask && ch > 4)) {
fprintf(stderr, "%s: %s: out of range channel \"%s\"\n",
prgname, argv[0], argv[1]);
exit(1);
}
} else {
fprintf(stderr, "%s: %s: wrong number of arguments\n",
prgname, argv[0]);
if (ismask)
fprintf(stderr, " Use: %s [<channel-mask>]\n",
argv[0]);
else
fprintf(stderr, " Use: %s [<channel>] [wait]\n",
argv[0]);
return -1;
}
if (ismask)
cmd->flags = WR_DIO_F_MASK;
while (1) {
cmd->channel = ch;
errno = 0;
if (ioctl(fd, PRIV_MEZZANINE_CMD, (unsigned long) cmd) < 0 ) {
if (errno == EAGAIN)
break;
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_CMD(%s)): "
"%s\n", prgname, devname, strerror(errno));
return -1;
}
for (i = 0; i < cmd->nstamp; i++)
printf("ch %i, %9li.%09li\n", cmd->channel,
(long)cmd->t[i].tv_sec, cmd->t[i].tv_nsec);
}
return 0;
}
static int one_mode(int c, int index)
{
if (c == '-')
return 0;
cmd->channel |= 1 << index;
//Add error message for channel 0
if(index==0 && strchr("dD01",c))
{
fprintf(stderr, "Error: Only p/P modes are available as ouput mode for channel 0\n");
return -1;
}
switch(c) {
case 'D':
cmd->value |= WR_DIO_INOUT_TERM << index;
case 'd':
cmd->value |= WR_DIO_INOUT_DIO << index;
cmd->value |= WR_DIO_INOUT_OUTPUT << index;
break;
case 'C':
cmd->value |= WR_DIO_INOUT_TERM << index;
case 'c':
cmd->value |= WR_DIO_INOUT_DIO << index;
cmd->value |= WR_DIO_INOUT_VALUE << index;
if(index!=4)
fprintf(stdout, "Warning: Clock mode is only available for last channel (ch4)\n,"
"(on other channel it corresponds to input mode without interruptions)\n");
break;
case 'P':
cmd->value |= WR_DIO_INOUT_TERM << index;
case 'p':
cmd->value |= WR_DIO_INOUT_DIO << index;
cmd->value |= WR_DIO_INOUT_VALUE << index;
cmd->value |= WR_DIO_INOUT_OUTPUT << index;
break;
case 'I':
cmd->value |= WR_DIO_INOUT_TERM << index;
case 'i':
break;
case '1':
cmd->value |= WR_DIO_INOUT_VALUE << index;
case '0':
cmd->value |= WR_DIO_INOUT_OUTPUT << index;
break;
default:
fprintf(stderr, "%s: mode: invalid mode '%c'\n",
prgname, c);
return -1;
}
return 0;
}
static int scan_inout(int argc, char **argv)
{
int i, ch;
char c;
cmd->flags = WR_DIO_F_MASK;
cmd->channel = 0;
cmd->value = 0;
if (argc == 2) {
if (strlen(argv[1]) != 5) {
fprintf(stderr, "%s: %s: wrong argument \"%s\"\n",
prgname, argv[0], argv[1]);
exit(1);
}
for (i = 0; i < 5; i++)
if (one_mode(argv[1][i], i) < 0)
return -1;
} else {
if (argc < 3 || argc > 11 || ((argc & 1) == 0)) {
fprintf(stderr, "%s: %s: wrong number of arguments\n",
prgname, argv[0]);
return -1;
}
while (argc >= 3) {
if (sscanf(argv[1], "%i%c", &ch, &c) != 1
|| ch < 0 || ch > 4) {
fprintf(stderr, "%s: mode: invalid channel "
"\"%s\"\n", prgname, argv[1]);
return -1;
}
if (strlen(argv[2]) != 1) {
fprintf(stderr, "%s: mode: invalid mode "
"\"%s\"\n", prgname, argv[2]);
return -1;
}
if (one_mode(argv[2][0], ch) < 0)
return -1;
argv += 2;
argc -= 2;
}
}
if (ioctl(fd, PRIV_MEZZANINE_CMD, cmd) < 0) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_CMD(%s)): %s\n",
prgname, devname, strerror(errno));
return -1;
}
return 0;
}
static void print_version(char *pname)
{
printf("%s %s\n", pname, git_version);
}
int main(int argc, char **argv)
{
prgname = argv[0];
argv++, argc--;
if ((argc == 2) && (!strcmp(argv[1], "-V"))) {
print_version(argv[0]);
exit(0);
}
if (argc < 2) {
fprintf(stderr, "%s: use \"%s [-V] <dev> <cmd> [...]\"\n",
prgname, prgname);
exit(1);
}
devname = argv[0];
argv++, argc--;
fd = open(devname,O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: open(): %s\n",
prgname, strerror(errno));
exit(1);
}
if (ioctl(fd, PRIV_MEZZANINE_ID, (unsigned long) devname) < 0
/* EAGAIN is special: it means we have no ID to check yet */
&& errno != EAGAIN) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_ID(%s)): %s\n",
prgname, devname, strerror(errno));
}
/*
* Parse the command line:
*
* pulse <ch> .<len> <seconds>.<fraction>
* pulse <ch> .<len> now
* pulse <ch> .<len> +<seconds>.<fraction>
*
* stamp [<channel>]
* stampm [<mask>]
*
* mode <01234>
* mode <ch> <mode> [...]
*/
if (!strcmp(argv[0], "pulse")) {
cmd->command = WR_DIO_CMD_PULSE;
if (scan_pulse(argc, argv) < 0)
exit(1);
} else if (!strcmp(argv[0], "stamp")) {
cmd->command = WR_DIO_CMD_STAMP;
if (scan_stamp(argc, argv, 0 /* no mask */) < 0)
exit(1);
} else if (!strcmp(argv[0], "stampm")) {
cmd->command = WR_DIO_CMD_STAMP;
if (scan_stamp(argc, argv, 1 /* mask */) < 0)
exit(1);
} else if (!strcmp(argv[0], "mode")) {
cmd->command = WR_DIO_CMD_INOUT;
if (scan_inout(argc, argv) < 0)
exit(1);
} else {
fprintf(stderr, "%s: unknown command \"%s\"\n", prgname,
argv[0]);
exit(1);
}
// FIXME: Is this useful?
last_cmd = (void *)cmd;
exit(0);
}
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Copyright (C) 2019 Seven Solutions (sevensols.com)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released to the public domain as sample code to be customized.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "fmc-dio.h"
/**
* This takes two arguments: interface name and channel number
*
* This simple tools just show an example of how to program with wr-nic/dio.
* If you want to measure WR timing we suggest to use the hardwire PPS on channel 0
* that gives a more accurate precision.
**/
static char git_version[] = "version: " GIT_VERSION;
static void print_version(char *pname)
{
printf("%s %s\n", pname, git_version);
}
int main(int argc, char **argv)
{
struct wr_dio_cmd _cmd;
struct wr_dio_cmd *cmd = &_cmd;
char *prgname = argv[0];
char *devname = "fmc-dio0";
int fd, ch, charg = 1;
char c;
if ((argc == 2) && (!strcmp(argv[1], "-V"))) {
print_version(argv[0]);
exit(0);
}
if (argc < 2 || argc > 3) {
fprintf(stderr, "%s: Use \"%s [<devname>] <channel>\"\n",
prgname, prgname);
exit(1);
}
if (argc == 3) {
devname = argv[1];
charg++;
}
if (sscanf(argv[charg], "%i%c", &ch, &c) != 1) {
fprintf(stderr, "%s: Not a channel number \"%s\"\n",
prgname, argv[charg]);
exit(1);
}
if (ch < 1 || ch > 4) {
fprintf(stderr, "%s: Out of range channel number \"%s\"\n",
prgname, argv[charg]);
exit(1);
}
fprintf(stderr, "%s: Using dev \"%s\" and channel %i for "
"pps output\n", prgname,devname, ch);
/* The following is standard stuff to access wr-dio */
fd = open(devname, O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: open(): %s\n",
prgname, strerror(errno));
exit(1);
}
if (ioctl(fd, PRIV_MEZZANINE_ID, (unsigned long) devname) < 0
/* EAGAIN is special: it means we have no ID to check yet */
&& errno != EAGAIN) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_ID(%s)): %s\n",
prgname, devname, strerror(errno));
}
/* Fill the command structure */
memset(cmd, 0, sizeof(*cmd));
cmd->command = WR_DIO_CMD_PULSE;
cmd->channel = ch;
cmd->flags = WR_DIO_F_REL | WR_DIO_F_LOOP;
/* Number of loops: -1 <=> Inf */
cmd->value = -1;
/* 2s delay to have time to send and process this command */
cmd->t[0].tv_sec = 2;
/* 1ms pulse width */
cmd->t[1].tv_nsec = 1000 * 1000;
/* Loop period */
cmd->t[2].tv_sec = 1;
/* Give it to the driver and we are done */
if (ioctl(fd, PRIV_MEZZANINE_CMD, (unsigned long) cmd) < 0) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_CMD(%s)): %s\n",
prgname, devname, strerror(errno));
exit(1);
}
exit(0);
}
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Copyright (C) 2019 Seven Solutions (sevensols.com)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released to the public domain as sample code to be customized.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
/* Typical use: "wr-dio-ruler wr1 /dev/fmc-dio1 IN0 L3+0.001 R3+0.001 L4+0.002" */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include "fmc-dio.h"
static char git_version[] = "version: " GIT_VERSION;
#define RULER_PROTO 0x5752 /* WR */
/*
* Lazily, use global variables, so the code has less parameter passing.
* Everything in this file is using "ruler_" as a prefix, to ease the
* reader -- and remember that <TAB> aligns at 8 spaces, not 4.
*/
char *ruler_prgname;
int ruler_sock;
char *ruler_ifname;
int ruler_fd_dio;
char *ruler_dio_name;
struct wr_dio_cmd ruler_cmd;
struct ifreq ruler_ifr;
unsigned char ruler_macaddr[ETH_ALEN];
struct ruler_action {
int isremote;
int channel;
struct timespec delay;
};
/* Boring parsing separated to a separate function (same code as elsewhere) */
static int parse_ts(char *s, struct timespec *ts)
{
int i, n;
unsigned long nano;
char c;
/*
* Hairy: if we scan "%ld%lf", the 0.009999 will become 9998 micro.
* Thus, scan as integer and string, so we can count leading zeros
*/
nano = 0;
ts->tv_sec = 0;
ts->tv_nsec = 0;
if ( (i = sscanf(s, "%ld.%ld%c", &ts->tv_sec, &nano, &c)) == 1)
return 0; /* seconds only */
if (i == 3)
return -1; /* trailing crap */
if (i == 0)
if (sscanf(s, ".%ld%c", &nano, &c) != 1)
return -1; /* leading or trailing crap */
s = strchr(s, '.') + 1;
n = strlen(s);
if (n > 9)
return -1; /* too many decimals */
while (n < 9) {
nano *= 10;
n++;
}
ts->tv_nsec = nano;
return 0;
}
/* Boring network stuff extracted from main function */
static int ruler_open_wr_sock(char *ifname, char *dio_name)
{
struct sockaddr_ll addr;
int sock,fd_dio, ifindex;
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0) {
fprintf(stderr, "%s: socket(): %s\n",
ruler_prgname, strerror(errno));
return -1;
}
fd_dio = open(dio_name, O_RDWR);
if(fd_dio < 0) {
fprintf(stderr, "%s: open(): %s\n",
ruler_prgname, strerror(errno));
close(sock);
return -1;
}
memset(&ruler_ifr, 0, sizeof(ruler_ifr));
strncpy(ruler_ifr.ifr_name, ifname, sizeof(ruler_ifr.ifr_name));
if (ioctl(fd_dio, PRIV_MEZZANINE_ID, (unsigned long)dio_name) < 0
/* EAGAIN is special: it means we have no ID to check yet */
&& errno != EAGAIN) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_ID(%s)): %s\n",
ruler_prgname, dio_name, strerror(errno));
close(sock);
return -1;
}
/* Retrieve the local MAC address to send correct Ethernet frames */
if (ioctl(sock, SIOCGIFHWADDR, &ruler_ifr) < 0) {
fprintf(stderr, "%s: SIOCGIFHWADDR(%s): %s\n", ruler_prgname,
ifname, strerror(errno));
close(sock);
return -1;
}
memcpy(ruler_macaddr, ruler_ifr.ifr_hwaddr.sa_data,
sizeof(ruler_macaddr));
/* Retieve the interfaceindex */
if (ioctl(sock, SIOCGIFINDEX, &ruler_ifr) < 0) {
fprintf(stderr, "%s: SIOCGIFINDEX(%s): %s\n", ruler_prgname,
ifname, strerror(errno));
close(sock);
return -1;
}
ifindex = ruler_ifr.ifr_ifindex;
/* Bind to the interface, so to be able to send */
memset(&addr, 0, sizeof(addr));
addr.sll_family = AF_PACKET;
addr.sll_protocol = htons(RULER_PROTO);
addr.sll_ifindex = ifindex;
addr.sll_pkttype = PACKET_OUTGOING;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
fprintf(stderr, "%s: bind(%s): %s\n", ruler_prgname,
ifname, strerror(errno));
close(sock);
return -1;
}
/* save in globals for later */
ruler_sock = sock;
ruler_ifname = ifname;
ruler_fd_dio = fd_dio;
ruler_dio_name = dio_name;
return 0;
}
/* The channel being monitored will be configured as input */
static int ruler_config_in(char *chname)
{
int ch;
char c;
if (sscanf(chname, "IN%i%c", &ch, &c) != 1) {
fprintf(stderr, "%s: Argument \"%s\" must be \"IN<ch>\"\n",
ruler_prgname, chname);
return -1;
}
if (ch < 0 || ch > 4) {
fprintf(stderr, "%s: Out of range channel number in \"%s\"\n",
ruler_prgname, chname);
return -1;
}
return ch;
}
/* Actions are allocated in an array and returned for later use */
static struct ruler_action *ruler_build_actions(int nact, char **strings)
{
struct ruler_action *act;
char *s;
int i, ch;
char lr, c;
act = calloc(nact, sizeof(*act));
if (!act) {
fprintf(stderr, "%s: %s\n", ruler_prgname, strerror(errno));
return NULL;
}
for (i = 0; i < nact; i++) {
if (sscanf(strings[i], "%c%i+%c\n", &lr, &ch, &c) != 3) {
fprintf(stderr, "%s: Wrong argument \"%s\"\n",
ruler_prgname, strings[i]);
free(act);
return NULL;
}
if (lr != 'L' && lr != 'R') {
fprintf(stderr, "%s: Wrong argument \"%s\"\n",
ruler_prgname, strings[i]);
free(act);
return NULL;
}
if (ch < 0 || ch > 4) {
fprintf(stderr, "%s: Out of range channel in \"%s\"\n",
ruler_prgname, strings[i]);
free(act);
return NULL;
}
s = strchr(strings[i], '+') + 1;
if (parse_ts(s, &act[i].delay) < 0) {
fprintf(stderr, "%s: Wrong time \"%s\" in \"%s\"\n",
ruler_prgname, s, strings[i]);
free(act);
return NULL;
}
if (lr == 'L')
act[i].isremote = 0;
else
act[i].isremote = 1;
act[i].channel = ch;
}
for (i = 0; i < nact; i++) {
fprintf(stderr, "%s: configured for %s channel %i, "
"delay %li.%09li\n", ruler_prgname,
act[i].isremote ? "remote" : " local",
act[i].channel,
act[i].delay.tv_sec, act[i].delay.tv_nsec);
}
return act;
}
/* The main loop will wait for an event... */
static int ruler_wait_event(int inch, struct timespec *ts)
{
ruler_cmd.command = WR_DIO_CMD_STAMP;
ruler_cmd.flags = WR_DIO_F_WAIT;
ruler_cmd.channel = inch;
if (ioctl(ruler_fd_dio, PRIV_MEZZANINE_CMD, (unsigned long)&ruler_cmd) < 0 ) {
fprintf(stderr, "%s: A ioctl(PRIV_MEZZANINE_CMD(%s)): "
"%s\n", ruler_prgname, ruler_dio_name, strerror(errno));
return -1;
}
/* Assume it's only one stamp */
*ts = ruler_cmd.t[0];
return 0;
}
/* ...and run all actions when the event happens */
static int ruler_run_actions(int nact, struct timespec *ts,
struct ruler_action *actions)
{
int i;
/* We are building stuff in this frame, to possibly send it */
static struct frame {
struct ether_header h;
unsigned char pad[2];
struct wr_dio_cmd cmd;
} f;
/* Most parameters are unchanged over actions */
memset(&f.h.ether_dhost, 0xff, ETH_ALEN); /* broadcast */
memcpy(&f.h.ether_shost, ruler_macaddr, ETH_ALEN);
f.h.ether_type = ntohs(RULER_PROTO);
f.cmd.t[1].tv_nsec = 1000 * 1000; /* 1ms*/
f.cmd.command = WR_DIO_CMD_PULSE;
for (i = 0; i < nact; i++) {
f.cmd.channel = actions[i].channel;
f.cmd.t[0] = *ts;
/* add the requested delay */
f.cmd.t[0].tv_sec += actions[i].delay.tv_sec;
f.cmd.t[0].tv_nsec += actions[i].delay.tv_nsec;
if (f.cmd.t[0].tv_nsec > 1000 * 1000 * 1000) {
f.cmd.t[0].tv_nsec -= 1000 * 1000 * 1000;
f.cmd.t[0].tv_sec++;
}
if (actions[i].isremote) {
if (send(ruler_sock, &f, sizeof(f), 0) < 0) {
fprintf(stderr, "%s: send(): %s\n",
ruler_prgname, strerror(errno));
return -1;
}
continue;
}
/* local */
if (ioctl(ruler_fd_dio, PRIV_MEZZANINE_CMD, (unsigned long) &f.cmd) < 0) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_CMD(%s)): "
"%s\n", ruler_prgname, ruler_dio_name,
strerror(errno));
return -1;
}
}
return 0;
}
static void print_version(char *pname)
{
printf("%s %s\n", pname, git_version);
}
/* Finally, a main function to wrap it all */
int main(int argc, char **argv)
{
struct ruler_action *actions;
struct timespec ts;
int inch;
if ((argc == 2) && (!strcmp(argv[1], "-V"))) {
print_version(argv[0]);
exit(0);
}
if (argc < 5) {
fprintf(stderr, "%s: Use \"%s [-V] <wr-if> <dio-dev> IN<ch> "
"{L,R}<ch>+<delay-as-decimal> [...]\n",
argv[0], argv[0]);
exit(1);
}
ruler_prgname = argv[0];
/* All functions print error messages by themselves, so just exit */
if (ruler_open_wr_sock(argv[1],argv[2]) < 0)
exit(1);
inch = ruler_config_in(argv[3]);
if (inch < 0)
exit(1);
actions = ruler_build_actions(argc - 4, argv + 4);
if (!actions)
exit(1);
while(1) {
if (ruler_wait_event(inch, &ts) < 0)
exit(1);
if (ruler_run_actions(argc - 4, &ts, actions) < 0)
exit(1);
}
exit(0);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment