Commit 16e6a7bc authored by Jorge Machado's avatar Jorge Machado

Merge branch 'dio_extension' of https://ohwr.org/project/fmc-dio-6chttla into dio_extension

parents 11cd4f6f 0c2eba80
......@@ -53,3 +53,4 @@ modules.order
# Project Specifi #
###################
wr-dio.mod*
irq-demo
\ No newline at end of file
......@@ -43,7 +43,8 @@
@title FMC DIO Software Support
@subtitle @value{update-month} (@value{release})
@subtitle A driver for the FMC DIO module
@author Alessandro Rubini for CERN (BE-CO-HT), Jorge Machado Cano (Seven Solutions)
@author Alessandro Rubini for CERN (BE-CO-HT)
@author Jorge Machado Cano for Seven Solutions
@end titlepage
@headings single
......@@ -64,12 +65,23 @@ This is the manual for the fmc-dio device driver.
@node History and Overview
@chapter History and Overview
This version of the driver adds support for generating a train pulses in HW and
a new virtual channel that generates interrupts and is fully configurable by the user.
The HW train pulse generator improves the precision of the pulses which are
not any more generated in software as it was the case in the previous versions.
In this implementation, the software only takes care of the number of pulses to
disable them when all the pulses have been generated.
This and previous versions of the driver provide support for immediate pulse
generation, programmed pulse generation synchronized with White Rabbit clock,
and input pulse timestamping. The driver provides an ioctl interface to configure
the hdl core.
@c ##########################################################################
@node Compiling the Drivers
@chapter Compiling the Drivers
The kernel modules that are part of this package live in the @i{kernel}
The kernel modules that are part of this package live in the @i{sw/kernel}
subdirectory. To compile them, you need to
set the following variables in your environment:
......@@ -78,10 +90,8 @@ set the following variables in your environment:
@item LINUX
The top directory of the kernel sources for the version you
are going to run the driver under. I'm testing mostly with 3.4,
but this version compiles against Linux-2.6.37 and later ones
(2.6.36 had a different interface for hardware timestamping,
so @i{fmc} and @i{spec} compile fine, but @i{wr-nic} does not).
are going to run the driver under. I'm testing mostly with 4.15,
but this version compiles against Linux-4.15-4.18 and later ones.
@item CROSS_COMPILE
......@@ -101,7 +111,7 @@ set the following variables in your environment:
To compile run ``@code{make}'' with the previous variables set. To
install run ``@code{make install} to install under
@code{/lib/modules/3.4.0} (or other version-based directory). You can
@code{/lib/modules/4.15} (or other version-based directory). You can
set @code{INSTALL_MOD_PATH} to force and installation directory
(as a prefix followed by @code{/lib/modules/...}).
For example, if your target computer's filesystem is mounted under
......@@ -116,10 +126,10 @@ the case shown above your driver will end up being installed
(together with the other modules) as
@example
/mnt/target/lib/modules/3.4.0/extra/fmc-dio.ko
/mnt/target/lib/modules/4.15.0/extra/fmc-dio.ko
@end example
Please note that by default the package compiles an installs the
Please note that by default the package compiles and installs the
@i{fmc-bus} modules, too (the project is a @i{git} submodule).
@c ##########################################################################
......@@ -132,8 +142,8 @@ totally configurable pulses. It also recovers timestamps of external and generat
pulses.
@c ==========================================================================
@node fmc-dio Initialization
@section fmc-dio Initialization
@node Initialization of fmc-dio
@section Initialization of fmc-dio
There is no need to explicitly initialize the FMC-DIO driver.
......@@ -141,8 +151,26 @@ There is no need to explicitly initialize the FMC-DIO driver.
@node Interrupts in fmc-dio.ko
@section Interrupts in fmc-dio.ko
There is only an interrupt to control the number of pulses that has been generated.
There are two types of interrupts:
@table @file
@item I/O channel interrupt
This interrupt is generated when one channel generates an
output pulse or detects an input pulse. The timestamp of the
pulse is stored into the timestamp FIFO queue of the channel.
@item Dedicated interrupt channel
This interrupt is generated only when the interrupt channel
is triggered. The timestamp of the interrupt is stored into
the interrupt timestamp FIFO.
@end table
Both interrupt types comes from an unique HW interrupt. The software interrupt
handler manages both types.
@c ==========================================================================
@node Code Layout
@section Code Layout
......@@ -156,16 +184,16 @@ spread over several directories:
@table @file
@item fmc-bus/
@item sw/fmc-bus/
FMC-BUS kernel driver support (see specific doc inside)
@item kernel/
@item sw/kernel/
This directory contains the low level functions to configure each
physical channel and the interrupt management.
@item tools/
@item sw/tools/
This directory hosts the userspace tools that uses the low level
functions.
......@@ -177,65 +205,68 @@ register definitions. This is the role of each of them:
@table @file
@item kernel/fmc-dio.c
@item kernel/fmc-dio.h
@item sw/kernel/fmc-dio.c
@item sw/kernel/fmc-dio.h
Code and headers of the driver initialization. It also detects
the version of the FPGA binary to able coherent memory accesses
through all the code.
@item kernel/fmc-dio-gpio.c
@item sw/kernel/fmc-dio-gpio.c
DIO GPIO support skeleton to be developed.
DIO GPIO support skeleton to be developed using gpiolib Linux feature.
@item kernel/fmc-dio-internal.c
@item sw/kernel/fmc-dio-internal.c
Code that excecutes the requested operations through the IOCTL.
It also implements the interrupt management.
Code that implements the low-level functions to handle FMC DIO devices
through the IOCTL and interrupt handler.
@item kernel/fmc-dio-mdev.c
@item sw/kernel/fmc-dio-mdev.c
This code created the device and connects the IOCTL.
@item kernel/hw/ppsg-regs.h
@item kernel/hw/wr-dio-regs.c
@item kernel/hw/wr-dio-regs.h
@item kernel/hw/wr-dio-regs_v1.c
@item kernel/hw/wr-dio-regs_v1.h
@item kernel/hw/wr-dio-regs_v2.c
@item kernel/hw/wr-dio-regs_v2.h
@item sw/kernel/hw/ppsg-regs.h
@item sw/kernel/hw/wr-dio-regs.c
@item sw/kernel/hw/wr-dio-regs.h
@item sw/kernel/hw/wr-dio-regs_v1.c
@item sw/kernel/hw/wr-dio-regs_v1.h
@item sw/kernel/hw/wr-dio-regs_v2.c
@item sw/kernel/hw/wr-dio-regs_v2.h
Code and headers with the register map of the fmc-dio. It also
Code and headers with the registers map of the fmc-dio. It also
implements some functions to get the offset of each register.
@end table
The @i{tools} directory contains all the userspace tools the manage the
The @i{sw/tools} directory contains all the userspace tools the manage the
@i{fmc-dio} driver. This is the role of each of them:
@table @file
@item tools/net_tstamp.h
@item tools/stamp-frame.c
@item sw/tools/net_tstamp.h
@item sw/tools/stamp-frame.c
Code and headers of timestampig related functions.
@item tools/wr-dio-agent.c
@item sw/tools/wr-dio-agent.c
Code to receive almosteverything that appears on the cable.
Code that implements a very basic server to receive contol packets from
ruler. Once received them, it executes some operation to the FMC DIO device
(mode change, pulse generation, etc).
@item tools/wr-dio-cmd.c
@item sw/tools/wr-dio-cmd.c
Code that implements the userpace commands to manage the fmc-dio
channels.
@item tools/wr-dio-pps.c
@item sw/tools/wr-dio-pps.c
Example of a PPS generation.
@item tools/wr-dio-ruler.c
@item sw/tools/wr-dio-ruler.c
Code to transmit raw Ethernet frames to the broadcast address.
Code that implements a basic client to send DIO command to the agent.
It also is able to run local FMC DIO commands.
@end table
......@@ -243,7 +274,12 @@ The @i{tools} directory contains all the userspace tools the manage the
@node Overview of the Driver
@section Overview of the Driver
TO BE WRITTEN
This fmc-dio driver provides support for both versions of the fmc-dio hdl core,
i.e. the current version with extended functionality and a previous version.
The fmc-dio hdl core provides immediate pulse generation, immediate pulse
train generator, programmed pulse/pulse train generation synchronized with
White Rabbit clock, programmed/immediate interrupt generation and input
pulse timestamping.
@c ==========================================================================
@node Accessing the DIO Channels
......@@ -267,7 +303,7 @@ for device-specific use:
field. (For example, we may return EEPROM contents to user space
on request).
@b{Warning}: the command is not implemented because we still have
@b{Warning}: The command is not implemented because we still have
no mezzanine identification in place. The error being returned
is @code{EAGAIN}; user code can rely on that error to know it
is actually talking with a SPEC device, even if no identification
......@@ -311,13 +347,13 @@ In lab environments you may be concerned about the duration of the
than needed. To verify whether we have an over-engineering problem in
kernel space, I provided a simple measurement of how much time is
spent in the @i{Ioctl} itself. The @i{make} variable
@code{WR_NIC_CFLAGS} can be used to pass extra flags to the compiler,
@code{WR_DIO_FLAGS} can be used to pass extra flags to the compiler,
and the macro @code{DIO_STAT} enables the time measurement.
Compiling with the following command thus enable such measurement
and associated @i{printk} -- the time is usually 5 microseconds for me:
and associated @i{printk}
@example
make WR_NIC_FLAGS=-DDIO_STAT
make WR_DIO_FLAGS=-DDIO_STAT
@end example
Previous versions of this manual described how to command pulses on
......@@ -330,15 +366,15 @@ fast, so calling it several times is acceptable).
@node FMC-DIO Command Tool
@section FMC-DIO Command Tool
In the @file{tools/} subdirectory of this project, you find the
In the @file{sw/tools/} subdirectory of this project, you find the
@file{wr-dio-cmd} program, which is a command-line interface to the
@i{ioctl} command that acts on the @i{simple-DIO} mezzanine card. Other
@code{wr-dio-} tools are provided (and described below) but this is
the most generic one.
Please note that neither timestamping nor pulse generation work
@b{Please note that neither timestamping nor pulse generation work
if the WR core is not running or has an invalid time: it must either be
a master or a synchronized slave.
a master or a synchronized slave.}
Moreover, please note that this tool is just a demonstration to quickly
test the I/O features of the device (and for me to verify the kernel
......@@ -349,17 +385,17 @@ of ASCII and repeated invocation of this program.
This is the general syntax of the command:
@example
wr-dio-cmd <ifname> <cmd> [<arg> ...]
wr-dio-cmd <devname> <cmd> [<arg> ...]
@end example
The arguments have the following meaning
@table @code
@item ifname
@item devname
The name of the network interface, most likely @code{wr0}
The name of the device, most likely @code{/dev/fmc-dio-1:0}
(if you have more than one SPEC card, the other interfaces are
called @code{wr1}, @code{wr2} and so on).
called @code{/dev/fmc-dio-1:1}, @code{/dev/fmc-dio-1:2} and so on).
@item cmd
......@@ -373,7 +409,7 @@ The arguments have the following meaning
@b{Note}: This command, like everything else in this package, numbers
channels from 0 through 5, whereas the back panel of the mezzanine
numbers them 0 through 4. The last channel does not represent a physical channel,
numbers them 1 through 5. The last channel does not represent a physical channel,
it is a virtual channel to generate interrupts.
The current version of the tool supports the following commands:
......@@ -402,27 +438,26 @@ The current version of the tool supports the following commands:
be specified as a fraction of a second (decimal number, less than
one second), the @code{when} argument can be the string @code{now},
an absolute time (@code{<seconds>.<fraction>}) or a relative
time (@code{+<seconds>.<fraction}). In the last case, the
time (@code{+<seconds>.<fraction>}). In the last case, the
current second is added to @code{<seconds>} while the fraction
is used unchanged. The @code{+} form is useful for simple checks with
visual inspection. @code{period}, if specified, requests for
visual inspection. The @code{period} parameter, if specified, requests for
a pulse train, with the specified time period between raising edges;
@code{count} is the number of instances to run (-1 means forever,
0 means ``stop generating pulses'').
the @code{count} parameter is the number of times to run pulse generation (-1 means forever,
0 means ``stop ongoing pulse generation'').
@item irq <when> [<period> <count>]
This command only generates interrupts and does not generate any
physical pulse. The duration must be specified as a fraction of a
second (decimal number, less than one second), the @code{when}
argument can be the string @code{now}, an absolute time
(@code{<seconds>.<fraction>}) or a relative time
(@code{+<seconds>.<fraction}). In the last case, the current second
physical pulse. The @code{when} argument can be the string @code{now},
an absolute time (@code{<seconds>.<fraction>}) or a relative time
(@code{+<seconds>.<fraction>}). In the last case, the current second
is added to @code{<seconds>} while the fraction is used unchanged.
The @code{+} form is useful for simple checks with visual inspection.
@code{period}, if specified, requests for a pulse train, with the
specified time period between rising edges; @code{count} is the number
of instances to run (-1 means forever, 0 means ``stop generating pulses'').
The @code{period} parameter, if specified, requests for a interrupt train,
with the specified time period between interrupts. It must be specified in
a range between 1ms and 2s; the @code{count} parameter is the number
of times to run interrupt generation (-1 means forever, 0 means ``stop ongoing interrupt generation'').
@item mode <channel> <mode> [<channel> <mode> ...]
@itemx mode <m0><m1><m2><m3><m4>
......@@ -432,9 +467,7 @@ The current version of the tool supports the following commands:
configures all 5 channels by means of a 5-bytes-long string,
each characters specifies a mode according to the next table.
The irq channel can not be configured with this command, it is
automatically configured with the @code{irq} command.
@end table
should be configured with the @code{irq} command.
This is the list of supported modes for channels:
......@@ -458,14 +491,14 @@ This is the list of supported modes for channels:
is driven by the WR-aware digital I/O, and can thus generate pulses
and so on. Uppercase enables the termination.
A channel managed by the DIO core is normally low and can
pulse high on request (see @code{pulse}
pulse high on request (see @code{pulse})
command. The termination resistor doesn't make much sense for
output, but the code is provided for consistency with input modes.
@item P p (Channel 0)
Pulse per second output from the White Rabbit core that can be used
only for the first channel (ch0). This @i{pps} is sharper in its absolute
only for the first channel (ch0). This @i{PPS} is sharper in its absolute
time than the one that can be
generated by software using DIO pulses. Again, upper case
selects the termination, for symmetry with input modes.
......@@ -474,13 +507,35 @@ This is the list of supported modes for channels:
``Clock'' input. This mode (with or without termination) is
used to feed a clock to the White Rabbit core (currently the
WR core supports a 10MHz input on channel 4 (the last one).
WR core supports a 10MHz input on channel 4 (the last one)).
For other channel, the mode is just like @t{I} or @t{i} but
without timestamping (and thus without a software interrupt).
@end table
@item update_width <channel> <new_width>
This command updates the width of the last pulse train programmed.
The duration must be specified as a fraction of a second (decimal number,
less than one second). The command reads the previous period and checks if
the new width is compatible.
@item mask_irq <channel> <Y/y>
This command disables interrupt coming from the selected channel if the second
argument is 'y' and enables it if the argument is 'Y'. In normal operation
(if not disabled), an interrupt is generated on a channel (0-4) when a pulse
is received or transmitted on this channel. For example, if channel 0 is
configured to generate 1 Pulse Per Second, an interrupt is generated every
time 1 PPS is generated on this channel. This command allows to disable
such generation of interrupts.
@end table
@b{Note}: The first channel (channel 0) has been modified and now support only the
@t{P}/@t{p} as output mode. You will not be able to use @t{D},@t{d},@t{1},@t{0} modes.
......@@ -488,10 +543,14 @@ This is the list of supported modes for channels:
@code{wr-dio-cmd mode p00ic}.
Generation of a pulse train is performed by the FPGA, but the sofware takes care of
the pulse count. For this reason you'll observe that the pulse period cannot be too short,
according to how powerful your computer is and how much you loaded it with other
tasks.
The new hardware pulse generation feature allows to configure a pulse train with a higher
frequency than the previous version. However, there is still a software limitation due to the
interrupts handling. If the interrupt of the channel is disabled, the pulse train generation can
be as fast as the reference clock period allows, but there will be no timestamps in the FIFO and
the pulse train will be indefinite because the pulse counting mechanism remains excecuted in the
sofware interrupt handler. This frequency should be controlled in a proper way just to avoid PC
freezing. Because of this, the maximum frequency depends on your current machine configuration
and can be easily characterized with the existing tools for fmc-dio project.
There is no command to flush the timestamp FIFOs, but you can
always ``@code{wr-dio-cmd stamp > /dev/null}'' if needed.
......@@ -500,28 +559,43 @@ Example uses of the tool follow:
@example
# Pulse channel 4 for 0.1 seconds now
wr-dio-cmd /dev/fmc-dio-1:0 pulse 4 .1 now
sudo wr-dio-cmd /dev/fmc-dio-1:0 pulse 4 .1 now
# Pulse for 10 microseconds in the middle of the next second
wr-dio-cmd /dev/fmc-dio-1:0 pulse 4 .00001 +1.5
sudo wr-dio-cmd /dev/fmc-dio-1:0 pulse 4 .00001 +1.5
# Pulse for 1ms at 17:00 today
wr-dio-cmd /dev/fmc-dio-1:0 pulse 4 .001 $(date +%s --date 17:00)
sudo wr-dio-cmd /dev/fmc-dio-1:0 pulse 4 .001 $(date +%s --date 17:00)
# Get timestamps for the output events above
wr-dio-cmd /dev/fmc-dio-1:0 stamp 4
sudo wr-dio-cmd /dev/fmc-dio-1:0 stamp 4
# Make a train of 5 pulses, 0.5ms wide, every ms at next second
wr-dio-cmd /dev/fmc-dio-1:0 pulse 4 0.0005 +1 .001 5
sudo wr-dio-cmd /dev/fmc-dio-1:0 pulse 4 0.0005 +1 .001 5
# Configure channel 0 as input with termination, 1 as input, 4 as low
wr-dio-cmd /dev/fmc-dio-1:0 mode Ii--0
sudo wr-dio-cmd /dev/fmc-dio-1:0 mode Ii--0
# Generate interrupt now
wr-dio-cmd /dev/fmc-dio-1:0 irq now
sudo wr-dio-cmd /dev/fmc-dio-1:0 irq now
# Generate 100 interrupts every 10 ms at the next second
wr-dio-cmd /dev/fmc-dio-1:0 irq +1 .01 100
# Generate a train of 100 interrupts every 10 ms starting at the next second
sudo wr-dio-cmd /dev/fmc-dio-1:0 irq +1 .01 100
# Make a train of indefinite pulses, 1ms wide every 5ms
sudo wr-dio-cmd /dev/fmc-dio-1:0 pulse 1 0.001 now 0.005 -1
# Stop the previous pulse train
sudo wr-dio-cmd /dev/fmc-dio-1:0 pulse 1 0.001 now 0.005 0
# Update pulse width in channel 1 to 10ms
sudo wr-dio-cmd /dev/fmc-dio-1:0 update_width 1 0.01
# Disable interrupts in channel 0
sudo wr-dio-cmd /dev/fmc-dio-1:0 mask_irq 0 y
# Enable interrupts in channel 2
sudo wr-dio-cmd /dev/fmc-dio-1:0 mask_irq 2 Y
@end example
......@@ -538,7 +612,7 @@ because it spends all of its time in interrupt handling.
This problem is transient: as soon as you remove the offending cable
the system recovers. However, you need a 10MHz input signal if you
want to run your SPEC device to be a White Rabbit @i{grandmaster}. In
order to support that, the driver disables DIO interrupts when the
order to support that, the driver automatically disables DIO interrupts when the
time spent in interrupt management exceeds 80% of the total time,
averaged over one thousand interrupt events. Ethernet interrupts
are not affected. The fact is reported by a kernel message, using
......@@ -553,10 +627,14 @@ kilohertz and still be able to feed higher frequencies without manual
intervention. However, after DIO interrupts are disabled, the only
way to re-enable them is removing and reloading the device driver.
@b{Note}: if you run two SPEC cards, and one is fed with high frequency
pulses, it may happen that interrupts are disabled on both boards.
The safeguard is currently not very refined, as it was implemented in
a hurry.
On the other hand, you can manually disable interrupts on a channel where
you expect high frequency input signals by using the mask_irq command, see
previous section.
@b{Note}: If you run two SPEC cards, and one is fed with high frequency
pulses without disabling the interrupts manually, it may happen that interrupts
are automatically disabled on both boards. The safeguard is currently not
very refined, as it was implemented in a hurry.
@c ==========================================================================
@node WR-DIO Pulse per Second
......@@ -569,11 +647,11 @@ file.
The program is meant as a source code example, more than a real PPS
signal. If you need a real WR-driven pulse-per-second, you should
use the channel0 wich is "hard-wired" to the PTP core and can be configured by
use the channel0 which is "hard-wired" to the PTP core and can be configured by
executing: ``@t{wr-dio-cmd mode 0 p}''.
The program just fires a 1ms-long @i{pps} pulse on one of the output
channels. The device name defaults to @i{wr0} but can specify a different one;
The program just fires a 1ms-long @i{PPS} pulse on one of the output
channels. The device name defaults to @i{wri0} but can specify a different one;
the channel number is a mandatory argument.
@b{Note}: This command, like everything else in this package, numbers
......@@ -586,7 +664,7 @@ the wrong guess.
./wr-dio-pps 2
# run pps on channel 0 of the "second" card
./wr-dio-pps wr1 0
./wr-dio-pps wri1 0
@end example
The following two figures show two such @i{pulse-per-second} signals
......@@ -640,7 +718,8 @@ The example is made up of two programs: @code{wr-dio-agent} and
@code{wr-dio-ruler} (the former is a dumb actor, while the latter
states the rules). To keep things simple the two programs
assume that the SPEC is connected point-to-point to another SPEC
and both carry the @i{simple-DIO} mezzanine.
and both carry the @i{simple-DIO} mezzanine. @b{The wr-nic driver should
be installed to run this example @url{https://ohwr.org/project/wr-nic}}
Under this simplified assumptions, the @i{ruler} transmits raw Ethernet
frames to the broadcast address, while the @i{agent} receives almost
......@@ -665,7 +744,7 @@ command line argument is the name of the @i{White Rabbit} interface to
use (for most users it is @code{wr0}):
@example
wr-dio-agent wr0
wr-dio-agent [-V] <wr-if> <dio-dev>
@end example
The @i{ruler} command, on the other hand, waits for timestamps
......@@ -689,7 +768,7 @@ reprograms all output triggers at each input event.
@b{Note}: As usual, channels are numbered 0 through 4.
@example
wr-dio-ruler wr1 IN0 L3+0.001 R3+0.001 L4+0.002
wr-dio-ruler wr1 /dev/fmc-dio-1:0 IN0 L3+0.001 R3+0.001 L4+0.002
@end example
There is no sample code that generates trains of pulses as a response
......@@ -716,32 +795,63 @@ on @code{spusa} are replicated to one local channel and two remote channels,
with a delay of 1ms. The input events in this case are from a @i{pulse-per-second} signal:
@smallexample
tornado.root# /tmp/wr-dio-agent wr0 &
tornado.root# /tmp/wr-dio-agent wr0 /dev/fmc-dio-1:0 &
spusa.root# wr-dio-ruler wr1 IN4 L3+.001 R4+.001 R2+.001
spusa.root# sudo wr-dio-ruler wr1 /dev/fmc-dio-1:0 IN4 L3+.001 R4+.001 R2+.001
wr-dio-ruler: configured for local channel 3, delay 0.001000000
wr-dio-ruler: configured for remote channel 4, delay 0.001000000
wr-dio-ruler: configured for remote channel 2, delay 0.001000000
[... wait a few seconds ...]
spusa.root# wr-dio-cmd wr1 stamp 3
spusa.root# sudo wr-dio-cmd /dev/fmc-dio-1:0 stamp 3
ch 3, 385.001000000
ch 3, 386.001000000
ch 3, 387.001000000
ch 3, 388.001000000
tornado.root# wr-dio-cmd wr0 stamp 2
tornado.root# sudo wr-dio-cmd /dev/fmc-dio-1:0 stamp 2
ch 2, 385.001000000
ch 2, 386.001000000
ch 2, 387.001000000
ch 2, 388.001000000
tornado.root# wr-dio-cmd wr0 stamp 4
tornado.root# sudo wr-dio-cmd /dev/fmc-dio-1:0 stamp 4
ch 4, 385.001000000
ch 4, 386.001000000
ch 4, 387.001000000
ch 4, 388.001000000
@end smallexample
@c ##########################################################################
@node Interrupt Demo
@chapter Interrupt Demo
The interrupt demo software can be found in the @code{sw/irq-demo} directoy.
This demo sofware disables the interrupts of all channel except the new one, only
dedicated to interrupts.
By default, it setups the interrupt with 100 milliseconds between them.
The tool provides the total number of interrupts, the interrupts per second and
some statistic information calculated with the difference between OS time and
WR time.
This is the general syntax of the command:
@table @code
@item irq-demo <dio-device> [<period in ns>]
@end table
Example uses of the tool follow:
@example
# Generate one interrupt each 100 ms
sudo irq-demo /dev/fmc-dio-1:0
# Generate one interrupt each 10 ms
sudo irq-demo /dev/fmc-dio-1:0 10000000
@end example
@c ##########################################################################
@node Bugs and Missing Features
@chapter Bugs and Missing Features
......@@ -750,24 +860,18 @@ with a delay of 1ms. The input events in this case are from a @i{pulse-per-secon
@item Identification of the mezzanine is completely missing; every @i{fmc}
driver at this point takes hold of every device. We are working on this,
and the next version of spec-sw will support identification, with a flag
and the next version of fmc-dio will support identification, with a flag
to run without identification for users whose EEPROM has not been programmed.
@item Both spec and wr-nic should have GPIO support with @i{gpiolib};
there is skeletal support but no real code for actual I/O. This is not
a priority, just a wish list for better Linux integration.
@item The NIC driver should directly support setting the White Rabbit
mode for each card (grandmaster, free-running master or slave). This
will be supported at module load time, not at runtime (for that please
use the UART).
@item DIO support in @i{wr-nic} is missing some of the features listed
@item DIO support is missing some of the features listed
in @file{wr-dio.h} (i.e. DAC control)>
@item The @i{wr-nic} functionality should be completely detached from
the specific mezzanine. This is a longer-term desire.
@item Locking in kernel code should be verified with a serious audit
effort. There are no known issues at this point, but some code may
be made safer.
......@@ -780,26 +884,7 @@ be made safer.
This package should be portable. However I didn't test it on a wide
variety of systems. Currently most of my use is
on a 32-bit x86 host, running version 3.4 of the kernel.
The complete package builds without any warning from version 2.6.37
up to 3.13 (I didn't try later versions, yet). Frame timestamping
changed seriously after 2.6.36, so the @code{wr-nic.ko} driver is not
easily backward portable.
To allow use of the core @i{spec} driver, to drive custom mezzanines,
the @i{Makefile} supports the configuration variable
@code{CONFIG_WR_NIC}, which you may set to @code{n} before compiling:
@smallexample
export CONFIG_WR_NIC=n
@end smallexample
With this in place, the package compiles without any warning on a
32-bit PC from version 2.6.30 onwards.
By using the backport branch of the 2013-05 release you can build
for all kernels back to 2.6.27 and also 2.6.24 (the one we were still
using in production). Later releases have no associated backport branch.
on a 64-bit x86 host, running version between 4.15-4.18 of the kernel.
@c ##########################################################################
@bye
......
......@@ -3,6 +3,7 @@ files = ["wr_dio_wb.vhd",
"wr_dio.vhd",
"wr_dio_pkg.vhd",
"pulse_gen_pl.vhd",
"immed_pulse_counter.vhd",
"dummy_time.vhd" ]
"immed_pulse_counter.vhd",
"dummy_time.vhd",
"imm_pulse_train_gen.vhd" ]
......@@ -15,6 +15,8 @@ export FMC_BUS_ABS
DIRS = $(FMC_BUS_ABS) kernel tools
CFLAGS += $(WR_DIO_CFLAGS)
.PHONY: all clean modules install modules_install $(DIRS)
all clean modules install modules_install: $(DIRS)
......
CFLAGS += -I./dep
irq-demo: irq-demo.c ./dep/*.c
gcc -o irq-demo irq-demo.c ./dep/*.c $(CFLAGS) -lm
clean:
-rm irq-demo
-rm .irq-demo.log
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __FILE_LOG_PRIVATE_H__
#define __FILE_LOG_PRIVATE_H__
struct file_log_private {
FILE *fp;
char *path;
};
static int check_file_log_priv(log_device dev);
static struct file_log_private * get_private_info(log_device dev);
static void init_file_log_device(log_device dev);
static void send_to_file_log_device(log_device, const char *msg);
static void deinit_file_log_device(log_device dev);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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 <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include "log-device.h"
#include "file-log-private.h"
static struct log_device_iface _file_log_iface =
{
.init_log_device = init_file_log_device,
.send_to_log_device = send_to_file_log_device,
.clear_log_device = NULL,
.deinit_log_device = deinit_file_log_device,
};
static const log_device_interface file_log_iface = &_file_log_iface;
log_device create_file_log_device(const char *path)
{
return create_log_device((void *)path, file_log_iface);
}
static void init_file_log_device(log_device dev)
{
struct file_log_private *priv;
priv = calloc(1, sizeof(*priv));
priv->path = dev->private;
priv->fp = fopen(priv->path, "w+");
dev->private = priv;
}
static void send_to_file_log_device(log_device dev, const char *msg)
{
struct file_log_private *priv;
if(!check_file_log_priv(dev)) {
priv = get_private_info(dev);
fputs(msg, priv->fp);
fputs("\n", priv->fp);
}
}
static void deinit_file_log_device(log_device dev)
{
struct file_log_private *priv;
if(dev->private) {
priv = get_private_info(dev);
fclose(priv->fp);
free(dev->private);
}
}
static int check_file_log_priv(log_device dev)
{
return (dev->private != NULL) ? 0 : 1;
}
static struct file_log_private * get_private_info(log_device dev)
{
return (struct file_log_private *)dev->private;
}
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __FILE_LOG_H__
#define __FILE_LOG_H__
log_device create_file_log_device(const char *file);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __FMC_DIO_DEVICE_PRIVATE_H__
#define __FMC_DIO_DEVICE_PRIVATE_H__
#include "../../kernel/fmc-dio.h"
#define MAX_TMP_BUF 1024
#define FMC_DIO_N_CH 5
#define NS_IN_A_SEC 1000000000
struct fmc_dio_dev {
char *dev_name;
int fd;
log_device *logs;
unsigned int n_logs;
int enabled_log;
unsigned int total_n_ts;
unsigned int partial_n_ts;
struct wr_dio_cmd cmd;
};
static const fmc_dio_device BAD_FMC_DEVICE = NULL;
static int check_fmc_dio_device(fmc_dio_device dev);
static int enable_fmc_dio_device_hw_irq(fmc_dio_device dev,
int ch, int en);
static int setup_fmc_dio_device_hw_irq(fmc_dio_device dev,
unsigned long period,
long count);
static int get_hw_ts_from_fmc_dio_device(fmc_dio_device dev, int ch,
struct timespec **ts, unsigned int *nts);
static int get_kernel_leaps_info(fmc_dio_device dev);
static void log_msg(fmc_dio_device dev, const char *msg);
static void LOG(fmc_dio_device dev, const char *fmt, ...);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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 <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/timex.h>
#include <sys/ioctl.h>
#include "fmc-dio-device.h"
#include "fmc-dio-device-private.h"
fmc_dio_device create_fmc_dio_device(char *name)
{
fmc_dio_device dev = calloc(1, sizeof(*dev));
if(!dev)
return BAD_FMC_DEVICE;
dev->dev_name = name;
return dev;
}
static int check_fmc_dio_device(fmc_dio_device dev)
{
if(dev != BAD_FMC_DEVICE) {
return 0;
}
else {
return 1;
}
}
int open_fmc_dio_device(fmc_dio_device dev)
{
if(check_fmc_dio_device(dev))
return -EINVAL;
dev->fd = open(dev->dev_name, O_RDWR);
if(dev->fd < 0) {
free(dev);
return -ENODEV;
}
if(ioctl(dev->fd, PRIV_MEZZANINE_ID, (unsigned long) dev->dev_name) < 0 &&
errno != EAGAIN) {
free(dev);
return -EFAULT;
}
return 0;
}
void attach_log_devices_to_fmc_dio_device(fmc_dio_device dev,
log_device *log_devs,
unsigned int n_log_devs)
{
if(check_fmc_dio_device(dev) || !log_devs || n_log_devs == 0)
return;
dev->logs = log_devs;
dev->n_logs = n_log_devs;
}
int enable_log_for_fmc_dio_device(fmc_dio_device dev)
{
if(check_fmc_dio_device(dev))
return -EINVAL;
dev->enabled_log = 1;
return 0;
}
int disable_log_for_fmc_dio_device(fmc_dio_device dev)
{
if(check_fmc_dio_device(dev))
return -EINVAL;
dev->enabled_log = 0;
return 0;
}
void close_fmc_dio_device(fmc_dio_device dev)
{
if(check_fmc_dio_device(dev))
return;
close(dev->fd);
free(dev);
}
int enable_fmc_dio_device_irq(fmc_dio_device dev, int ch)
{
int ret;
if(check_fmc_dio_device(dev)) {
return -EINVAL;
}
ret = enable_fmc_dio_device_hw_irq(dev, ch, 1);
return ret;
}
int enable_fmc_dio_device_all_irq(fmc_dio_device dev)
{
int ret = 0;
for(int i = 0 ; i <= FMC_DIO_N_CH ; i++) {
ret = enable_fmc_dio_device_irq(dev, i);
if(ret)
break;
}
return ret;
}
int disable_fmc_dio_device_irq(fmc_dio_device dev, int ch)
{
int ret;
if(check_fmc_dio_device(dev)) {
return -EINVAL;
}
ret = enable_fmc_dio_device_hw_irq(dev, ch, 0);
return ret;
}
int disable_fmc_dio_device_all_irq(fmc_dio_device dev)
{
int ret = 0;
for(int i = 0 ; i <= FMC_DIO_N_CH ; i++) {
ret = disable_fmc_dio_device_irq(dev, i);
if(ret)
break;
}
return ret;
}
static int enable_fmc_dio_device_hw_irq(fmc_dio_device dev,
int ch, int en)
{
if(ch < 0 || ch > FMC_DIO_N_CH) {
return -EINVAL;
}
struct wr_dio_cmd *c = &dev->cmd;
en = (en) ? 1 : 0;
c->command = WR_DIO_CMD_MASK_IRQ;
c->flags = WR_DIO_F_MASK;
c->channel = 1 << ch;
c->value = (en) ? WR_DIO_F_MASK_ENABLE_IRQ :
WR_DIO_F_MASK_DISABLE_IRQ;
if(ioctl(dev->fd, PRIV_MEZZANINE_CMD, c) < 0)
return -EFAULT;
return 0;
}
int setup_fmc_dio_device_irq(fmc_dio_device dev,
unsigned long period,
long count)
{
int ret;
if(check_fmc_dio_device(dev))
return -EINVAL;
ret = setup_fmc_dio_device_hw_irq(dev, period, count);
return ret;
}
static int setup_fmc_dio_device_hw_irq(fmc_dio_device dev,
unsigned long period,
long count)
{
struct wr_dio_cmd *c = &dev->cmd;
int ret;
c->command = WR_DIO_CMD_IRQ;
c->flags = 0x0;
c->flags |= WR_DIO_F_LOOP;
c->flags |= WR_DIO_F_NOW;
c->t[1].tv_nsec = period % NS_IN_A_SEC;
c->t[1].tv_sec = period / NS_IN_A_SEC;
c->value= count;
ret = ioctl(dev->fd, PRIV_MEZZANINE_CMD, c);
if(ret < 0) {
return -EFAULT;
}
return 0;
}
int get_tai_ts_from_fmc_dio_device(fmc_dio_device dev, int ch,
struct timespec **ts, unsigned int *nts)
{
if(check_fmc_dio_device(dev))
return -EINVAL;
return get_hw_ts_from_fmc_dio_device(dev, ch, ts, nts);
}
int get_utc_ts_from_fmc_dio_device(fmc_dio_device dev, int ch,
struct timespec **ts, unsigned int *nts)
{
int ret;
int tai;
ret = get_tai_ts_from_fmc_dio_device(dev, ch, ts, nts);
if(ret)
return ret;
tai = get_kernel_leaps_info(dev);
for(int i = 0 ; i < *nts ; i++)
(*ts)[i].tv_sec -= tai;
return ret;
}
static int get_hw_ts_from_fmc_dio_device(fmc_dio_device dev, int ch,
struct timespec **ts, unsigned int *nts)
{
struct wr_dio_cmd *c = &dev->cmd;
int ret;
c->command = WR_DIO_CMD_STAMP;
c->flags = 0;
c->channel = ch;
ret = ioctl(dev->fd, PRIV_MEZZANINE_CMD, (unsigned long) c);
if(ret < 0) {
return -EFAULT;
}
if(c->nstamp <= 0) {
return -EAGAIN;
}
*nts = c->nstamp;
*ts = calloc(c->nstamp, sizeof(struct timespec));
if(!*ts)
return -ENOMEM;
for(int i = 0 ; i < c->nstamp ; i++) {
(*ts)[i] = c->t[i];
}
dev->total_n_ts += c->nstamp;
dev->partial_n_ts += c->nstamp;
return 0;
}
static int get_kernel_leaps_info(fmc_dio_device dev)
{
struct timex tx = {0};
if (adjtimex(&tx) < 0) {
return -EFAULT;
}
return tx.tai;
}
unsigned int get_total_n_timestamps(fmc_dio_device dev)
{
if(check_fmc_dio_device(dev))
return 0;
return dev->total_n_ts;
}
unsigned int get_partial_n_timestamps(fmc_dio_device dev)
{
if(check_fmc_dio_device(dev))
return 0;
return dev->partial_n_ts;
}
void reset_partial_n_timestamps(fmc_dio_device dev)
{
if(check_fmc_dio_device(dev))
return;
dev->partial_n_ts = 0;
}
static void log_msg(fmc_dio_device dev, const char *msg)
{
if(!dev->logs || dev->n_logs == 0)
return;
if(!dev->enabled_log)
return;
for(int i = 0 ; i < dev->n_logs ; i++)
send_to_log_device(dev->logs[i], msg);
}
static void LOG(fmc_dio_device dev, const char *fmt, ...)
{
va_list ap;
char buffer[MAX_TMP_BUF] = {0};
va_start(ap, fmt);
vsnprintf(buffer, MAX_TMP_BUF-1, fmt, ap);
va_end(ap);
log_msg(dev, buffer);
}
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __FMC_DIO_DEVICE_H__
#define __FMC_DIO_DEVICE_H__
#include <time.h>
#include "log-device.h"
typedef struct fmc_dio_dev * fmc_dio_device;
fmc_dio_device create_fmc_dio_device(char *name);
int open_fmc_dio_device(fmc_dio_device dev);
void close_fmc_dio_device(fmc_dio_device dev);
void attach_log_devices_to_fmc_dio_device(fmc_dio_device dev,
log_device *log_devs,
unsigned int n_log_devs);
int enable_log_for_fmc_dio_device(fmc_dio_device dev);
int disable_log_for_fmc_dio_device(fmc_dio_device dev);
int enable_fmc_dio_device_irq(fmc_dio_device dev, int ch);
int enable_fmc_dio_device_all_irq(fmc_dio_device dev);
int disable_fmc_dio_device_irq(fmc_dio_device dev, int ch);
int disable_fmc_dio_device_all_irq(fmc_dio_device dev);
int setup_fmc_dio_device_irq(fmc_dio_device dev,
unsigned long period,
long count);
int get_tai_ts_from_fmc_dio_device(fmc_dio_device dev, int ch,
struct timespec **ts, unsigned int *nts);
int get_utc_ts_from_fmc_dio_device(fmc_dio_device dev, int ch,
struct timespec **ts, unsigned int *nts);
unsigned int get_total_n_timestamps(fmc_dio_device dev);
unsigned int get_partial_n_timestamps(fmc_dio_device dev);
void reset_partial_n_timestamps(fmc_dio_device dev);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __LOG_DEVICE_PRIVATE_H__
#define __LOG_DEVICE_PRIVATE_H__
static const log_device BAD_LOG_DEVICE = NULL;
static int check_log_device(log_device dev);
static int check_log_device_interface(log_device dev);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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 <stdlib.h>
#include "log-device.h"
#include "log-device-private.h"
log_device create_log_device(void *args, log_device_interface interface)
{
log_device dev = calloc(1, sizeof(struct log_dev));
if(dev != BAD_LOG_DEVICE && interface != NULL) {
dev->private = args;
dev->ops = interface;
if(dev->ops->init_log_device)
dev->ops->init_log_device(dev);
}
return dev;
}
void send_to_log_device(log_device dev, const char *msg)
{
if(check_log_device_interface(dev))
return;
dev->ops->send_to_log_device(dev, msg);
}
void clear_log_device(log_device dev)
{
if(check_log_device_interface(dev))
return;
if(dev->ops->clear_log_device)
dev->ops->clear_log_device(dev);
}
void destroy_log_device(log_device dev)
{
if(check_log_device_interface(dev))
return;
if(dev->ops->deinit_log_device)
dev->ops->deinit_log_device(dev);
free(dev);
}
static int check_log_device(log_device dev)
{
return (dev == BAD_LOG_DEVICE) ? 1 : 0;
}
static int check_log_device_interface(log_device dev)
{
if(check_log_device(dev))
return 1;
if(!dev->ops)
return 1;
else
return 0;
}
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __LOG_DEVICE_H__
#define __LOG_DEVICE_H__
typedef struct log_device_iface * log_device_interface;
struct log_dev {
log_device_interface ops;
void *private;
};
typedef struct log_dev * log_device;
struct log_device_iface {
void (*init_log_device)(log_device dev);
void (*send_to_log_device)(log_device dev, const char *msg);
void (*deinit_log_device)(log_device dev);
void (*clear_log_device)(log_device dev);
};
log_device create_log_device(void *args, log_device_interface interface);
void send_to_log_device(log_device dev, const char *msg);
void clear_log_device(log_device dev);
void destroy_log_device(log_device dev);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __PRINTF_LOG_PRIVATE_H__
#define __PRINTF_LOG_PRIVATE_H__
static void send_to_printf_log_device(log_device dev, const char *msg);
static void clear_printf_log_device(log_device dev);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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 <stddef.h>
#include <stdio.h>
#include "log-device.h"
#include "printf-log-private.h"
static struct log_device_iface _printf_log_iface =
{
.init_log_device = NULL,
.send_to_log_device = send_to_printf_log_device,
.deinit_log_device = NULL,
.clear_log_device = clear_printf_log_device,
};
static const log_device_interface printf_log_iface = &_printf_log_iface;
static void send_to_printf_log_device(log_device dev, const char *msg)
{
printf("%s\n", msg);
}
static void clear_printf_log_device(log_device dev)
{
printf("\e[1;1H\e[2J");
}
log_device create_printf_log_device(void)
{
return create_log_device(NULL, printf_log_iface);
}
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __PRINTF_LOG_H__
#define __PRINTF_LOG_H__
#include "log-device.h"
log_device create_printf_log_device(void);
void clear_screen(log_device device);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __STATS_ENGINE_PRIVATE_H__
#define __STATS_ENGINE_PRIVATE_H__
#define MAX_TS 1000
#define NS_IN_A_SEC 1000000000
#define MAX_TMP_BUF 1024
struct ts_stats {
double mean;
double std;
unsigned int ts_per_second;
unsigned int ts_total;
};
struct _stats_engine {
struct timespec usr_ts[MAX_TS];
struct timespec sys_ts[MAX_TS];
double diff_ts[MAX_TS];
struct ts_stats stats;
unsigned int n_ts;
unsigned int current_ts;
unsigned int next_ts;
log_device *logs;
unsigned int n_logs;
int enabled_log;
};
const stats_engine BAD_STATS_ENGINE = NULL;
static void update_n_ts_stats_engine(stats_engine engine);
static void update_index_stats_engine(unsigned int *index);
static void log_stats_engine(stats_engine engine, int verbose);
static int check_log_configuration(stats_engine engine);
static void log_msg(stats_engine engine,
const char *msg);
static void log_clear(stats_engine engine);
static void LOG(stats_engine engine, const char *fmt, ...);
static int timespec_subtract(struct timespec *result,
struct timespec *x,
struct timespec *y);
static double compute_mean(const double *values,
const unsigned int n_values);
static double compute_stdev(const double *values,
const unsigned int n_values,
const double mean);
static int compute_ts_per_second(const int sec,
const struct timespec *ts,
const unsigned int n_ts);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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 <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <stdarg.h>
#include <errno.h>
#include "stats-engine.h"
#include "stats-engine-private.h"
stats_engine create_stats_engine(void)
{
stats_engine engine = calloc(1, sizeof(*engine));
return engine;
}
int check_stats_engine(stats_engine engine)
{
if(engine != BAD_STATS_ENGINE)
return 0;
else
return 1;
}
void attach_log_devices_to_stats_engine(stats_engine engine,
log_device *log_devs,
unsigned int n_log_devs)
{
if(check_stats_engine(engine) || !log_devs || n_log_devs == 0)
return;
engine->logs = log_devs;
engine->n_logs = n_log_devs;
}
int enable_log_for_stats_engine(stats_engine engine)
{
if(check_stats_engine(engine))
return -EINVAL;
engine->enabled_log = 1;
return 0;
}
int disable_log_for_stats_engine(stats_engine engine)
{
if(check_stats_engine(engine))
return -EINVAL;
return 0;
}
int add_usr_timestamp_to_stats_engine(stats_engine engine, struct timespec *ts)
{
struct timespec system_ts;
if(check_stats_engine(engine))
return -EINVAL;
clock_gettime(CLOCK_REALTIME, &system_ts);
engine->sys_ts[engine->next_ts] = system_ts;
engine->usr_ts[engine->next_ts] = *ts;
update_index_stats_engine(&engine->next_ts);
update_n_ts_stats_engine(engine);
return 0;
}
int run_stats_engine(stats_engine engine)
{
struct timespec ts;
int neg;
int count_ts;
if(check_stats_engine(engine))
return -EINVAL;
neg = timespec_subtract(&ts, &engine->sys_ts[engine->current_ts],
&engine->usr_ts[engine->current_ts]);
engine->diff_ts[engine->current_ts] = ((neg) ? -1.0 : 1.0)*(ts.tv_sec + ((double) ts.tv_nsec / NS_IN_A_SEC));
engine->stats.mean = compute_mean(engine->diff_ts, engine->n_ts);
engine->stats.std = compute_stdev(engine->diff_ts, engine->n_ts, engine->stats.mean);
count_ts = compute_ts_per_second(engine->usr_ts[engine->current_ts].tv_sec,
engine->usr_ts, engine->n_ts);
if(count_ts != -1)
engine->stats.ts_per_second = count_ts;
engine->stats.ts_total++;
log_stats_engine(engine, 0);
update_index_stats_engine(&engine->current_ts);
return 0;
}
void destroy_stats_engine(stats_engine engine)
{
if(check_stats_engine(engine))
return;
free(engine);
}
static void update_n_ts_stats_engine(stats_engine engine)
{
if(engine->n_ts < MAX_TS)
engine->n_ts++;
}
static void update_index_stats_engine(unsigned int *index)
{
if(*index == MAX_TS-1)
*index = 0;
else
(*index)++;
}
static void log_stats_engine(stats_engine engine, int verbose)
{
LOG(engine, "\n===================== STATS ENGINE LOG =====================\n");
if(verbose) {
LOG(engine, "n_ts: %d", engine->n_ts);
LOG(engine, "next_ts: %d\n", engine->next_ts);
for(int i = 0 ; i < engine->n_ts ; i++) {
LOG(engine, "\tsys_ts[%d]: sec %ld, nsec %ld",
i, engine->sys_ts[i].tv_sec,
engine->sys_ts[i].tv_nsec);
LOG(engine, "\twr_ts[%d]: sec %ld, nsec %ld",
i, engine->usr_ts[i].tv_sec,
engine->usr_ts[i].tv_nsec);
LOG(engine, "\tdiff[%d]: diff %f",
i, engine->diff_ts[i]);
}
}
LOG(engine, "\nMetrics => nts: %d, nts_per_sec: %d\nmean: %f, stdev: %f",
engine->stats.ts_total, engine->stats.ts_per_second, engine->stats.mean, engine->stats.std);
LOG(engine, "\n============================================================\n");
log_clear(engine);
}
static int check_log_configuration(stats_engine engine)
{
if(!engine->logs || engine->n_logs == 0)
return 1;
if(!engine->enabled_log)
return 1;
return 0;
}
static void log_msg(stats_engine engine, const char *msg)
{
if(check_log_configuration(engine))
return;
for(int i = 0 ; i < engine->n_logs ; i++)
send_to_log_device(engine->logs[i], msg);
}
static void log_clear(stats_engine engine)
{
if(check_log_configuration(engine))
return;
for(int i = 0 ; i < engine->n_logs ; i++)
clear_log_device(engine->logs[i]);
}
static void LOG(stats_engine engine, const char *fmt, ...)
{
va_list ap;
char buffer[MAX_TMP_BUF] = {0};
va_start(ap, fmt);
vsnprintf(buffer, MAX_TMP_BUF-1, fmt, ap);
va_end(ap);
log_msg(engine, buffer);
}
static int timespec_subtract(struct timespec *result,
struct timespec *x,
struct timespec *y)
{
struct timespec xx = *x;
struct timespec yy = *y;
x = &xx; y = &yy;
if (x->tv_nsec > NS_IN_A_SEC-1)
{
x->tv_sec += x->tv_nsec / NS_IN_A_SEC;
x->tv_nsec %= NS_IN_A_SEC;
}
if (y->tv_nsec > NS_IN_A_SEC-1)
{
y->tv_sec += y->tv_nsec / NS_IN_A_SEC;
y->tv_nsec %= NS_IN_A_SEC;
}
result->tv_sec = x->tv_sec - y->tv_sec;
result->tv_nsec = x->tv_nsec - y->tv_nsec;
if(result->tv_sec>0 && result->tv_nsec < 0)
{
result->tv_nsec += NS_IN_A_SEC;
result->tv_sec--;
}
else if(result->tv_sec<0 && result->tv_nsec > 0)
{
result->tv_nsec -= NS_IN_A_SEC;
result->tv_sec++;
}
return (result->tv_sec < 0) || (result->tv_nsec<0);
}
static double compute_mean(const double *values,
const unsigned int n_values)
{
double mean = 0.0;
for(int i = 0 ; i < n_values ; i++) {
mean += values[i];
}
mean /= n_values;
return mean;
}
static double compute_stdev(const double *values,
const unsigned int n_values,
const double mean)
{
double stdev = 0.0;
double aux = 0.0;
for(int i = 0 ; i < n_values ; i++) {
aux += pow((values[i]-mean),2);
}
stdev = sqrt((aux/n_values));
return stdev;
}
static int compute_ts_per_second(const int sec,
const struct timespec *ts,
const unsigned int n_ts)
{
static int prev_sec = -1;
int count = -1;
if(prev_sec == -1) {
prev_sec = sec;
}
else {
if(prev_sec != sec) {
count = 0;
for(int i = 0 ; i < n_ts ; i++) {
if(ts[i].tv_sec == prev_sec)
count++;
}
prev_sec = sec;
}
}
return count;
}
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __STATS_ENGINE_H__
#define __STATS_ENGINE_H__
#include <time.h>
#include "log-device.h"
typedef struct _stats_engine *stats_engine;
stats_engine create_stats_engine(void);
int check_stats_engine(stats_engine engine);
void attach_log_devices_to_stats_engine(stats_engine engine,
log_device *log_devs,
unsigned int n_log_devs);
int enable_log_for_stats_engine(stats_engine engine);
int disable_log_for_stats_engine(stats_engine engine);
int add_usr_timestamp_to_stats_engine(stats_engine engine,
struct timespec *ts);
int run_stats_engine(stats_engine engine);
void destroy_stats_engine(stats_engine engine);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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.
*/
#ifndef __DEMO_IRQ_PRIVATE_H__
#define __DEMO_IRQ_PRIVATE_H__
#define PROG_NAME "irq-demo"
#define TEST_FMC_DIO_CH 5
#define TEST_FMC_DIO_COUNT -1
#define TEST_FMC_DIO_PERIOD 100000000
#define STATS_LOG_PATH "./.irq-demo.log"
#define PROCESS_SLEEP_US 100000
struct _user_args {
char *fmc_dev_path;
unsigned int irq_period;
};
typedef struct _user_args * user_args;
static void demo_irq_process_loop(fmc_dio_device fmc_dev, stats_engine engine);
static void process_timestamps_to_engine(stats_engine engine, struct timespec *ts,
unsigned int nts);
static user_args create_user_arguments(void);
static int parse_user_arguments(int argc, char *argv[], user_args parsed_args);
static void destroy_user_arguments(user_args args);
static void show_help(void);
static unsigned int check_user_stop(void);
static void set_stdin_as_nonblocking(void);
static void exit_abruptly(int sig);
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
*
* 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 <signal.h>
#include <fcntl.h>
#include <time.h>
#include "stats-engine.h"
#include "fmc-dio-device.h"
#include "printf-log.h"
#include "file-log.h"
#include "irq-demo-private.h"
/* Static data for signal handlers */
static fmc_dio_device global_fmc_dev = NULL;
/*
* irq-demo /dev/<fmc-device-file> [<irq period>]
* To exit: Please press 'q' in the keyboard or send a stop signal (CNTR+C)
*
* This DEMO shows the behavior of the interrupts for the FMC DIO device. The basic operation
* is to get all the timestamps generated by the FMC DIO channel and compute a basic statistics
* taking into consideration the system time such as time difference, mean and standard deviation.
*
* In order to work properly, it uses several resources that can be found in under dep folder:
* - loggers: General implementation for logs generation. Two kind of loggers are supported now:
* - printf-log: It uses the stdout for messages.
* - file-log: It uses a specific file for messages.
* - fmc-dio-device: Implementation of FMC DIO device functionalities.
* - stats-engine: Statistics engine code. It contains all the logic to compute the different metrics
*/
int main(int argc, char *argv[])
{
fmc_dio_device dev;
log_device gen_log;
log_device stats_log;
user_args user_arguments;
log_device logs[2];
unsigned int n_logs = 2;
unsigned int user_stop = 0;
stats_engine sengine;
int ret = 0;
/* Initialize signal handler for force exit */
signal(SIGINT, exit_abruptly);
signal(SIGQUIT, exit_abruptly);
/* Create the loggers: One for stdout and the other for a specific file */
gen_log = create_printf_log_device();
stats_log = create_file_log_device(STATS_LOG_PATH);
/* Loggers are stored in array:
* -[0]: General logger for stdout
* -[1]: File logger for stats
*/
logs[0] = gen_log;
logs[1] = stats_log;
/* Parse user arguments (FMC DIO dev entry path and optionally irq period) */
user_arguments = create_user_arguments();
if(parse_user_arguments(argc, argv, user_arguments)) {
/* In case of failure, show the help message and exit */
show_help();
ret = 1;
goto out_log;
}
/* Create stats engine to compute timestamps metrics (difference, mean, stdev) */
sengine = create_stats_engine();
if(check_stats_engine(sengine)) {
/* In case of failure, exit */
ret = 1;
goto out_user_args;
}
/* Attach loggers to stats engine
* This allows the engine to generate output messages for the stdout and log file
*/
attach_log_devices_to_stats_engine(sengine, logs, n_logs);
/* Enable log for stats engine (by default is disabled) */
enable_log_for_stats_engine(sengine);
/* Create FMC DIO device using user arguments */
dev = create_fmc_dio_device(user_arguments->fmc_dev_path);
if(open_fmc_dio_device(dev)) {
/* In case of failure, exit */
ret = 1;
goto out_stats_engine;
}
/*
* Attach loggers to FMC DIO device
* This allows the FMC DIO to generate output messages for the stdout and log file
*/
attach_log_devices_to_fmc_dio_device(dev, logs, n_logs);
enable_log_for_fmc_dio_device(dev);
/* Before starting IRQ demo, Interrupts should be properly configured:
* 1) Disable all interrupts
* 2) Enable the TEST_FMC_DIO_CH interrupt line
* 3) Setup specific information for interrupt line (period from user arguments or default one)
*/
disable_fmc_dio_device_all_irq(dev);
enable_fmc_dio_device_irq(dev, TEST_FMC_DIO_CH);
setup_fmc_dio_device_irq(dev, user_arguments->irq_period, TEST_FMC_DIO_COUNT);
/* Configure stdin as non-blocking. It is required for the user check exit function */
set_stdin_as_nonblocking();
/* Save FMC DIO dev in globals for the signal handlers */
global_fmc_dev = dev;
/* Main loop of IRQ demo (until user requests to exit) */
while(!user_stop) {
/* Main logic funtion (get timestamps and process them in the stats engine) */
demo_irq_process_loop(dev, sengine);
/* Check if user has requested to exit */
user_stop = check_user_stop();
}
/* Exit sequence */
/* Disable all interrupts */
disable_fmc_dio_device_all_irq(dev);
out_fmc_dio_device:
/* Close FMC DIO device */
close_fmc_dio_device(dev);
out_stats_engine:
/* Close stats engine */
destroy_stats_engine(sengine);
out_user_args:
/* Free resources for user arguments */
destroy_user_arguments(user_arguments);
out_log:
/* Close loggers */
destroy_log_device(stats_log);
destroy_log_device(gen_log);
return ret;
}
static void demo_irq_process_loop(fmc_dio_device fmc_dev, stats_engine engine)
{
struct timespec *ts;
unsigned int nts;
/* Process loop:
* - Get timestamps from FMC DIO device (specific channel under testing)
* - Pass them to the stats engine and generate the statistics
* - Sleep for a while to avoid excesive CPU consumption
*/
while(!get_utc_ts_from_fmc_dio_device(fmc_dev, TEST_FMC_DIO_CH, &ts, &nts)) {
process_timestamps_to_engine(engine, ts, nts);
usleep(PROCESS_SLEEP_US);
}
}
static void process_timestamps_to_engine(stats_engine engine, struct timespec *ts,
unsigned int nts)
{
for(int i = 0 ; i < nts ; i++) {
/* Pass a timestamp to stats engine */
add_usr_timestamp_to_stats_engine(engine, &ts[i]);
/* Compute stats taking into consideration all the timestamps inside the engine */
run_stats_engine(engine);
}
/* Finally, free memory for timestamps (reserved by FMC DIO device) */
free(ts);
}
static user_args create_user_arguments(void)
{
user_args arguments;
arguments = calloc(1, sizeof(*arguments));
return arguments;
}
static int parse_user_arguments(int argc, char *argv[], user_args parsed_args)
{
if(argc != 2 && argc != 3)
return 1;
parsed_args->fmc_dev_path = argv[1];
if(argc == 3)
sscanf(argv[2], "%d", &parsed_args->irq_period);
else
parsed_args->irq_period = TEST_FMC_DIO_PERIOD;
return 0;
}
static void destroy_user_arguments(user_args args)
{
if(args) {
free(args);
}
}
static void show_help(void)
{
printf("usage: %s <FMC device path> [<irq period (ns)>]\n", PROG_NAME);
}
static unsigned int check_user_stop(void)
{
char c;
c = getchar();
return (c == 'q');
}
static void set_stdin_as_nonblocking(void)
{
fcntl (STDIN_FILENO, F_SETFL, O_NONBLOCK);
}
static void exit_abruptly(int sig)
{
disable_fmc_dio_device_all_irq(global_fmc_dev);
exit(0);
}
......@@ -24,6 +24,8 @@
#include <linux/fmc.h>
#include <linux/fmc-sdb.h>
#include <linux/delay.h>
#include "fmc-dio.h"
#ifdef DIO_STAT
......@@ -105,6 +107,7 @@ static int fmc_dio_int_cmd_pulse(struct fmc_dio *dev,
uint32_t reg;
int ch;
struct regmap_common dio;
uint32_t pulse_per_loaded;
dio = get_regmap_common(dev->version);
......@@ -123,17 +126,27 @@ static int fmc_dio_int_cmd_pulse(struct fmc_dio *dev,
writel(reg | (1 << 4*ch), base + dio.iomode_reg);
/* Clear width and period registers */
pulse_per_loaded = readl(base + map->pulse_per);
writel(0, base + map->pulse_per);
writel(1, base + map->pulse); //Set to 1 to avoid 3 second pulse due to compensation into gw
writel(1 << ch, base + dio.latch_reg);
writel(1 << ch, base + dio.pulse_reg);
atomic_set(&c->count, 0);
writel(ts[1].tv_nsec / 8, base + map->pulse); /* width */
if(cmd->flags & WR_DIO_F_REL)
{
pulse_per_loaded *= 8;
msleep(pulse_per_loaded/1000000);
}
if(cmd->value == 0)
if(cmd->value == 0 && cmd->flags & WR_DIO_F_LOOP)
return 0;
writel(ts[1].tv_nsec / 8, base + map->pulse); /* width */
//if(cmd->value == 0)
// return 0;
if (cmd->flags & WR_DIO_F_NOW) {
/* Generate a pulse train from current time in V2 DIO version*/
......@@ -141,9 +154,9 @@ static int fmc_dio_int_cmd_pulse(struct fmc_dio *dev,
if (cmd->flags & WR_DIO_F_LOOP && cmd->value != 1) {
writel(ts[2].tv_nsec / 8, base + map->pulse_per);
c->target_channel = ch;
if (cmd->value > 1) {
cmd->value-=2;
}
cmd->value-=2;
atomic_set(&c->count, cmd->value);
}
}
......@@ -176,9 +189,7 @@ static int fmc_dio_int_cmd_pulse(struct fmc_dio *dev,
}
else {
writel(ts[2].tv_nsec / 8, base + map->pulse_per);
if (cmd->value > 1) {
cmd->value-=1;
}
cmd->value-=2;
}
atomic_set(&c->count, cmd->value);
......@@ -201,6 +212,10 @@ static int fmc_dio_int_cmd_update_width(struct fmc_dio *dev,
struct regmap_common dio;
uint32_t period;
if(dev->version == 0){
return -EINVAL;
}
dio = get_regmap_common(dev->version);
ch = cmd->channel;
......@@ -350,6 +365,41 @@ static int fmc_dio_int_cmd_inout(struct fmc_dio *dev,
return 0;
}
static int fmc_dio_int_cmd_mask_irq(struct fmc_dio *dev,
struct wr_dio_cmd *cmd)
{
struct fmc_dio_gpio_block __iomem *gpio = dev->gpio;
int mask, ch, last, bits;
uint32_t reg, iomode;
void __iomem *base = dev->dio;
if(dev->version == 0){ //Not implemented in old version
return -EINVAL;
}
struct regmap_common dio;
dio = get_regmap_common(dev->version);
if (cmd->value == WR_DIO_F_MASK_READ_IRQ) {
reg = readl(base + dio.eic_imr_reg);
if(reg & cmd->channel) {
cmd->value = WR_DIO_F_MASK_ENABLE_IRQ;
return 0;
} else {
cmd->value = WR_DIO_F_MASK_DISABLE_IRQ;
return 0;
}
} else if (cmd->value == WR_DIO_F_MASK_ENABLE_IRQ) {
writel(cmd->channel, base + dio.eic_ier_reg);
} else if (cmd->value == WR_DIO_F_MASK_DISABLE_IRQ) {
writel(cmd->channel, base + dio.eic_idr_reg);
} else {
return -1;
}
return 0;
}
static int fmc_dio_int_cmd_irq(struct fmc_dio *dev,
struct wr_dio_cmd *cmd)
{
......@@ -363,6 +413,7 @@ static int fmc_dio_int_cmd_irq(struct fmc_dio *dev,
uint32_t reg;
int ch;
struct regmap_common dio;
uint32_t pulse_per_loaded;
if(dev->version != 1)
return -ENOTSUPP;
......@@ -380,6 +431,21 @@ static int fmc_dio_int_cmd_irq(struct fmc_dio *dev,
ts = cmd->t;
pulse_per_loaded = readl(base + map->pulse_per);
writel(0, base + map->pulse_per);
writel(1, base + map->pulse); //Set to 1 to avoid 3 second pulse due to compensation into gw
writel(1 << ch, base + dio.latch_reg);
atomic_set(&c->count, 0);
if(cmd->flags & WR_DIO_F_REL)
{
pulse_per_loaded *= 8;
msleep(pulse_per_loaded/1000000);
}
if(cmd->value == 0 && cmd->flags & WR_DIO_F_LOOP)
return 0;
/* First, configure the IRQ channel */
reg = readl(base + dio.iomode_reg);
writel((reg | CHANNEL_5_IRQ_EN_MASK), base + dio.iomode_reg);
......@@ -390,9 +456,9 @@ static int fmc_dio_int_cmd_irq(struct fmc_dio *dev,
if (cmd->flags & WR_DIO_F_LOOP && cmd->value != 1) {
writel(ts[1].tv_nsec / 8, base + map->pulse_per);
c->target_channel = ch;
if (cmd->value > 1) {
cmd->value-=2;
}
cmd->value-=2;
atomic_set(&c->count, cmd->value);
}
writel(1 << ch, base + dio.pulse_reg);
......@@ -414,11 +480,10 @@ static int fmc_dio_int_cmd_irq(struct fmc_dio *dev,
if (cmd->flags & WR_DIO_F_LOOP && cmd->value != 1) {
c->target_channel = ch;
writel(ts[1].tv_nsec / 8, base + map->pulse_per);
if (cmd->value > 1) {
cmd->value-=2;
}
/* Configure period */
writel( (ts[1].tv_nsec / 8) + (ts[1].tv_sec * 1000000000 / 8), base + map->pulse_per);
cmd->value-=2;
atomic_set(&c->count, cmd->value);
c->prevts = ts[0]; /* our current setpoint */
......@@ -475,6 +540,9 @@ int fmc_dio_int_ioctl(struct fmc_dio *dev, unsigned int ioctlcmd,
case WR_DIO_CMD_UPDATE_WIDTH:
ret = fmc_dio_int_cmd_update_width(dev, cmd);
break;
case WR_DIO_CMD_MASK_IRQ:
ret = fmc_dio_int_cmd_mask_irq(dev, cmd);
break;
default:
ret = -EINVAL;
goto out;
......
......@@ -109,6 +109,7 @@ enum wr_dio_cmd_name {
WR_DIO_CMD_INOUT,
WR_DIO_CMD_IRQ,
WR_DIO_CMD_UPDATE_WIDTH,
WR_DIO_CMD_MASK_IRQ,
};
/*
......@@ -160,3 +161,6 @@ struct wr_dio_cmd {
#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 */
#define WR_DIO_F_MASK_READ_IRQ 0x20
#define WR_DIO_F_MASK_ENABLE_IRQ 0x40
#define WR_DIO_F_MASK_DISABLE_IRQ 0x80
\ No newline at end of file
......@@ -129,7 +129,7 @@ static int scan_pulse(int argc, char **argv)
}
if (cmd->t[2].tv_nsec < 1000000)
{
fprintf(stderr, "%s: %s: Frequency is greater than 1 kHz \n",
fprintf(stderr, "%s: %s: Frequency is greater than 1 kHz or higher than 1 s\n",
prgname, argv[0]);
return -1;
}
......@@ -362,6 +362,64 @@ static int scan_inout(int argc, char **argv)
return 0;
}
static int scan_mask_irq(int argc, char **argv)
{
int i, ch;
char c;
uint32_t value_internal;
cmd->flags = WR_DIO_F_MASK;
cmd->channel = 0;
cmd->value = 0;
if (argc == 2) {
cmd->value = WR_DIO_F_MASK_READ_IRQ;
} else if (argc == 3){
if (argv[2][0] == 'Y') {
cmd->value = WR_DIO_F_MASK_ENABLE_IRQ;
}
else if (argv[2][0] == 'y') {
cmd->value = WR_DIO_F_MASK_DISABLE_IRQ;
}
else
fprintf(stderr, "%s: mask_irq: invalid argument "
"\"%s\"\n", prgname, argv[2]);
} else {
fprintf(stderr, "%s: %s: wrong number of arguments\n",
prgname, argv[0]);
fprintf(stderr, " Use: %s <channel> "
"[<Y/y>]\n", argv[0]);
return -1;
}
if (sscanf(argv[1], "%i", &ch) != 1 || ch < 0 || ch > 5) {
fprintf(stderr, "%s: mask_irq: invalid channel "
"\"%s\"\n", prgname, argv[1]);
return -1;
}
cmd->channel |= 1 << ch;
value_internal = cmd->value; //Save value to read mask
if (ioctl(fd, PRIV_MEZZANINE_CMD, cmd) < 0) {
fprintf(stderr, "%s: ioctl(PRIV_MEZZANINE_CMD(%s)): %s\n",
prgname, devname, strerror(errno));
return -1;
}
if ( value_internal == WR_DIO_F_MASK_READ_IRQ ) {
if(cmd->value == WR_DIO_F_MASK_ENABLE_IRQ)
printf("Ch %i interrupt enabled\n",ch);
else
printf("Ch %i interrupt disabled\n",ch);
}
return 0;
}
static int scan_irq(int argc, char **argv)
{
char c;
......@@ -401,9 +459,9 @@ static int scan_irq(int argc, char **argv)
prgname, argv[0], argv[1]);
return -1;
}
if(cmd->t[1].tv_nsec < 1000000 || cmd->t[1].tv_sec)
if(cmd->t[1].tv_nsec < 1000000 || cmd->t[1].tv_sec > 2)
{
fprintf(stderr, "%s: %s: Frequency is greater than 1 kHz and lower than 1 Hz\n",
fprintf(stderr, "%s: %s: Frequency is greater than 1 kHz and lower than 0.5 Hz\n",
prgname, argv[0]);
return -1;
}
......@@ -507,6 +565,10 @@ int main(int argc, char **argv)
cmd->command = WR_DIO_CMD_UPDATE_WIDTH;
if (scan_width(argc, argv) < 0)
exit(1);
} else if (!strcmp(argv[0], "mask_irq")) {
cmd->command = WR_DIO_CMD_MASK_IRQ;
if (scan_mask_irq(argc, argv) < 0)
exit(1);
} else {
fprintf(stderr, "%s: unknown command \"%s\"\n", prgname,
argv[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