gnurabbit: added new version

parent 80d44a5f
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
DIRS = kernel user bench# doc
all:
@for d in $(DIRS); do $(MAKE) -C $$d $@ || exit 1; done
clean:
@for d in $(DIRS); do $(MAKE) -C $$d $@ || exit 1; done
This work is part of the White Rabbit project at CERN (cern.ch).
The package includes the raw I/O driver and the GN4124 driver. The kernel
part lives in the kernel/ subdir of the package, while user-space utilities
are under user/
Documentation is under doc/ and is written in Texinfo, which though
old is simple enough to require little efforts on my side. Output is
pdf, text and info.
To compile, just "make". If you miss TeX or texinfo you won't have the
docs. If you miss the compiler or gmake you won't have anything.
To make clean just "make clean".
ioctl
irq878
rdwr
\ No newline at end of file
CFLAGS = -Wall -ggdb -I../kernel
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
ALL = ioctl irq878 rdwr
all: $(ALL)
clean:
rm -f $(ALL) *.o *~
\ No newline at end of file
/*
* Trivial performance test for ioctl I/O
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "rawrabbit.h"
#define DEVNAME "/dev/rawrabbit"
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
struct rr_iocmd iocmd = {
.address = __RR_SET_BAR(4) | 0xa08,
.datasize = 4,
};
int main(int argc, char **argv)
{
int fd, count, count0, usec;
struct timeval tv1, tv2;
if (argc != 2) {
fprintf(stderr, "%s: use \"%s <count>\"\n", argv[0], argv[0]);
exit(1);
}
count0 = count = atoi(argv[1]);
if (!count) {
fprintf(stderr, "%s: not a number \"%s\"\n", argv[0], argv[1]);
exit(1);
}
fd = open(DEVNAME, O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: %s: %s\n", argv[0], DEVNAME,
strerror(errno));
exit(1);
}
gettimeofday(&tv1, NULL);
while (count--) {
static int values[] = {
/* These make a pwm signal on the leds */
0xf000, 0xf000, 0xf000, 0xf000,
0xe000, 0xc000, 0x8000, 0x0000
};
iocmd.data32 = values[ count % ARRAY_SIZE(values) ];
if (ioctl(fd, RR_WRITE, &iocmd) < 0) {
fprintf(stderr, "%s: %s: ioctl: %s\n", argv[0], DEVNAME,
strerror(errno));
exit(1);
}
}
gettimeofday(&tv2, NULL);
usec = (tv2.tv_sec - tv1.tv_sec) * 1000 * 1000
+ tv2.tv_usec - tv1.tv_usec;
printf("%i ioctls in %i usecs\n", count0, usec);
printf("%i ioctls per second\n",
(int)(count0 * 1000LL * 1000LL / usec));
exit(0);
}
/*
* Trivial performance test for irq management
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "rawrabbit.h"
#define DEVNAME "/dev/rawrabbit"
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
struct rr_devsel devsel = {
.vendor = 0x109e,
.device = 0x036e,
.subvendor = RR_DEVSEL_UNUSED,
.bus = RR_DEVSEL_UNUSED,
};
#define ENA_VAL 0x02
#define ENA_REG (__RR_SET_BAR(0) | 0x104)
#define ACK_REG (__RR_SET_BAR(0) | 0x100)
struct rr_iocmd iocmd = {
.datasize = 4,
};
int main(int argc, char **argv)
{
int fd, count, count0, nsec;
unsigned long long total = 0LL;
if (argc != 2) {
fprintf(stderr, "%s: use \"%s <count>\"\n", argv[0], argv[0]);
exit(1);
}
count0 = count = atoi(argv[1]);
if (!count) {
fprintf(stderr, "%s: not a number \"%s\"\n", argv[0], argv[1]);
exit(1);
}
fd = open(DEVNAME, O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: %s: %s\n", argv[0], DEVNAME,
strerror(errno));
exit(1);
}
/* choose the 878 device */
if (ioctl(fd, RR_DEVSEL, &devsel) < 0) {
fprintf(stderr, "%s: %s: ioctl: %s\n", argv[0], DEVNAME,
strerror(errno));
exit(1);
}
/* enable */
iocmd.address = ENA_REG;
iocmd.data32 = ENA_VAL;
if (ioctl(fd, RR_WRITE, &iocmd) < 0) {
fprintf(stderr, "%s: %s: ioctl: %s\n", argv[0], DEVNAME,
strerror(errno));
exit(1);
}
iocmd.address = ACK_REG;
while (count) {
nsec = ioctl(fd, RR_IRQWAIT);
if (nsec < 0) {
if (errno == EAGAIN) {
ioctl(fd, RR_IRQENA);
continue;
}
fprintf(stderr, "%s: %s: ioctl: %s\n", argv[0], DEVNAME,
strerror(errno));
exit(1); /* Argh! */
}
count--;
/* ack: this must work */
ioctl(fd, RR_WRITE, &iocmd);
nsec = ioctl(fd, RR_IRQENA, &iocmd);
if (nsec < 0) {
fprintf(stderr, "%s: %s: ioctl: %s\n", argv[0], DEVNAME,
strerror(errno));
/* Hmm... */
} else {
total += nsec;
}
}
/* now disable and then acknowledge */
iocmd.address = ENA_REG;
iocmd.data32 = 0;
ioctl(fd, RR_WRITE, &iocmd);
iocmd.address = ACK_REG;
iocmd.data32 = ~0;
ioctl(fd, RR_WRITE, &iocmd);
printf("got %i interrupts, average delay %lins\n", count0,
(long)(total / count0));
exit(0);
}
/*
* Trivial performance test for read(2)/write(2) I/O
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "rawrabbit.h"
#define DEVNAME "/dev/rawrabbit"
int main(int argc, char **argv)
{
int fd, count, count0, usec;
struct timeval tv1, tv2;
if (argc != 2) {
fprintf(stderr, "%s: use \"%s <count>\"\n", argv[0], argv[0]);
exit(1);
}
count0 = count = atoi(argv[1]);
if (!count) {
fprintf(stderr, "%s: not a number \"%s\"\n", argv[0], argv[1]);
exit(1);
}
fd = open(DEVNAME, O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: %s: %s\n", argv[0], DEVNAME,
strerror(errno));
exit(1);
}
/* write */
lseek(fd, __RR_SET_BAR(4) | 0xa08, SEEK_SET);
gettimeofday(&tv1, NULL);
while (count--) {
static uint32_t values[] = {0x0000, 0xf000};
write(fd, values + (count & 1), sizeof(values[0]));
lseek(fd, -sizeof(values[0]), SEEK_CUR);
}
gettimeofday(&tv2, NULL);
usec = (tv2.tv_sec - tv1.tv_sec) * 1000 * 1000
+ tv2.tv_usec - tv1.tv_usec;
printf("%i writes in %i usecs\n", count0, usec);
printf("%i writes per second\n",
(int)(count0 * 1000LL * 1000LL / usec));
/* read: we cut and paste the code, oh so lazy */
count = count0;
lseek(fd, __RR_SET_BAR(4) | 0xa08, SEEK_SET);
gettimeofday(&tv1, NULL);
while (count--) {
static uint32_t value;
read(fd, &value, sizeof(value));
lseek(fd, -sizeof(value), SEEK_CUR);
}
gettimeofday(&tv2, NULL);
usec = (tv2.tv_sec - tv1.tv_sec) * 1000 * 1000
+ tv2.tv_usec - tv1.tv_usec;
printf("%i reads in %i usecs\n", count0, usec);
printf("%i reads per second\n",
(int)(count0 * 1000LL * 1000LL / usec));
exit(0);
}
# temporaries
gnurabbit.texi
gnurabbit.aux
gnurabbit.cp
gnurabbit.fn
gnurabbit.ky
gnurabbit.log
gnurabbit.pg
gnurabbit.toc
gnurabbit.tp
gnurabbit.vr
# outputs
gnurabbit.html
gnurabbit.info
gnurabbit.pdf
gnurabbit.txt
#
# Makefile for the documentation directory
#
# Copyright 1994,2000,2010 Alessandro Rubini <rubini@linux.it>
#
#################
#
# BE CAREFUL in editing:
# due to the large number of index files, and my use of a non standard
# info input file, any file $(TARGET).* is removed by "make clean"
#
# I chose to use a prefix for the input file ("doc.$(TARGET)"), to ease
# makeing clean and applying my own rules.
#
###################################################################
TARGET = gnurabbit
# Assume makeinfo can do images and --html.
# In any case, MAKEINFO can be specified on the commandline
MAKEINFO = makeinfo
##############################################
INPUT = $(wildcard *.in)
TEXI = $(INPUT:.in=.texi)
.SUFFIXES: .in .texi .info .html .txt
.in.texi:
@rm -f $@ 2> /dev/null
sed -f ./infofilter $< > $@
chmod -w $@
# unfortuantely implicit rules are not concatenated, so force a make run
%.pdf: %.texi $(TEXI)
$(MAKE) $(TEXI)
texi2pdf --batch $<
%.info: %.texi $(TEXI)
$(MAKE) $(TEXI)
$(MAKEINFO) $< -o $@
%.html: %.texi $(TEXI)
$(MAKE) $(TEXI)
$(MAKEINFO) --html --no-split -o $@ $<
%.txt: %.texi $(TEXI)
$(MAKE) $(TEXI)
$(MAKEINFO) --no-headers $< > $@
##############################################
ALL = $(TARGET).info $(TARGET).txt $(TARGET).html $(TARGET).pdf
all: images $(TEXI) $(ALL)
images::
if [ -d images ]; then $(MAKE) -C images || exit 1; fi
info: $(TARGET).info
check: _err.ps
gs -sDEVICE=linux -r320x200x16 $<
terse:
for n in cp fn ky pg toc tp vr; do \
rm -f $(TARGET).$$n; \
done
rm -f *~
clean: terse
rm -f $(ALL) $(TEXI)
install:
\input texinfo @c -*-texinfo-*-
%
% gnurabbit.in - main file for the documentation
%
% Copyright (C) 2010 CERN (www.cern.ch)
% Author: Alessandro Rubini <rubini@gnudd.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.
%%%%
%------------------------------------------------------------------------------
%
% NOTE FOR THE UNAWARE USER
% =========================
%
% This file is a texinfo source. It isn't the binary file of some strange
% editor of mine. If you want ASCII, you should "make gnurabbit.txt".
%
%------------------------------------------------------------------------------
%
% This is not a conventional info file...
% I use three extra features:
% - The '%' as a comment marker, if at beginning of line ("\%" -> "%")
% - leading blanks are allowed (this is something I can't live without)
% - braces are automatically escaped when they appear in example blocks
%
@comment %**start of header
@documentlanguage en
@documentencoding ISO-8859-1
@setfilename gnurabbit.info
@settitle Gnu Rabbit
@iftex
@afourpaper
@end iftex
@paragraphindent none
@comment %**end of header
@setchapternewpage off
@set update-month January 2011
@finalout
@titlepage
@title Gnu Rabbit
@subtitle Code for GENNUM and White Rabbit
@subtitle @value{update-month}
@author Alessandro Rubini (@code{rubini@@gnudd.com})
@author Work sponsored by CERN (@code{www.cern.ch})
@end titlepage
@setchapternewpage off
@headings single
@c ##########################################################################
@node Top, Raw PCI I/O, (dir), (dir)
@top Introduction
This package is basically a
module for raw PCI I/O, used at CERN as development tool to help
prototyping White Rabbit hardware and software.
The main target of this code is the GN4124 PCI-E evaluation board, but
there is little stuff here that is specific to the gennum chip -- mainly
it's the firmware loader.
All code and documentation is released according to the GNU GPL, version
2 or (at your option) any later version.
@menu
* Raw PCI I/O::
* Firmware Loader::
@end menu
@c ##########################################################################
@node Raw PCI I/O, Firmware Loader, Top, Top
@chapter Raw PCI I/O
The kernel module for raw I/O is called @i{rawrabbit}. After
running @i{make} you'll find a file called @code{rawrabbit.ko}.
I'm sorry for this mismatch in naming, but initially the package was
expected to host another module in addition to @i{rawrabbit}, something
Gennum-specific like @i{gnuuser} for ``GN user''.
To compile you may optionally set the following three variables in
your environment:
@table @code
@item CROSS_COMPILE
The variable defaults to the empty string, used for native compilation
@item ARCH
The variable defaults to the build architecture
@item LINUX
This is the location of the kernel source against which you
are compiling. It defaults to the place where the currently running
kernel has been compiled (assuming it was compiled on this same
system).
@end table
The code has been run-tested on version 2.6.34 and 2.6.35.
It has also been compile-tested on
2.6.24.7-rt27, 2.6.29.4-rt15, 2.6.31.6-rt19, 2.6.31.12-rt21.
The module creates a @i{misc} char device driver, with major number 10
and minor number 42. If you are running @i{udev} the special file
@code{/dev/rawrabbit} will be created automatically.
@b{Warning:} future releases of this package may change the device
number or switch to several devices, I'm yet undecided on this choice.
@menu
* General features of rawrabbit::
* Interrupt management::
* Bugs and misfeatures::
* The DMA buffer::
* System calls implemented::
* Ioctl commands::
* User space demo programs::
* User space benchmarks::
@end menu
@c ==========================================================================
@node General features of rawrabbit, Interrupt management, Raw PCI I/O, Raw PCI I/O
@section General features of rawrabbit
The driver is designed to act as a misc device (i.e. a char device)
towards user programs and as a PCI driver towards hardware, declaring
the pair @i{vendor}/@i{device} it is able to drive.
The pair of identifiers is predefined at compile time but can be changed
at run time. The defaults (set forth) in @code{rawrabbit.h} refer to
the GN4124 evaluation board. The values can also be changed at module
load time by setting the @code{vendor} and @code{device} arguments.
For example the following command
sets @i{rawrabbit} to look for a 3com Ethernet device:
@example
insmod rawrabbit.ko vendor=0x10b7 device=0x9055
@end example
When the driver is loaded it registers as a PCI driver for the
preselected vendor/device pair, but loading succeeds even if no
matching peripheral exists on the system, as user space programs can
request a different vendor/device pair at runtime. Since a single bus
might host several instances of the same peripheral, user space
programs can also specify the @i{bus} and @i{devfn} values in order to
select a specific instance of the hardware device. Similarly, the pair
@i{subvendor}/@i{subdevice} may be specified.
@c mmap
User programs can use @i{read} and @i{write}, @i{mmap} and @i{ioctl}
as described later (@b{Note:} mmap is not currently
supported). Each and every command refers to the device
currently selected by means of the vendor/device pair as well as
bus/devfn and/or subvendor/subdevice if specified.
The driver allows access to the PCI memory regions for generic I/O
operations, as well as some limited interrupt management in user space.
Programs can also access a DMA buffer, for which they can know the
physical address on a page-by-page basis.
In the source file, each global function or variable declared in the
file itself or in the associated header
has @code{rr_} as prefix in the name, even if its scope is
static. Local variables have simple names with no prefix, like @code{i}
or @code{dev}. This convention is followed so when reading
random parts of the source you can immediately know whether the symbol
is defined in the same file (like @code{rr_dev}) or is an external Linux
resource (like @code{pci_driver}).
@c ==========================================================================
@node Interrupt management, Bugs and misfeatures, General features of rawrabbit, Raw PCI I/O
@section Interrupt management
The driver is able to handle the interrupt for the active device. User
space is allowed to wait for an interrupt and acknowledge it later at
will. To allow this latency, the driver disables the interrupt as soon as
it is reported, so user-space can do the board-specific I/O before asking
to re-enable the interrupt.
The interrupt handler is registered as a shared handler, as most PCI
cards must share the interrupt request line with other peripherals. In
particular, on my development motherboard both the PCI-E and the PCI
slot share the interrupt with other core peripherals and I couldn't test
stuff if I didn't enable sharing.
Unfortunately, the @i{rawrabbit} interrupt handler
can't know if the interrupt source is its
own board or another peripherals, so all it can do is saying it
handled to the interrupt (by returning @code{IRQ_HANDLED}) and disable
it. If you are running other peripherals on the same interrupt line,
you'll need to acknowledge the interrupt pretty often, to avoid a
system lock or data loss in your storage or network device.
@c ==========================================================================
@node Bugs and misfeatures, The DMA buffer, Interrupt management, Raw PCI I/O
@section Bugs and misfeatures
@c FIXME: single open
This version of @i{rawrabbit} creates a single device and can act on a
single PCI peripheral at a time. This limitation will be removed in
later versions, as time permits.
The @i{read} and @i{write} implementations don't enforce
general data-size constraints: reading or writing 1, 2, 4, 8 bytes at a
time forces 8, 16, 32, 64 bit accesses respectively, while bigger
transfers use unpredictable access patterns to I/O memory, as
the driver uses @i{copy_from_user} and @i{copy_to_user}.
@c FIXME: interrupt is always requested
The interrupt line is always requested and handled (by disabling it).
This means that if
the line is shared with other devices, you can't avoid it being disabled
thus breaking the other devices. A specific ioctl to request/release the
handler is needed.
@c FIXME: odd bars
The driver assumes to work with PCI-E so odd BAR areas are not supported.
This limitation may be lifted in future versions if needed.
@c FIXME: cache effects
@b{Important:} please note that there may be bugs related to cache
memory. When using DMA you may encounter incorrect data due to
missing flush or invalidate instructions. If the problem is real
please report the bug to me, with as much information as possible
about the inconsistency, and I'll do my best to find the proper
solution. One solution might be adding two @i{ioctl} commands: one to
flush the buffer after it has been written and one to invalidate it
before reading; however better solutions, with no API changes, may be
viable. Or the problem may just not appear as things are already
correct, I can't tell for sure.
@c ==========================================================================
@node The DMA buffer, System calls implemented, Bugs and misfeatures, Raw PCI I/O
@section The DMA buffer
At module load time, a 1MB buffer is allocated. The actual size
can be changed by means of a module parameter, but it currently
can't be bigger than 4MB.
The buffer is allocated with @i{vmalloc}, so it is contiguous in
virtual space but not in physical space. User space can read and
write the buffer like it was BAR 12 (0xc) of the device, using
contiguous offsets from 0 up to the buffer size.
In order to DMA data to/from the buffer, the peripheral device must be
told the physical address to use. Since allocation is page-grained,
you need a different physical address for each 4kB page of data. The
driver can thus return the list of @i{page frame numbers} that make up
the @i{vmalloc} buffer. A PFN is a 32-bit number that identifies the
position of the page in physical memory. With 4kB pages, you can shift
by 12 bits to have the physical address, and a 32-bit PFN can span up
to 44 bits of physical address space.
The details about how PFNs are returned to user space are described later
where the @i{ioctl} commands are discussed. A working example is in the
@i{rrcmd} user space tool.
Unlikely what happens with I/O memory, reading and writing the DMA
buffer uses the @i{copy_*_user} functions for all accesses, so the
pattern of actual access to memory can't be controlled, but this is
not a problem for RAM (as opposed to registers).
@c ==========================================================================
@node System calls implemented, Ioctl commands, The DMA buffer, Raw PCI I/O
@section System calls implemented
The following system calls are implemented in @i{rawrabbit}:
@table @i
@item open
@itemx close
These system calls are used to keep a reference count of device use.
If the device has been opened more than once, it will refuse
to change the active device, to prevent possible confusion in
another process using @i{rawrabbit} at the same time. Please note
that after @i{fork} the device is still opened twice but the
driver can't know about it, so in this case changing the active
device is allowed, but it can be confusing nonetheless.
@item llseek
The @i{seek} family of system calls is implemented using the
default kernel implementation. A process may seek the device to
access specific registers in specific BAR areas, or the DMA
buffer. The offset
being used selects the BAR and the offset within the BAR
at the same time. Each BAR is limited to an extension of 512MB:
so BAR0 starts at 0, BAR 2 starts at 0x2000.0000 and BAR 4 starts
at offset 0x4000.0000; if you prefer symbolic names,
@code{RR_BAR_0}, @code{RR_BAR_2} and @code{RR_BAR_4}
are defined in @code{rawrabbit.h}. The DMA buffer is accessed
like it was BAR 12 (@code{RR_BAR_BUF}), so @code{0xc} or @code{c}
can be used in @i{rrcmd} (see @ref{rrcmd}).
@item read
@itemx write
By reading and writing the device, a process can access on-board
I/O space. The file position (set through @i{llseek} or by
sequential access of file data) is used to specify both the BAR
and the offset within the BAR as described above. Access to
an inexistent BAR returns @code{EINVAL}, access outside the BAR
size returns @code{EIO}.
If the hardware device offers I/O ports (instead of I/O memory), the
system calls are not supported and you must use @i{ioctl} -- @i{read}
and @i{write} will return @code{EINVAL} like the BAR was not
existent.
As a special case, reading past the DMA buffer size returns 0 (EOF),
and writing returns @code{ENOSPC}, since the DMA buffer is a memory
region and a file-like interface is best suited for command-line tools
like @code{dd}.
@item mmap
@b{Warning:} mmap is not yet implemented in this version.
The @i{mmap} system call allows direct user-space access to the
I/O memory. The device offset has the same meaning as for @i{read},
but accesses to undefined pages cause a @code{SIGBUS} to be sent.
If the device offers I/O ports (instead of I/O memory), the
@i{mmap} method can't be used on such BAR areas.
@item ioctl
A number of @i{ioctl} commands are supported, they are listed
in the next section. Note that the commands to read and write
can act both on memory and ``I/O ports'' areas.
@end table
@c ==========================================================================
@node Ioctl commands, User space demo programs, System calls implemented, Raw PCI I/O
@section Ioctl commands
The following @i{ioctl} commands are currently implemented. The type
of the third argument is shown in parentheses after each command:
@table @code
@item RR_DEVSEL (struct rr_devsel *)
The command copies device selection information to kernel space.
If the device has been opened more than once the command fails
with @code{EBUSY}; otherwise the pci driver is unregistered and
re-registered with a new @code{pci_id} item. If no device matches
the new selection @code{ENODEV} is returned after a timeout of
100ms.
@item RR_DEVGET (struct rr_devsel *)
The command returns to user space device information: vendor/device,
subvendor/subdevice and bus/devfn. If no device is currently
managed by the driver, @code{ENODEV} is returned.
@item RR_READ (struct rr_iocmd *)
@itemx RR_WRITE (struct rr_iocmd *)
The commands can read or write one register from an even BAR
area (BAR 0, 2, 4) of within the DMA buffer (BAR 12, 0xc).
The @code{address} field of the structure
specifies both the BAR and the offset (see @code{rawrabbit.h} or
the description of @i{llseek} above for the details). Access outside
the size of the area returns @code{ENOMEDIUM}.
The @code{datasize} field of @code{rr_iocmd}
can be 1, 2, 4 or 8 and is a byte count.
The other fields, @code{data8} through @code{data64} are used to
host the register value; these fields are collapsed together in an
unnamed union (see the @i{gcc} documentation about unnamed unions),
so the same code works with little-endian and big-endian systems.
@item RR_IRQWAIT (no third argument)
The command waits for an interrupt to happen on the device. If an
interrupt did already happen, @code{EAGAIN} is returned, otherwise
an interrupt is waited for and 0 is returned. After the interrupt
fired, the interrupt line is disabled by the kernel handler.
Please note that this may
be a serious problem if the line is shared with other peripherals,
like your hard drive or ethernet card.
@item RR_IRQENA (no third argument)
The command re-enables the interrupt. The user is assumed to have
acknowledged the interrupt in the board itself, or another interrupt
will immediately fire. If the interrupt did not happen, @code{EAGAIN}
is returned, otherwise the command returns the number of nanoseconds
that elapsed since the interrupt occurred. If more than one
second elapsed, the command returns 1000000000 (one billion), to
avoid overflowing the signed integer return value of @i{ioctl}.
@item RR_GETDMASIZE (no third argument)
The command simply returns the size, in bytes, of the DMA buffer,
Currently such size can only be changed at module load time and is
fixed for the lifetime of the module.
@item RR_GETPLIST (array of 1024 32-bit values)
The command returns the PFNs for the current DMA buffer. The initial
part of the page passed as third argument is filled with 32-bit
values. The array must be a complete 1024-entry array, even if
only part of it is used. Each value written represents a @i{page
frame number} that can be shifted by 12 bits to obtain the physical
address for the associated page. The @i{rawrabbit} module can only
work with 4kB pages, and a compile-time check is built into the code
to prevent compilation with a different page size; at least not
before a serious audit of the code.
@end table
@c ==========================================================================
@node User space demo programs, User space benchmarks, Ioctl commands, Raw PCI I/O
@section User space demo programs
The subdirectory @code{user/} of this package includes the user-space
sample tools. The helper for @i{rawrabbit} (@i{rr}) is called @i{rrcmd}.
@menu
* rrcmd::
@end menu
@c --------------------------------------------------------------------------
@node rrcmd, , User space demo programs, User space demo programs
@subsection rrcmd
The @i{rrcmd} program can do raw I/O and change the active binding of
the device.
Every command line can change the binding and issue a command. Since
binding is persistent, you can issue commands without specifying a new
binding. The initial binding is defined by module parameters, or by
default as a GN4124 device.
To specify a new binding, the syntax is
``@code{@i{vendor}:@i{device}/@i{subvendor}:@i{subdevice}@@@i{bus}:@i{devfn}}''
where the first pair is mandatory and the following ones are optional.
The following is an example session with @i{rrcmd}, from the
compilation directory, note that in this case I'm using the GN4124
device and an ethernet port without active driver.
@example
tornado% sudo insmod kernel/rawrabbit.ko
tornado% ./user/rrcmd info
/dev/rawrabbit: bound to 1a39:0004/1a39:0004@0001:0000
tornado% ./user/rrcmd 10b7:9055
tornado% ./user/rrcmd info
/dev/rawrabbit: bound to 10b7:9055/10b7:9055@0004:0000
tornado% ./user/rrcmd 1a39:0004 info
/dev/rawrabbit: bound to 1a39:0004/1a39:0004@0001:0000
tornado% ./user/rrcmd 10b7:9055@01:0
./user/rrcmd: /dev/rawrabbit: ioctl(DEVSEL): No such device
tornado% ./user/rrcmd info
/dev/rawrabbit: not bound
@end example
The ``no such device'' error above depends on the chosen @i{bus:devfn}
parameter. Please note that trying to bind to a device already driven
by a kernel driver returns @code{ENODEV} in the same way, as the probe
function of the PCI driver registered by @i{rawrabbit} will not be
called.
To read and write data with @i{rrcmd} you can use the @code{r} and @code{w}
commands. The syntax of the commands is as follows:
@example
r[<sz>] <bar>:<addr>
w[<sz>] <bar>:<addr> <val>
<sz> = 1, 2, 4, 8 (default = 4)
<bar> = 0, 2, 4
@end example
Actually, since an interactive user often reads and writes the same
register, the @code{r} and @code{w} commands are the same, and a read
or write is selected according to the number of arguments. You can think
of @code{r} as ``register'' and @code{w} as ``word'' if you prefer.
In this example two Gennum leds are turned off, and the value is read back.
Address 0xa08 in BAR 4 is the ``output drive enable'' register for the
GPIO signals from the GN4124 chip, and enabling the drive without any
other change from default settings is enough to turn the leds off.
@example
tornado% ./user/rrcmd r 4:a08
0x00000000
tornado% ./user/rrcmd r 4:a08 0x3000
tornado% ./user/rrcmd r 4:a08
0x00003000
@end example
Note, in the example above, that ``@code{r}'' is used for writing
as well as reading. If you forget the @code{r} or @code{w}
command name, however, the program will understand the argument
as a @i{vendor}:@i{device} pair, and will unbind the driver.
This can be construed as a design bug and you can blame me at will.
Reading data with a different-from-default size returns the right number
of hex digits, to make clear what data size that has been read:
@example
tornado% ./user/rrcmd r1 4:a08
0x00
tornado% ./user/rrcmd r2 4:a08
0x3000
tornado% ./user/rrcmd r4 4:a08
0x00003000
tornado% ./user/rrcmd r8 4:a08
0x0000000000003000
@end example
Interrupt management with @i{rrcmd} can be performed using two
commands: @code{irqwait} and @code{irqena}. The former is used to wait
for an interrupt to happen; the latter re-enables the interrupt in the
controller. You should probably acknowledge the interrupt in the device
between these two operations. The @code{irqwait} command returns
@code{EAGAIN} if the interrupt has already happened; the @code{irqena}
command returns @code{EAGAIN} if the interrupt has not happened
yet.
For example, this script waits for an interrupt in a BT878 frame
grabber and acknowledges it for 100 times:
@example
# select device and enable vsync interrupt (bit 1, value 0x2)
./user/rrcmd 109e:036e w 0:104 2
# now wait for irq, acknowledging bit 1 for vsync
for n in $(seq 1 100); do
./user/rrcmd irqwait
./user/rrcmd w 0:100 2
./user/rrcmd irqena
done
# finally, disable the interrupt in the device, ack and enable
./user/rrcmd w 0:104 0
./user/rrcmd w 0:100 2
./user/rrcmd irqena
@end example
The other commands are @i{getdmasize} and @i{getplist}, that work
as follows:
@example
tornado% ./user/rrcmd getdmasize
dmasize: 1048576 (0x100000 -- 1 MB)
tornado% ./user/rrcmd getplist | head
buf 0x00000000: pfn 0x00029a3c, addr 0x000029a3c000
buf 0x00001000: pfn 0x0002dbb1, addr 0x00002dbb1000
buf 0x00002000: pfn 0x00029a34, addr 0x000029a34000
buf 0x00003000: pfn 0x00029839, addr 0x000029839000
buf 0x00004000: pfn 0x00029838, addr 0x000029838000
buf 0x00005000: pfn 0x000298ed, addr 0x0000298ed000
buf 0x00006000: pfn 0x000298ec, addr 0x0000298ec000
buf 0x00007000: pfn 0x00029843, addr 0x000029843000
buf 0x00008000: pfn 0x00029842, addr 0x000029842000
buf 0x00009000: pfn 0x0002dbab, addr 0x00002dbab000
@end example
@c ==========================================================================
@node User space benchmarks, , User space demo programs, Raw PCI I/O
@section User space benchmarks
The package includes a few trivial programs used to benchmark performance
of the various I/O primitives.
@menu
* bench/ioctl::
* bench/irq878::
* Benchmarking read and write::
@end menu
@c --------------------------------------------------------------------------
@node bench/ioctl, bench/irq878, User space benchmarks, User space benchmarks
@subsection bench/ioctl
The program tests how many ioctl output operations can be performed
per second. It issues a number of register writes assuming the driver
is currently accessing the Gennum evaluation board.
The data written makes the 4 GPIO leds blink with different duty
cycles, so you should see them lit at different light levels.
On my system, the program reports more than 3 million operations per
second:
@example
tornado% ./bench/ioctl 1000000
1000000 ioctls in 303611 usecs
3293688 ioctls per second
tornado% ./bench/ioctl 10000000
10000000 ioctls in 3068384 usecs
3259044 ioctls per second
@end example
@c --------------------------------------------------------------------------
@node bench/irq878, Benchmarking read and write, bench/ioctl, User space benchmarks
@subsection bench/irq878
The program does the same kind of
operation as the script shown earlier: it handles BT878 interrupts in
user space, and prints the delays from actual interrupt to
end-of-acknowledge. While the script shown earlier reports times in
the order of 10ms, since several processes are executed between
the interrupt and the final @code{irqena}, this shows the system call
overhead which is just a few microseconds:
@example
tornado% ./bench/irq878 100
got 100 interrupts, average delay 6389ns
@end example
@c --------------------------------------------------------------------------
@node Benchmarking read and write, , bench/irq878, User space benchmarks
@subsection Benchmarking read and write
No specific program is provided to check access to the DMA buffer, as
@i{dd} is enough to verify read and write speed. A script like the
following will work:
@smallexample
IF="if=/dev/rawrabbit"
OF="of=/dev/rawrabbit"
# test dmabuf read
for BS in 1 2 4 8 16 32 64 128 256 512 1024 2048 4096; do
dd bs=$BS skip=$(expr $(printf %i 0xc0000000) / $BS) $IF of=/dev/null \
2>&1 | grep MB/s
done
# test dmabuf write
for BS in 1 2 4 8 16 32 64 128 256 512 1024 2048 4096; do
dd bs=$BS seek=$(expr $(printf %i 0xc0000000) / $BS) $OF if=/dev/null \
2>&1 | grep MB/s
done
@end smallexample
To benchmark access to I/O memory, the @i{rdwr} utility is offered.
It repeatedly accesses the GPIO register (bar 4, offset 0xa08 of the
GN4124 board) as a 32bit register and measures the time it takes:
@example
tornado% ./bench/rdwr 1000000
1000000 writes in 361487 usecs
2766351 writes per second
1000000 reads in 1041681 usecs
959986 reads per second
@end example
It's interesting to note that reads are slower than writes. Even more
interesting is that direct
writes are slower than writing through @i{ioctls} (compare with
@code{bench/ioctl}). The difference is probably due use of
@i{lseek} between one @i{read} or @i{write} and the next one, so for an
@i{ioctl}-based I/O operation you need one system call, but to
achieve the same using @i{read} or @i{write} you need two system
calls.
@c ##########################################################################
@node Firmware Loader, , Raw PCI I/O, Top
@chapter The Firmware Loader
The kernel driver supports loading a binary file from user space.
Such a loading is Gennum-specific, so currently the code performs
the operation only when the bound device has @code{1a39:0004} as
@i{vendor:device} pair.
@menu
* Loading at Probe Time::
* The Firmware File Name::
* The Firmware File Format::
* Working Without Udev::
@end menu
@c ==========================================================================
@node Loading at Probe Time, The Firmware File Name, Firmware Loader, Firmware Loader
@section Loading at Probe Time
Whenever a device is probed for, the driver requests a firmware file. FIXME
Such @i{probe} actions happen when the driver is matched to a device.
This happens at load time, if the PCI bus is already hosting a device
with the proper vendor end product numbers, but also when @i{rrcmd}
or other user-space tools ask the driver to bind to a different device.
In practice, you can load a new firmware binary simply by re-binding
the driver to the same device. For example this command re-loads the
firmware:
@example
rrcmd 1a39:0004
@end example
@c ==========================================================================
@node The Firmware File Name, The Firmware File Format, Loading at Probe Time, Firmware Loader
@section The Firmware File Name
The name of the binary file being requested is generated from the various
identifiers of the board: vendor and product, subvendor and subproduct,
bus number and devfn. For example, in my system the file being
requested is: @code{rrabbit-1a39:0004-1a39:0004@@0001:0000} .
Since no message is reported to system logs when the file is not found,
the module uses @i{printk} to report the generated name. The file
can be placed in @i{/lib/firmware} or other places, according to how
your @i{hotplug} or @i{udevd} is configured. The firmware-loader
mechanism of the kernel arranges for the file to be made available to
@i{rawrabbit}, whatever the file name.
The current version is still too verbose in its messages, but this is
work in progress so you'll forgive me. This is for example what I get
then loading the module with a correct firmware file:
@c FIXME
@smallexample
[ 3048.954185] rr_ask_firmware: called with preempt_cont == 0x00000001
[ 3048.960525] rr_ask_firmware: current = 3061: insmod
[ 3048.965486] rr_load_firmware: rrabbit-1a39:0004-1a39:0004@0001:0000
[ 3048.971834] rr_load_firmware: called with preempt_cont == 0x00000000
[ 3048.978342] rr_load_firmware: current = 13: events/2
[ 3048.983441] request firmware returned 0
[ 3049.048928] rr_loader_complete: called with preempt_cont == 0x00000000
[ 3049.055572] rr_loader_complete: current = 3066: firmware/rrabbi
[ 3049.061527] eda3a3c0: size 594412 (0x911ec), data f8b02000
[ 3049.067045] programming with bar4 @ fe7ff000, vaddr f8494000
[ 3050.574029] __rr_gennum_load: 220: done after 148603
@end smallexample
In the message above, you can see both the file name (always starts
with @i{rrabbit-1a39:0004}) and the context of execution: @i{current}
is the current process, and as you see a new kernel thread is created
to run the completion function of the loading mechanism (here
the @i{pid} is 3066 and the name is @i{firmware/rrabbi} -- only the
leading 15 bytes of the name are printed. The final @i{done after 148603}
reports that the @i{done} bit in Gennum registers became true after
writing that number of 32-bit words. Hopefully, this matches the file
size divided by 4.
This version of the package allows changing the file name for the
firmware at will (but it doesn't allow picking it from a different
directory from the ones looked-for by the @i{hotplug} or @i{udev}
daemons). The default firmware name is set forth in a module parameter,
@code{fwname}; the predefined value of the string is
``@code{rrabbit-%P-%p@@%b}''; such a string is expanded at run time,
using the following markers:
@itemize @bullet
@item @code{%P} expands to the PCI vendor and device ID
@item @code{%p} expands to the subsystem vendor and device ID
@item @code{%b} expands to the bus and @i{devfn} values
@item @code{%%} expands to a single percent
@end itemize
Each such marker is expanded as @code{%04x:%04x}, using the pairs
vendor:device or bus:devfn. Trailing spaces and newlines are allowed,
as the module removed them automatically; other @code{%} escape on the
other hand are currently forbidden, and result in an error.
The version string can be changed at load time as a module parameter,
or later by writing in @code{/sys/module/rawrabbit/fwname}. When changing
the content of the file, a new firmware-load action is triggered, so
you don't need to rebind the device (may I say I dislike this feature?
you may just @code{rrcmd 1a39:0004} or rebind in @i{sysfs}).
The driver tries to check the @i{fwname} string: if it's too long or
uses a wrong format, writing it is refused and the previous name
remains. I fear, though, that there may be some buglet about
allocation/deallocation, so you'd better not write wrong names, to be
safe.
This is an example session:
@smallexample
tornado.root# insmod kernel/rawrabbit.ko fwname="fw-%P"
[22194.161358] rr_load_firmware: fw-1a39:0004
tornado.root# cat /sys/module/rawrabbit/parameters/fwname
fw-%P
tornado.root# echo -n "rabbit-%P-%p-bus-%b" \
> /sys/module/rawrabbit/parameters/f
[22898.924501] rr_load_firmware: rabbit-1a39:0004-1a39:0004-bus-0001:0000
@end smallexample
@c ==========================================================================
@node The Firmware File Format, Working Without Udev, The Firmware File Name, Firmware Loader
@section The Firmware File Format
Testing has been performed with the binary firmware provided by Gennum
as an example. The file is called @file{lambo.gfw} and is in an
yet-unknown format. Binaries generated with the Xilinx tools are in
a different format and don't work (although they load successfully).
The Gennum binary must be preprocessed before it can be programmed in
the hardware: the header must be removed and the bits in each byte
must be reversed. This work is expected to be performed in user
space, and the file in @file{/lib/firmware} is expected to be the
raw binary. For bit-reversal, you can use @file{user/flip} which is
part of this package.
The Gennum header is simple: after 4 bytes of magic number there is
a teo-byte little-endian count of header size. In the @file{lambo.gfw}
this is 34 bytes (0x22). The following script is an example of how
to process this file, assuming @code{GFW} is the input file name and
@code{RRABBIT} is the top directory of this package:
@example
# print as decimal-2bytes, no address, 2bytes per line,
# then get line 3, and delete any space in the string. We get "34".
hsize=$(od -t d2 -An -w2 $GFW | sed -n 3p | tr -d ' ')
# skip 1 block, of size 34, and bit-reverse the resulting file.
dd bs=$hsize skip=1 if=$GFW | $RRABBIT/user/flip > $OUTNAME
@end example
With the original @file{lambo.gfw} (size 594446, md5sum:
7d9d289b8014ff2be0550816496463f9) you'll get a file with md5sum
2aec561e9aa289944d87b2f3ec988b66.
You can check that this works by writing and reading at offset 0x4000 of
BAR0, where FPGA RAM is exposed:
@example
tornado% sudo ./user/rrcmd w4 0:4000 c0ffee
tornado% sudo ./user/rrcmd r4 0:4000
0x00c0ffee
@end example
@c ==========================================================================
@node Working Without Udev, , The Firmware File Format, Firmware Loader
@section Working Without Udev
If needed, you can work withoPlease note that with some kernel versions, you won't be able to
remove the module while it is bound to a device. You must first force
it to unbind from the device, either by running @i{rrcmd} with
an unexistent @i{vendor:device} pair, or by using the @i{unbind}
method in @i{sysfs}:
@example
tornado.root# rmmod rawrabbit
ERROR: Module rawrabbit is in use
tornado.root# DEV=$(basename /sys/bus/pci/drivers/rawrabbit/*:*)
tornado.root# echo $DEV > /sys/bus/pci/drivers/rawrabbit/unbind
tornado.root# rmmod rawrabbit
tornado.root#
@end example
ut @i{udev} or @i{hotplug}, which is good
if your system is meant to be a small embedded-like, fast-boot device.
Without @i{udev}, the binary being called by the userspace-helper of
the kernel is the path appearing in @file{/proc/sys/kernel/hotplug}.
So, for example, you can run this script as @file{hotplug},
to show what's happening at firmware-load time:
@example
#!/bin/sh
date > /tmp/hp.$$
echo Called as: $0 "$@" >> /tmp/hp.$$
echo Environment: >> /tmp/hp.$$
env >> /tmp/hp.$$
@end example
To activate it, just make it executable as
@i{/usr/local/bin/hotplug-logger} and run this command (without
@i{udev} or other interfering stuff running):
@example
echo /usr/local/bin/hotplug-logger > /sys/kernel/uevent_helper
@end example
The following file appeared on my system as @file{/tmp/hp.3190} when
firmware loading happened (3190 is the @i{pid} of the script):
@smallexample
Wed Jan 12 09:26:16 CET 2011
Called as: /usr/local/bin/hotplug-logger firmware
Environment:
SUBSYSTEM=firmware
ASYNC=1
DEVPATH=/devices/pci0000:00/0000:00:01.0/0000:01:00.0/firmware/0000:01:00.0
FIRMWARE=rrabbit-1a39:0004-1a39:0004@0001:0000
PATH=/sbin:/bin:/usr/sbin:/usr/bin
ACTION=add
PWD=/
TIMEOUT=60
SHLVL=1
HOME=/
SEQNUM=1351
_=/usr/bin/env
@end smallexample
Thus, with those environment variables, you can use this script as
user-space helper for firmware loading. Again, just place it as
executable file in some place and write its pathname to
@file{/sys/kernel/uevent_helper}.
@smallexample
#!/bin/sh
export HOTPLUG_FW_DIR=/lib/firmware/
# FIRMWARE and DEVPATH are provided in the event
if [ -z "$FIRMWARE" ]; then exit 0; fi
echo "$0: loading $FIRMWARE to $DEVPATH" > /dev/console
echo 1 > /sys/$DEVPATH/loading
cat $HOTPLUG_FW_DIR/$FIRMWARE > /sys/$DEVPATH/data
echo 0 > /sys/$DEVPATH/loading
@end smallexample
Note that the userspace helper is also called at device creation and
removal, so you'll get several invocations of either script. This
leads to several log files in the former case. Also, please note that
is no @i{udev} is running, you'll need to @i{mknod} manually, with 10
as major number and 42 as minor number.
As a final note, please remember that when running the simple
@i{hotplug-logger} shown above, no firmware is actually loaded, so the
module remains busy until the timeout (1 minute) expires, and you
won't be able to unload it. You may want to change such timeout with
a command like:
@example
echo 3 > /sys/class/firmware/timeout
@end example
@c ##########################################################################
@iftex
@contents
@end iftex
@bye
#! /usr/bin/sed -f
# allow "%" as a comment char, but only at the beginning of the line
s/^%/@c /
#s/[^\\]%.*$//
s/^\\%/%/
# turn accents into tex encoding (for Italian language)
s/a`//g
s/e`//g
s/E`//g
s/i`//g
s/o`//g
s/u`//g
s/erch/erch/g
s/oich/oich/g
s/n/n/g
s//@`a/g
s//@`e/g
s//@'e/g
s//@`i/g
s//@`o/g
s//@`E/g
s//@`u/g
#preserve blanks, braces and @ in @example and @smallexample blocks
/@example/,/@end example/ s/{/@{/g
/@example/,/@end example/ s/}/@}/g
/@example/,/@end example/ s/@/@@/g
s/^@@example/@example/
s/^@@end/@end/
/@example/,/@end example/ p
/@example/,/@end example/ d
/@smallexample/,/@end smallexample/ s/{/@{/g
/@smallexample/,/@end smallexample/ s/}/@}/g
/@smallexample/,/@end smallexample/ s/@/@@/g
s/^@@smallexample/@smallexample/
s/^@@end/@end/
/@smallexample/,/@end smallexample/ p
/@smallexample/,/@end smallexample/ d
# remove leading blanks
s/^[ ]*//
# fix include to include texi not in
s/^\(.include.*\).in$/\1.texi/
.*cmd
.tmp_versions
Module.symvers
Module.markers
modules.order
*.mod.c
*.ko
\ No newline at end of file
LINUX ?= /lib/modules/$(shell uname -r)/build
obj-m = rawrabbit.o
rawrabbit-objs = rawrabbit-core.o loader.o loader-ll.o
all modules:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) modules
clean:
rm -rf *.o *~ .*.cmd *.ko *.mod.c .tmp_versions Module.symvers \
Module.markers modules.order
#include <linux/version.h>
/* Simple compatibility macros */
#ifdef CONFIG_X86
/* Readq for IA32 introduced in 2.6.28-rc7 */
#ifndef readq
static inline __u64 readq(const volatile void __iomem *addr)
{
const volatile u32 __iomem *p = addr;
u32 low, high;
low = readl(p);
high = readl(p + 1);
return low + ((u64)high << 32);
}
static inline void writeq(__u64 val, volatile void __iomem *addr)
{
writel(val, addr);
writel(val >> 32, addr+4);
}
#endif
#endif /* X86 */
/*
* request_firmware_nowait adds a gfp_t argument at some point:
* patch 9ebfbd45f9d4ee9cd72529cf99e5f300eb398e67 == v2.6.32-5357-g9ebfbd4
*/
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)
#define __RR_GFP_FOR_RFNW(x) x,
#else
#define __RR_GFP_FOR_RFNW(x) /* nothing */
#endif
/* Hack... something I sometimes need */
static inline void dumpstruct(char *name, void *ptr, int size)
{
int i;
unsigned char *p = ptr;
printk("dump %s at %p (size 0x%x)\n", name, ptr, size);
for (i = 0; i < size; ) {
printk("%02x", p[i]);
i++;
printk(i & 3 ? " " : i & 0xf ? " " : "\n");
}
if (i & 0xf)
printk("\n");
}
/*
* This is the low-level engine of firmware loading. It is meant
* to be compiled both as kernel code and user code, using the associated
* header to differentiate
*/
#define __LOADER_LL_C__ /* Callers won't define this symbol */
#include "rawrabbit.h"
#include "loader-ll.h"
static inline uint8_t reverse_bits8(uint8_t x)
{
x = ((x >> 1) & 0x55) | ((x & 0x55) << 1);
x = ((x >> 2) & 0x33) | ((x & 0x33) << 2);
x = ((x >> 4) & 0x0f) | ((x & 0x0f) << 4);
return x;
}
static uint32_t unaligned_bitswap_le32(const uint32_t *ptr32)
{
static uint32_t tmp32;
static uint8_t *tmp8 = (uint8_t *) &tmp32;
static uint8_t *ptr8;
ptr8 = (uint8_t *) ptr32;
*(tmp8 + 0) = reverse_bits8(*(ptr8 + 0));
*(tmp8 + 1) = reverse_bits8(*(ptr8 + 1));
*(tmp8 + 2) = reverse_bits8(*(ptr8 + 2));
*(tmp8 + 3) = reverse_bits8(*(ptr8 + 3));
return tmp32;
}
/*
* Unfortunately, most of the following is from fcl_gn4124.cpp, for which
* the license terms are at best ambiguous.
*/
int loader_low_level(int fd, void __iomem *bar4, const void *data, int size8)
{
int size32 = (size8 + 3) >> 2;
const uint32_t *data32 = data;
int ctrl = 0, i, done = 0, wrote = 0;
lll_write(fd, bar4, 0x00, FCL_CLK_DIV);
lll_write(fd, bar4, 0x40, FCL_CTRL); /* Reset */
i = lll_read(fd, bar4, FCL_CTRL);
if (i != 0x40) {
printk(KERN_ERR "%s: %i: error\n", __func__, __LINE__);
return -EIO;
}
lll_write(fd, bar4, 0x00, FCL_CTRL);
lll_write(fd, bar4, 0x00, FCL_IRQ); /* clear pending irq */
switch(size8 & 3) {
case 3: ctrl = 0x116; break;
case 2: ctrl = 0x126; break;
case 1: ctrl = 0x136; break;
case 0: ctrl = 0x106; break;
}
lll_write(fd, bar4, ctrl, FCL_CTRL);
lll_write(fd, bar4, 0x00, FCL_CLK_DIV); /* again? maybe 1 or 2? */
lll_write(fd, bar4, 0x00, FCL_TIMER_CTRL); /* "disable FCL timr fun" */
lll_write(fd, bar4, 0x10, FCL_TIMER_0); /* "pulse width" */
lll_write(fd, bar4, 0x00, FCL_TIMER_1);
/*
* Set delay before data and clock is applied by FCL
* after SPRI_STATUS is detected being assert.
*/
lll_write(fd, bar4, 0x08, FCL_TIMER2_0); /* "delay before data/clk" */
lll_write(fd, bar4, 0x00, FCL_TIMER2_1);
lll_write(fd, bar4, 0x17, FCL_EN); /* "output enable" */
ctrl |= 0x01; /* "start FSM configuration" */
lll_write(fd, bar4, ctrl, FCL_CTRL);
while(size32 > 0)
{
/* Check to see if FPGA configuation has error */
i = lll_read(fd, bar4, FCL_IRQ);
if ( (i & 8) && wrote) {
done = 1;
printk("%s: %i: done after %i\n", __func__, __LINE__,
wrote);
} else if ( (i & 0x4) && !done) {
printk("%s: %i: error after %i\n", __func__, __LINE__,
wrote);
return -EIO;
}
/* Wait until at least 1/2 of the fifo is empty */
while (lll_read(fd, bar4, FCL_IRQ) & (1<<5))
;
/* Write a few dwords into FIFO at a time. */
for (i = 0; size32 && i < 32; i++) {
lll_write(fd, bar4, unaligned_bitswap_le32(data32),
FCL_FIFO);
data32++; size32--; wrote++;
}
}
lll_write(fd, bar4, 0x186, FCL_CTRL); /* "last data written" */
/* Checking for the "interrupt" condition is left to the caller */
return wrote;
}
/*
* This header differentiates between kernel-mode and user-mode compilation,
* as loader-ll.c is meant to be used in both contexts.
*/
#ifndef __iomem
#define __iomem /* nothing, for user space */
#endif
extern int loader_low_level(
int fd, /* This is ignored in kernel space */
void __iomem *bar4, /* This is ignored in user space */
const void *data,
int size8);
/* The following part implements a different access rule for user and kernel */
#ifdef __LOADER_LL_C__
#ifdef __KERNEL__
#include <asm/io.h>
//#include <linux/kernel.h> /* for printk */
static inline void lll_write(int fd, void __iomem *bar4, u32 val, int reg)
{
writel(val, bar4 + reg);
}
static inline u32 lll_read(int fd, void __iomem *bar4, int reg)
{
return readl(bar4 + reg);
}
#else /* ! __KERNEL__ */
#include <stdio.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <errno.h>
static inline void lll_write(int fd, void __iomem *bar4, uint32_t val, int reg)
{
struct rr_iocmd iocmd = {
.datasize = 4,
.address = reg | __RR_SET_BAR(4),
};
iocmd.data32 = val;
if (ioctl(fd, RR_WRITE, &iocmd) < 0) perror("ioctl");
return;
}
static inline uint32_t lll_read(int fd, void __iomem *bar4, int reg)
{
struct rr_iocmd iocmd = {
.datasize = 4,
.address = reg | __RR_SET_BAR(4),
};
if (ioctl(fd, RR_READ, &iocmd) < 0) perror("ioctl");
return iocmd.data32;
}
#define KERN_ERR /* nothing */
#define printk(format, ...) fprintf (stderr, format, ## __VA_ARGS__)
#endif
#endif /* __LOADER_LL_C__ */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/sched.h>
#include <linux/hardirq.h>
#include <linux/workqueue.h>
#include <linux/firmware.h>
#include <linux/jiffies.h>
#include <linux/ctype.h>
#include <linux/mutex.h>
#include "rawrabbit.h"
#include "compat.h"
#include "loader-ll.h"
#ifndef CONFIG_FW_LOADER
#warning "CONFIG_FW_LOADER is need in kernel configuration."
#warning "Compiling anyways, but won't be able to load firmware"
#endif
/* a few prototypes, to avoid many diff lines from previous versions */
static int __rr_gennum_load(struct rr_dev *dev, const void *data, int size);
static int rr_expand_name(struct rr_dev *dev, char *outname);
/* I need to be notified when I get written, so "charp" won't work */
static int param_set_par_fwname(const char *val, const struct kernel_param *kp)
{
const char *prev = *(const char **)kp->arg;
int ret = param_set_charp(val, kp);
extern struct rr_dev rr_dev; /* global: no good */
struct rr_dev *dev = &rr_dev;
static char fwname[RR_MAX_FWNAME_SIZE];
if (ret)
return ret;
ret = rr_expand_name(dev, fwname);
if (ret) {
/*
* bad it went: refuse this string and restore prev one
* Looks like there's a problem here: the previous string
* was not freed, or it's me who didn't see it?
*/
kfree(*(const char **)kp->arg);
*(const char **)kp->arg = prev;
return ret;
}
/*
* Use the new firmware immediately at this time. We are in user
* context, so take the mutex to avoid contention with ioctl.
* Note that if we are not bound (like at insmod time) we don't
* want to do the actual loading
*/
if (dev->pdev) {
mutex_lock(&dev->mutex);
rr_ask_firmware(dev);
mutex_unlock(&dev->mutex);
}
return 0;
}
static int param_get_par_fwname(char *buffer, const struct kernel_param *kp)
{
int ret = param_get_charp(buffer, kp);
return ret;
}
static struct kernel_param_ops param_ops_par_fwname = {
.set = param_set_par_fwname,
.get = param_get_par_fwname,
};
#define param_check_par_fwname(name, p) __param_check(name, p, char *)
static char *rr_fwname = RR_DEFAULT_FWNAME;
module_param_named(fwname, rr_fwname, par_fwname, 0644);
static int rr_expand_name(struct rr_dev *dev, char *outname)
{
struct rr_devsel *devsel = dev->devsel;
char *si, *so = outname;
for (si = rr_fwname; *si ; si++) {
if (so - outname >= RR_MAX_FWNAME_SIZE)
return -ENOSPC;
if (*si != '%') {
*so++ = *si;
continue;
}
si++;
if (so - outname + 9 >= RR_MAX_FWNAME_SIZE)
return -ENOSPC;
switch(*si) {
case 'P': /* PCI vendor:device */;
so += sprintf(so, "%04x:%04x",
devsel->vendor, devsel->device);
break;
case 'p': /* PCI subvendor:subdevice */;
so += sprintf(so, "%04x:%04x",
devsel->subvendor, devsel->subdevice);
break;
case 'b': /* BUS id */
so += sprintf(so, "%04x:%04x",
devsel->bus, devsel->devfn);
break;
case '%':
*so++ = '%';
default:
return -EINVAL;
}
}
/* terminate and remove trailing spaces (includes newlines) */
*so = '\0';
while (isspace(*--so))
*so = '\0';
return 0;
}
/* This stupid function is used to report what is the calling environment */
static void __rr_report_env(const char *func)
{
/* Choose 0 or 1 in the if below, to turn on/off without warnings */
if (1) {
printk("%s: called with preempt_cont == 0x%08x\n", func,
preempt_count());
printk("%s: current = %i: %s\n", func,
current->pid, current->comm);
}
}
/*
* The callback when loading is over, either with or without data.
* This a context that can sleep, so we can program it taking as
* much time as we want.
*/
static void rr_loader_complete(const struct firmware *fw, void *context)
{
struct rr_dev *dev = context;
int ret;
dev->fw = fw;
__rr_report_env(__func__); /* Here, preempt count is 0: good! */
if (fw) {
pr_info("%s: %p: size %i (0x%x), data %p\n", __func__, fw,
fw ? fw->size : 0, fw ? fw->size : 0,
fw ? fw->data : 0);
} else {
pr_warning("%s: no firmware\n", __func__);
return;
}
/*
* At this point, use fw->data as a linear array of fw->size bytes.
* If we are handling the gennum device, load this firmware binary;
* otherwise, just complain to the user -- I'd better use a much
* fancier method of defining one programmer per vendor/device...
*/
if (dev->devsel->vendor == RR_DEFAULT_VENDOR
&& dev->devsel->device == RR_DEFAULT_DEVICE) {
ret = __rr_gennum_load(dev, fw->data, fw->size);
} else {
pr_err("%s: not loading firmware: this is not a GN4124\n",
__func__);
ret = -ENODEV;
}
/* At this point, we can releae the firmware we got */
release_firmware(dev->fw);
if (ret)
pr_err("%s: loading returned error %i\n", __func__, ret);
dev->fw = NULL;
}
/*
* We want to run the actual loading from a work queue, to have a known
* loading environment, especially one that can sleep. The function
* pointer is already in the work structure, set at compile time from
* rawrabbit-core.c .
*/
void rr_load_firmware(struct work_struct *work)
{
struct rr_dev *dev = container_of(work, struct rr_dev, work);
struct pci_dev *pdev = dev->pdev;
static char fwname[RR_MAX_FWNAME_SIZE];
int err;
if (rr_expand_name(dev, fwname)) {
dev_err(&pdev->dev, "Wrong fwname: \"%s\"\n", rr_fwname);
return;
}
if (1)
printk("%s: %s\n", __func__, fwname);
__rr_report_env(__func__);
printk(KERN_INFO "firmware name: %s\n", fwname);
err = request_firmware_nowait(THIS_MODULE, 1, fwname, &pdev->dev,
__RR_GFP_FOR_RFNW(GFP_KERNEL)
dev, rr_loader_complete);
printk("request firmware returned %i\n", err);
}
/* This function is called by the PCI probe function. */
void rr_ask_firmware(struct rr_dev *dev)
{
/* Here, preempt_cont is 1 for insmode/rrcmd. What for hotplug? */
__rr_report_env(__func__);
if (dev->fw) {
pr_err("%s: firmware asked, but firmware already present\n",
__func__);
return;
}
/* Activate the work queue, so we are sure we are in user context */
schedule_work(&dev->work);
}
/*
* Finally, this is the most important thing, the loader for a Gennum
* 4124 evaluation board. We know we are mounting a Xilinx Spartan.
*
* This function doesn't actually do the actual work: another level is
* there to allow the same code to be run both in kernel and user space.
* Thus, actual access to registers has been split to loader_low_level(),
* in loader-ll.c (aided by loader-ll.h).
*/
static int __rr_gennum_load(struct rr_dev *dev, const void *data, int size8)
{
int i, done = 0, wrote = 0;
unsigned long j;
void __iomem *bar4 = dev->remap[2]; /* remap == bar0, bar2, bar4 */
printk("programming with bar4 @ %x, vaddr %p\n",
dev->area[2]->start, bar4);
/* Ok, now call register access, which lived elsewhere */
wrote = loader_low_level( 0 /* unused fd */, bar4, data, size8);
if (wrote < 0)
return wrote;
j = jiffies + 2 * HZ;
/* Wait for DONE interrupt */
while(!done) {
i = readl(bar4 + FCL_IRQ);
if (i & 0x8) {
printk("%s: %i: done after %i\n", __func__, __LINE__,
wrote);
done = 1;
} else if( (i & 0x4) && !done) {
printk("%s: %i: error after %i\n", __func__, __LINE__,
wrote);
return -ETIMEDOUT;
}
if (time_after(jiffies, j)) {
printk("%s: %i: tout after %i\n", __func__, __LINE__,
wrote);
return -ETIMEDOUT;
}
}
return 0;
}
/*
* Raw I/O interface for PCI or PCI express interfaces
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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/moduleparam.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/ioctl.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>
#include "rawrabbit.h"
#include "compat.h"
static int rr_vendor = RR_DEFAULT_VENDOR;
static int rr_device = RR_DEFAULT_DEVICE;
module_param_named(vendor, rr_vendor, int, 0);
module_param_named(device, rr_device, int, 0);
static int rr_bufsize = RR_DEFAULT_BUFSIZE;
module_param_named(bufsize, rr_bufsize, int, 0);
struct rr_dev rr_dev; /* defined later */
/* Interrupt handler: just disable the interrupt in the controller */
irqreturn_t rr_interrupt(int irq, void *devid)
{
struct rr_dev *dev = devid;
int status;
status = readl(dev->remap[2] + GPIO_INT_STATUS);
if (status == 0)
return IRQ_NONE;
getnstimeofday(&dev->irqtime);
dev->irqcount++;
dev->flags |= RR_FLAG_IRQDISABLE;
/* disable_irq_nosync(irq); */
wake_up_interruptible(&dev->q);
return IRQ_HANDLED;
}
/*
* We have a PCI driver, used to access the BAR areas.
* One device id only is supported.
*/
static struct pci_device_id rr_idtable[2]; /* last must be zero */
static void rr_fill_table(struct rr_dev *dev)
{
if (dev->devsel->subvendor == RR_DEVSEL_UNUSED) {
dev->id_table->subvendor = PCI_ANY_ID;
dev->id_table->subdevice = PCI_ANY_ID;
} else {
dev->id_table->subvendor = dev->devsel->subvendor;
dev->id_table->subdevice = dev->devsel->subdevice;
}
dev->id_table->vendor = dev->devsel->vendor;
dev->id_table->device = dev->devsel->device;
}
static int rr_fill_table_and_probe(struct rr_dev *dev)
{
int ret;
/* This needs no locks, as it's called in semaphorized process ctxt */
if (dev->flags & RR_FLAG_REGISTERED) {
pci_unregister_driver(dev->pci_driver);
dev->flags &= ~ RR_FLAG_REGISTERED;
}
rr_fill_table(dev);
/* Use the completion mechanism to be notified of probes */
init_completion(&dev->complete);
ret = pci_register_driver(dev->pci_driver);
if (ret == 0)
dev->flags |= RR_FLAG_REGISTERED;
if (ret < 0) {
printk(KERN_ERR "%s: Can't register pci driver\n",
KBUILD_MODNAME);
return ret;
}
/* This ret is 0 (timeout) or positive */
ret = wait_for_completion_timeout(&dev->complete, RR_PROBE_TIMEOUT);
if (!ret) {
printk("%s: Warning: no device found\n", __func__);
}
return ret;
}
/* The probe and remove function can't get locks, as it's already locked */
static int rr_pciprobe (struct pci_dev *pdev, const struct pci_device_id *id)
{
struct rr_dev *dev = &rr_dev;
int i;
/* Only manage one device, refuse further probes */
if (dev->pdev)
return -EBUSY;
/* vendor/device and subvendor/subdevice have already been matched */
if (dev->devsel->bus != RR_DEVSEL_UNUSED) {
if (dev->devsel->bus != pdev->bus->number)
return -ENODEV;
if (dev->devsel->devfn != pdev->devfn)
return -ENODEV;
}
/* Record the informaztion in the local structure anyways */
dev->devsel->subvendor = pdev->subsystem_vendor;
dev->devsel->subdevice = pdev->subsystem_device;
dev->devsel->bus = pdev->bus->number;
dev->devsel->devfn = pdev->devfn;
i = pci_enable_device(pdev);
if (i < 0)
return i;
dev->pdev = pdev;
/* FIXME: how to know if irq is valid? */
if (pdev->irq > 0) {
i = request_irq(pdev->irq, rr_interrupt, IRQF_SHARED,
"rawrabbit", dev);
if (i < 0) {
printk("%s: can't request irq %i, error %i\n", __func__,
pdev->irq, i);
} else {
dev->flags |= RR_FLAG_IRQREQUEST;
}
}
complete(&dev->complete);
if (0) { /* Print some information about the bars */
int i;
struct resource *r;
for (i = 0; i < 6; i++) {
r = pdev->resource + i;
printk("%s: resource %i: %llx-%llx (%s) - %08lx\n",
__func__, i,
(long long)r->start,
(long long)r->end,
r->name, r->flags);
}
}
/* Record the three bars and possibly remap them */
for (i = 0; i < 3; i++) {
struct resource *r = pdev->resource + (2 * i);
if (!r->start)
continue;
dev->area[i] = r;
if (r->flags & IORESOURCE_MEM)
dev->remap[i] = ioremap(r->start,
r->end + 1 - r->start);
}
/* Finally, ask for a copy of the firmware for this device */
rr_ask_firmware(dev);
return 0;
}
/* This function is called when the pcidrv is removed, with lock held */
static void rr_pciremove(struct pci_dev *pdev)
{
struct rr_dev *dev = &rr_dev;
int i;
if (dev->flags & RR_FLAG_IRQREQUEST) {
free_irq(pdev->irq, dev);
dev->flags &= ~RR_FLAG_IRQREQUEST;
/* Also, reenable it, just in case we are shared.*/
if (dev->flags & RR_FLAG_IRQDISABLE) {
dev->flags &= ~RR_FLAG_IRQDISABLE;
/* enable_irq(dev->pdev->irq); */
}
}
for (i = 0; i < 3; i++) {
iounmap(dev->remap[i]); /* safe for NULL ptrs */
dev->remap[i] = NULL;
dev->area[i] = NULL;
}
release_firmware(dev->fw);
dev->fw = NULL;
dev->pdev = NULL;
}
static struct pci_driver rr_pcidrv = {
.name = "rawrabbit",
.id_table = rr_idtable,
.probe = rr_pciprobe,
.remove = rr_pciremove,
};
/* There is only one device by now; I didn't find how to associate to pcidev */
static struct rr_devsel rr_devsel;
/* The device can't be static. I need it in the module parameter */
struct rr_dev rr_dev = {
.pci_driver = &rr_pcidrv,
.id_table = rr_idtable,
.q = __WAIT_QUEUE_HEAD_INITIALIZER(rr_dev.q),
.mutex = __MUTEX_INITIALIZER(rr_dev.mutex),
.devsel = &rr_devsel,
.work = __WORK_INITIALIZER(rr_dev.work, rr_load_firmware),
};
/*
* These functions are (inlined) helpers for ioctl
*/
static int rr_do_read_mem(struct rr_dev *dev, struct rr_iocmd *iocmd)
{
int bar = __RR_GET_BAR(iocmd->address) / 2;
int off = __RR_GET_OFF(iocmd->address);
struct resource *r = dev->area[bar];
if (off >= r->end - r->start + 1)
return -ENOMEDIUM;
switch(iocmd->datasize) {
case 1:
iocmd->data8 = readb(dev->remap[bar] + off);
break;
case 2:
if (off & 1)
return -EIO;
iocmd->data16 = readw(dev->remap[bar] + off);
break;
case 4:
if (off & 3)
return -EIO;
iocmd->data32 = readl(dev->remap[bar] + off);
break;
case 8:
if (off & 7)
return -EIO;
iocmd->data64 = readq(dev->remap[bar] + off);
break;
default:
return -EINVAL;
}
return 0;
}
static int rr_do_write_mem(struct rr_dev *dev, struct rr_iocmd *iocmd)
{
int bar = __RR_GET_BAR(iocmd->address) / 2;
int off = __RR_GET_OFF(iocmd->address);
struct resource *r = dev->area[bar];
if (off >= r->end - r->start + 1)
return -ENOMEDIUM;
switch(iocmd->datasize) {
case 1:
writeb(iocmd->data8, dev->remap[bar] + off);
break;
case 2:
if (off & 1)
return -EIO;
writew(iocmd->data16, dev->remap[bar] + off);
break;
case 4:
if (off & 3)
return -EIO;
writel(iocmd->data32, dev->remap[bar] + off);
break;
case 8:
if (off & 7)
return -EIO;
writeq(iocmd->data64, dev->remap[bar] + off);
break;
default:
return -EINVAL;
}
return 0;
}
static int rr_do_read_io(struct rr_dev *dev, struct rr_iocmd *iocmd)
{
int bar = __RR_GET_BAR(iocmd->address) / 2;
int off = __RR_GET_OFF(iocmd->address);
struct resource *r = dev->area[bar];
if (off >= r->end - r->start + 1)
return -ENOMEDIUM;
switch(iocmd->datasize) {
case 1:
iocmd->data8 = inb(r->start + off);
break;
case 2:
if (off & 1)
return -EIO;
iocmd->data16 = inw(r->start + off);
break;
case 4:
if (off & 3)
return -EIO;
iocmd->data32 = inl(r->start + off);
break;
case 8:
if (off & 7)
return -EIO;
/* assume little-endian bus */
iocmd->data64 = inl(r->start + off);
iocmd->data64 |= (__u64)(inl(r->start + off + 4)) << 32;
break;
default:
return -EINVAL;
}
return 0;
}
static int rr_do_write_io(struct rr_dev *dev, struct rr_iocmd *iocmd)
{
int bar = __RR_GET_BAR(iocmd->address) / 2 ;
int off = __RR_GET_OFF(iocmd->address);
struct resource *r = dev->area[bar];
if (off >= r->end - r->start + 1)
return -ENOMEDIUM;
switch(iocmd->datasize) {
case 1:
outb(iocmd->data8, r->start + off);
break;
case 2:
if (off & 1)
return -EIO;
outw(iocmd->data16, r->start + off);
break;
case 4:
if (off & 3)
return -EIO;
outl(iocmd->data32, r->start + off);
break;
case 8:
if (off & 7)
return -EIO;
/* assume little-endian bus */
outl(iocmd->data64, r->start + off);
outl(iocmd->data64 >> 32, r->start + off + 4);
break;
default:
return -EINVAL;
}
return 0;
}
/* This helper is called for dmabuf operations */
static int rr_do_iocmd_dmabuf(struct rr_dev *dev, unsigned int cmd,
struct rr_iocmd *iocmd)
{
int off = __RR_GET_OFF(iocmd->address);
if (off >= rr_bufsize)
return -ENOMEDIUM;
switch(iocmd->datasize) {
case 1:
if (cmd == RR_WRITE)
*(u8 *)(dev->dmabuf + off) = iocmd->data8;
else
iocmd->data8 = *(u8 *)(dev->dmabuf + off);
break;
case 2:
if (off & 1)
return -EIO;
if (cmd == RR_WRITE)
*(u16 *)(dev->dmabuf + off) = iocmd->data16;
else
iocmd->data16 = *(u16 *)(dev->dmabuf + off);
break;
case 4:
if (off & 3)
return -EIO;
if (cmd == RR_WRITE)
*(u32 *)(dev->dmabuf + off) = iocmd->data32;
else
iocmd->data32 = *(u32 *)(dev->dmabuf + off);
break;
case 8:
if (off & 7)
return -EIO;
if (cmd == RR_WRITE)
*(u64 *)(dev->dmabuf + off) = iocmd->data64;
else
iocmd->data64 = *(u64 *)(dev->dmabuf + off);
break;
default:
return -EINVAL;
}
return 0;
}
static int rr_do_iocmd(struct rr_dev *dev, unsigned int cmd,
struct rr_iocmd *iocmd)
{
int bar;
unsigned off;
struct resource *r;
bar = __RR_GET_BAR(iocmd->address);
off = __RR_GET_OFF(iocmd->address);
if (!rr_is_valid_bar(iocmd->address))
return -EINVAL;
if (rr_is_dmabuf_bar(iocmd->address))
return rr_do_iocmd_dmabuf(dev, cmd, iocmd);
bar /= 2; /* use 0,1,2 as index */
r = dev->area[bar];
if (!r)
return -ENODEV;
if (likely(r->flags & IORESOURCE_MEM)) {
if (cmd == RR_READ)
return rr_do_read_mem(dev, iocmd);
else
return rr_do_write_mem(dev, iocmd);
}
if (likely(r->flags & IORESOURCE_IO)) {
if (cmd == RR_READ)
return rr_do_read_io(dev, iocmd);
else
return rr_do_write_io(dev, iocmd);
}
return -EIO;
}
/*
* The ioctl method is the one used for strange stuff (see docs)
*/
static long rr_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
struct rr_dev *dev = f->private_data;
int size = _IOC_SIZE(cmd); /* the size bitfield in cmd */
int ret = 0;
unsigned long count;
struct timespec tv, tvirq;
void *addr;
u32 __user *uptr = (u32 __user *)arg;
/* local copies: use a union to save stack space */
union {
struct rr_iocmd iocmd;
struct rr_devsel devsel;
} karg;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOIOCTLCMD before verify_area()
*/
if (_IOC_TYPE(cmd) != __RR_IOC_MAGIC)
return -ENOIOCTLCMD;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Dir' is user-oriented, while
* verify_area is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ) {
if (!access_ok(VERIFY_WRITE, (void *)arg, size))
return -EFAULT;
}
else if (_IOC_DIR(cmd) & _IOC_WRITE) {
if (!access_ok(VERIFY_READ, (void *)arg, size))
return -EFAULT;
}
/* first, retrieve data from userspace */
if ((_IOC_DIR(cmd) & _IOC_WRITE) && (size <= sizeof(karg)))
if (copy_from_user(&karg, (void *)arg, size))
return -EFAULT;
/* serialize the switch with other processes */
mutex_lock(&dev->mutex);
switch(cmd) {
case RR_DEVSEL:
/* If we are the only user, change device requested id */
if (dev->usecount > 1) {
printk("usecount %i\n", dev->usecount);
ret = -EBUSY;
break;
}
/* Warning: race: we can't take the lock */
*dev->devsel = karg.devsel;
ret = rr_fill_table_and_probe(dev);
if (!ret)
ret = -ENODEV; /* timeout */
else if (ret > 0)
ret = 0; /* success */
break;
case RR_DEVGET:
/* Return to user space the id of the current device */
if (!dev->pdev) {
ret = -ENODEV;
break;
}
memset(&karg.devsel, 0, sizeof(karg.devsel));
karg.devsel.vendor = dev->pdev->vendor;
karg.devsel.device = dev->pdev->device;
karg.devsel.subvendor = dev->pdev->subsystem_vendor;
karg.devsel.subdevice = dev->pdev->subsystem_device;
karg.devsel.bus = dev->pdev->bus->number;
karg.devsel.devfn = dev->pdev->devfn;
break;
case RR_READ: /* Read a "word" of memory */
case RR_WRITE: /* Write a "word" of memory */
ret = rr_do_iocmd(dev, cmd, &karg.iocmd);
break;
case RR_IRQWAIT: /* Wait for an interrupt to happen */
count = dev->irqcount;
if (dev->flags & RR_FLAG_IRQDISABLE) {
ret = -EAGAIN; /* already happened */
break;
}
wait_event_interruptible(dev->q, count != dev->irqcount);
if (signal_pending(current))
ret = -ERESTARTSYS;
break;
case RR_IRQENA: /* Re-enable the interrupt after handling it */
getnstimeofday(&tv);
tvirq = dev->irqtime;
if ( !(dev->flags & RR_FLAG_IRQDISABLE)) {
ret = -EAGAIN;
break;
}
dev->flags &= ~RR_FLAG_IRQDISABLE;
/* enable_irq(dev->pdev->irq); */
/* return the delay to user space, capped at 1s */
if (tv.tv_sec - tvirq.tv_sec > 1) {
ret = NSEC_PER_SEC;
break;
}
ret = (tv.tv_sec - tvirq.tv_sec) * NSEC_PER_SEC
+ tv.tv_nsec - tvirq.tv_nsec;
if (ret > NSEC_PER_SEC) {
ret = NSEC_PER_SEC;
break;
}
break;
case RR_GETDMASIZE: /* Return the current dma size */
ret = rr_bufsize;
break;
case RR_GETPLIST: /* Return the page list */
/* Since we assume PAGE_SIZE is 4096, check at compile time */
if (PAGE_SIZE != RR_PLIST_SIZE) {
extern void __page_size_is_not_4096(void);
__page_size_is_not_4096(); /* undefined symbol */
}
if (!access_ok(VERIFY_WRITE, arg, RR_PLIST_SIZE)) {
ret = -EFAULT;
break;
}
for (addr = dev->dmabuf; addr - dev->dmabuf < rr_bufsize;
addr += PAGE_SIZE) {
if (0) {
printk("page @ %p - pfn %08lx\n", addr,
page_to_pfn(vmalloc_to_page(addr)));
}
__put_user(page_to_pfn(vmalloc_to_page(addr)), uptr);
uptr++;
}
break;
default:
ret = -ENOIOCTLCMD;
break;
}
/* finally, copy data to user space and return */
mutex_unlock(&dev->mutex);
if (ret < 0)
return ret;
if ((_IOC_DIR(cmd) & _IOC_READ) && (size <= sizeof(karg)))
if (copy_to_user((void *)arg, &karg, size))
return -EFAULT;
return ret;
}
/*
* Other fops are more conventional
*/
static int rr_open(struct inode *ino, struct file *f)
{
struct rr_dev *dev = &rr_dev;
f->private_data = dev;
mutex_lock(&dev->mutex);
dev->usecount++;
mutex_unlock(&dev->mutex);
return 0;
}
static int rr_release(struct inode *ino, struct file *f)
{
struct rr_dev *dev = f->private_data;
mutex_lock(&dev->mutex);
dev->usecount--;
mutex_unlock(&dev->mutex);
return 0;
}
static int rr_mmap(struct file *f, struct vm_area_struct *vma)
{
return -EOPNOTSUPP;
}
static ssize_t rr_read(struct file *f, char __user *buf, size_t count,
loff_t *offp)
{
struct rr_dev *dev = f->private_data;
void *base;
loff_t pos = *offp;
int bar, off, size;
bar = __RR_GET_BAR(pos) / 2; /* index in the array */
off = __RR_GET_OFF(pos);
if (0)
printk("%s: pos %llx = bar %x off %x\n", __func__, pos,
bar*2, off);
if (!rr_is_valid_bar(pos))
return -EINVAL;
/* reading the DMA buffer is trivial, so do it first */
if (RR_IS_DMABUF(pos)) {
base = dev->dmabuf;
if (off >= rr_bufsize)
return 0; /* EOF */
if (off + count > rr_bufsize)
count = rr_bufsize - off;
if (copy_to_user(buf, base + off, count))
return -EFAULT;
*offp += count;
return count;
}
/* inexistent or I/O ports: EINVAL */
if (!dev->remap[bar])
return -EINVAL;
base = dev->remap[bar];
/* valid on-board area: enforce sized access if size is 1,2,4,8 */
size = dev->area[bar]->end + 1 - dev->area[bar]->start;
if (off >= size)
return -EIO; /* it's not memory, an error is better than EOF */
if (count + off > size)
count = size - off;
switch (count) {
case 1:
if (put_user(readb(base + off), (u8 *)buf))
return -EFAULT;
break;
case 2:
if (put_user(readw(base + off), (u16 *)buf))
return -EFAULT;
break;
case 4:
if (put_user(readl(base + off), (u32 *)buf))
return -EFAULT;
break;
case 8:
if (put_user(readq(base + off), (u64 *)buf))
return -EFAULT;
break;
default:
if (copy_to_user(buf, base + off, count))
return -EFAULT;
}
*offp += count;
return count;
}
static ssize_t rr_write(struct file *f, const char __user *buf, size_t count,
loff_t *offp)
{
struct rr_dev *dev = f->private_data;
void *base;
loff_t pos = *offp;
int bar, off, size;
union {u8 d8; u16 d16; u32 d32; u64 d64;} data;
bar = __RR_GET_BAR(pos) / 2; /* index in the array */
off = __RR_GET_OFF(pos);
if (!rr_is_valid_bar(pos))
return -EINVAL;
/* writing the DMA buffer is trivial, so do it first */
if (RR_IS_DMABUF(pos)) {
base = dev->dmabuf;
if (off >= rr_bufsize)
return -ENOSPC;
if (off + count > rr_bufsize)
count = rr_bufsize - off;
if (copy_from_user(base + off, buf, count))
return -EFAULT;
*offp += count;
return count;
}
/* inexistent or I/O ports: EINVAL */
if (!dev->remap[bar])
return -EINVAL;
base = dev->remap[bar];
/* valid on-board area: enforce sized access if size is 1,2,4,8 */
size = dev->area[bar]->end + 1 - dev->area[bar]->start;
if (off >= size)
return -EIO; /* it's not memory, an error is better than EOF */
if (count + off > size)
count = size - off;
switch (count) {
case 1:
if (get_user(data.d8, (u8 *)buf))
return -EFAULT;
writeb(data.d8, base + off);
break;
case 2:
if (get_user(data.d16, (u16 *)buf))
return -EFAULT;
writew(data.d16, base + off);
break;
case 4:
if (get_user(data.d32, (u32 *)buf))
return -EFAULT;
writel(data.d32, base + off);
break;
case 8:
/* while put_user_8 exists, get_user_8 does not */
if (copy_from_user(&data.d64, buf, count))
return -EFAULT;
writeq(data.d64, base + off);
break;
default:
if (copy_from_user(base + off, buf, count))
return -EFAULT;
}
*offp += count;
return count;
}
static struct file_operations rr_fops = {
.open = rr_open,
.release = rr_release,
.read = rr_read,
.write = rr_write,
.mmap = rr_mmap,
.unlocked_ioctl = rr_ioctl,
};
/* Registering and unregistering the misc device */
static struct miscdevice rr_misc = {
.minor = 42,
.name = "rawrabbit",
.fops = &rr_fops,
};
/* init and exit */
static int rr_init(void)
{
int ret;
struct rr_dev *dev = &rr_dev; /* always use dev as pointer */
if (rr_bufsize > RR_MAX_BUFSIZE) {
printk(KERN_WARNING "rawrabbit: too big a size, using 0x%x\n",
RR_MAX_BUFSIZE);
rr_bufsize = RR_MAX_BUFSIZE;
}
dev->dmabuf = __vmalloc(rr_bufsize, GFP_KERNEL | __GFP_ZERO,
PAGE_KERNEL);
if (!dev->dmabuf)
return -ENOMEM;
/* misc device, that's trivial */
ret = misc_register(&rr_misc);
if (ret < 0) {
printk(KERN_ERR "%s: Can't register misc device\n",
KBUILD_MODNAME);
return ret;
}
/* prepare registration of the pci driver according to parameters */
dev->devsel->vendor = rr_vendor;
dev->devsel->device = rr_device;
dev->devsel->subvendor = RR_DEVSEL_UNUSED;
dev->devsel->bus = RR_DEVSEL_UNUSED;
/* This function return < 0 on error, 0 on timeout, > 0 on success */
ret = rr_fill_table_and_probe(dev);
if (ret < 0) {
misc_deregister(&rr_misc);
return ret;
}
return 0;
}
static void rr_exit(void)
{
struct rr_dev *dev = &rr_dev;
pci_unregister_driver(&rr_pcidrv);
misc_deregister(&rr_misc);
vfree(dev->dmabuf);
}
module_init(rr_init);
module_exit(rr_exit);
MODULE_LICENSE("GPL");
/*
* Public header for the raw I/O interface for PCI or PCI express interfaces
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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.
*/
#ifndef __RAWRABBIT_H__
#define __RAWRABBIT_H__
#include <linux/types.h>
#include <linux/ioctl.h>
#ifdef __KERNEL__ /* The initial part of the file is driver-internal stuff */
#include <linux/pci.h>
#include <linux/completion.h>
#include <linux/workqueue.h>
#include <linux/firmware.h>
#include <linux/wait.h>
struct rr_devsel;
struct rr_dev {
struct rr_devsel *devsel;
struct pci_driver *pci_driver;
struct pci_device_id *id_table;
struct pci_dev *pdev; /* non-null after pciprobe */
struct mutex mutex;
wait_queue_head_t q;
void *dmabuf;
struct timespec irqtime;
unsigned long irqcount;
struct completion complete;
struct resource *area[3]; /* bar 0, 2, 4 */
void *remap[3]; /* ioremap of bar 0, 2, 4 */
unsigned long flags;
struct work_struct work;
const struct firmware *fw;
int usecount;
};
#define RR_FLAG_REGISTERED 0x00000001
#define RR_FLAG_IRQDISABLE 0x00000002
#define RR_FLAG_IRQREQUEST 0x00000002
#define RR_PROBE_TIMEOUT (HZ/10) /* for pci_register_drv */
/* These two live in ./loader.c */
extern void rr_ask_firmware(struct rr_dev *dev);
extern void rr_load_firmware(struct work_struct *work);
#endif /* __KERNEL__ */
/* By default, the driver registers for this vendor/devid */
#define RR_DEFAULT_VENDOR 0x1a39
#define RR_DEFAULT_DEVICE 0x0004
#define RR_DEFAULT_FWNAME "rrabbit-%P-%p@%b"
#define RR_MAX_FWNAME_SIZE 64
#define RR_DEFAULT_BUFSIZE (1<<20) /* 1MB */
#define RR_PLIST_SIZE 4096 /* no PAGE_SIZE in user space */
#define RR_PLIST_LEN (RR_PLIST_SIZE / sizeof(void *))
#define RR_MAX_BUFSIZE (RR_PLIST_SIZE * RR_PLIST_LEN)
/* This structure is used to select the device to be accessed, via ioctl */
struct rr_devsel {
__u16 vendor;
__u16 device;
__u16 subvendor; /* RR_DEVSEL_UNUSED to ignore subvendor/dev */
__u16 subdevice;
__u16 bus; /* RR_DEVSEL_UNUSED to ignore bus and devfn */
__u16 devfn;
};
#define RR_DEVSEL_UNUSED 0xffff
/* Offsets for BAR areas in llseek() and/or ioctl */
#define RR_BAR_0 0x00000000
#define RR_BAR_2 0x20000000
#define RR_BAR_4 0x40000000
#define RR_BAR_BUF 0xc0000000 /* The DMA buffer */
#define RR_IS_DMABUF(addr) ((addr) >= RR_BAR_BUF)
#define __RR_GET_BAR(x) ((x) >> 28)
#define __RR_SET_BAR(x) ((x) << 28)
#define __RR_GET_OFF(x) ((x) & 0x0fffffff)
static inline int rr_is_valid_bar(unsigned long address)
{
int bar = __RR_GET_BAR(address);
return bar == 0 || bar == 2 || bar == 4 || bar == 0x0c;
}
static inline int rr_is_dmabuf_bar(unsigned long address)
{
int bar = __RR_GET_BAR(address);
return bar == 0x0c;
}
struct rr_iocmd {
__u32 address; /* bar and offset */
__u32 datasize; /* 1 or 2 or 4 or 8 */
union {
__u8 data8;
__u16 data16;
__u32 data32;
__u64 data64;
};
};
/* ioctl commands */
#define __RR_IOC_MAGIC '4' /* random or so */
#define RR_DEVSEL _IOW(__RR_IOC_MAGIC, 0, struct rr_devsel)
#define RR_DEVGET _IOR(__RR_IOC_MAGIC, 1, struct rr_devsel)
#define RR_READ _IOWR(__RR_IOC_MAGIC, 2, struct rr_iocmd)
#define RR_WRITE _IOW(__RR_IOC_MAGIC, 3, struct rr_iocmd)
#define RR_IRQWAIT _IO(__RR_IOC_MAGIC, 4)
#define RR_IRQENA _IO(__RR_IOC_MAGIC, 5)
#define RR_GETDMASIZE _IO(__RR_IOC_MAGIC, 6)
/* #define RR_SETDMASIZE _IO(__RR_IOC_MAGIC, 7, unsigned long) */
#define RR_GETPLIST _IO(__RR_IOC_MAGIC, 8) /* returns a whole page */
#define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, struct dirent [2])
/* Registers from the gennum header files */
enum {
GPIO_BASE = 0xA00,
GPIO_INT_STATUS = GPIO_BASE + 0x20,
FCL_BASE = 0xB00,
FCL_CTRL = FCL_BASE,
FCL_STATUS = FCL_BASE + 0x4,
FCL_IODATA_IN = FCL_BASE + 0x8,
FCL_IODATA_OUT = FCL_BASE + 0xC,
FCL_EN = FCL_BASE + 0x10,
FCL_TIMER_0 = FCL_BASE + 0x14,
FCL_TIMER_1 = FCL_BASE + 0x18,
FCL_CLK_DIV = FCL_BASE + 0x1C,
FCL_IRQ = FCL_BASE + 0x20,
FCL_TIMER_CTRL = FCL_BASE + 0x24,
FCL_IM = FCL_BASE + 0x28,
FCL_TIMER2_0 = FCL_BASE + 0x2C,
FCL_TIMER2_1 = FCL_BASE + 0x30,
FCL_DBG_STS = FCL_BASE + 0x34,
FCL_FIFO = 0xE00,
PCI_SYS_CFG_SYSTEM = 0x800
};
#endif /* __RAWRABBIT_H__ */
CFLAGS = -Wall -ggdb -I../kernel
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
ALL = rrcmd flip
all: $(ALL)
clean:
rm -f $(ALL) *.o *~
\ No newline at end of file
/* Trivial.... */
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned int c;
while ( (c = getchar()) != EOF)
putchar( 0
| ((c & 0x80) >> 7)
| ((c & 0x40) >> 5)
| ((c & 0x20) >> 3)
| ((c & 0x10) >> 1)
| ((c & 0x08) << 1)
| ((c & 0x04) << 3)
| ((c & 0x02) << 5)
| ((c & 0x01) << 7));
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include "rr_io.h"
int main(int argc, char *argv[])
{
if(argc != 2){
printf( "usage: %s filename \n", argv[0]);
}
else{
rr_init();
gennum_writel(0xE001F07C, 0x808); // set local bus freq
//gennum_writel(0x00002000, 0xA04); // GPIO direction
//gennum_writel(0x0000D000, 0xA08); // GPIO output enable
//gennum_writel(0x00008000, 0xA0C); // GPIO output value
printf("Firmware to be loaded : %s \n", argv[1]);
rr_load_bitstream_from_file(argv[1]);
printf("Firmware loaded successfully ! \n");
}
}
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include "rr_io.h"
#include "gpio.h"
int main(int argc, char *argv[])
{
uint32_t i=0;
if(argc != 2){
printf( "usage: %s filename \n", argv[0]);
}
else{
rr_init();
gpio_init();
gpio_bootselect(GENNUM_FPGA);
//gpio_bootselect(FPGA_FLASH);
printf("CLK_CSR : %.8X\n", gennum_readl(0x808));
/*
while(1){
i++;
//printf("%5d Flash status : %.2X\n", i, flash_read_status());
printf("%5d Flash status : %.8X\n", i, flash_read_id());
sleep(1);
}
*/
printf("Firmware to be loaded : %s \n", argv[1]);
rr_load_bitstream_from_file(argv[1]);
printf("Firmware loaded successfully ! \n");
}
}
#include <stdio.h>
#include <sys/time.h>
#include "rr_io.h"
#include "gpio.h"
void gpio_set1(uint32_t addr, uint8_t bit)
{
uint32_t reg;
reg = gennum_readl(addr);
//printf("register:%.8X ", reg);
reg |= (1 << bit);
//printf("SET :%.8X(%.2d):%.8X\n", addr, bit, reg);
gennum_writel(reg, addr);
}
void gpio_set0(uint32_t addr, uint8_t bit)
{
uint32_t reg;
reg = gennum_readl(addr);
//printf("register:%.8X ", reg);
reg &= ~(1 << bit);
//printf("CLEAR:%.8X(%.2d):%.8X\n", addr, bit, reg);
gennum_writel(reg, addr);
}
uint8_t gpio_get(uint32_t addr, uint8_t bit)
{
return (gennum_readl(addr) & (1 << bit)) ? 1 : 0;
}
void gpio_init(void)
{
gennum_writel(0x00000000, FCL_CTRL);// FCL mode
gennum_writel(0x00000017, FCL_EN);// FCL output enable
gennum_writel(0x00000000, FCL_IODATA_OUT);// FCL outputs to 0
gennum_writel(0x00002000, GPIO_DIRECTION_MODE); // GPIO direction (1=input)
gennum_writel(0x0000D000, GPIO_OUTPUT_ENABLE); // GPIO output enable
gennum_writel(0x00000000, GPIO_OUTPUT_VALUE); // GPIO output to 0
gpio_set1(GPIO_OUTPUT_VALUE, GPIO_FLASH_CS);
}
void gpio_bootselect(uint8_t select)
{
switch(select){
case GENNUM_FLASH:
gpio_set0(GPIO_OUTPUT_VALUE, GPIO_BOOTSEL0);
gpio_set1(GPIO_OUTPUT_VALUE, GPIO_BOOTSEL1);
break;
case GENNUM_FPGA:
gpio_set1(GPIO_OUTPUT_VALUE, GPIO_BOOTSEL0);
gpio_set0(GPIO_OUTPUT_VALUE, GPIO_BOOTSEL1);
break;
case FPGA_FLASH:
gennum_writel(0x00000000, FCL_EN);// FCL output all disabled
gpio_set1(GPIO_OUTPUT_VALUE, GPIO_BOOTSEL0);
gpio_set1(GPIO_OUTPUT_VALUE, GPIO_BOOTSEL1);
break;
default:
break;
}
}
static uint8_t spi_read8(void)
{
uint8_t rx;
int i;
gpio_set0(FCL_IODATA_OUT, SPRI_CLKOUT);
for(i = 0; i < 8;i++){
usleep(SPI_DELAY);
rx <<= 1;
if (gpio_get(GPIO_INPUT_VALUE, GPIO_SPRI_DIN))
rx |= 1;
//usleep(SPI_DELAY);
gpio_set1(FCL_IODATA_OUT, SPRI_CLKOUT);
usleep(SPI_DELAY);
gpio_set0(FCL_IODATA_OUT, SPRI_CLKOUT);
}
usleep(SPI_DELAY);
return rx;
}
static void spi_write8(uint8_t tx)
{
int i;
gpio_set0(FCL_IODATA_OUT, SPRI_CLKOUT);
for(i = 0; i < 8;i++){
//usleep(SPI_DELAY);
if(tx & 0x80)
gpio_set1(FCL_IODATA_OUT, SPRI_DATAOUT);
else
gpio_set0(FCL_IODATA_OUT, SPRI_DATAOUT);
tx<<=1;
usleep(SPI_DELAY);
gpio_set1(FCL_IODATA_OUT, SPRI_CLKOUT);
usleep(SPI_DELAY);
gpio_set0(FCL_IODATA_OUT, SPRI_CLKOUT);
}
usleep(SPI_DELAY);
}
uint8_t flash_read_status(void)
{
uint8_t val;
gpio_set0(GPIO_OUTPUT_VALUE, GPIO_FLASH_CS);
usleep(SPI_DELAY);
spi_write8(FLASH_RDSR);
val = spi_read8();
gpio_set1(GPIO_OUTPUT_VALUE, GPIO_FLASH_CS);
usleep(SPI_DELAY);
return val;
}
uint32_t flash_read_id(void)
{
uint32_t val=0;
gpio_set0(GPIO_OUTPUT_VALUE, GPIO_FLASH_CS);
usleep(SPI_DELAY);
spi_write8(FLASH_RDID);
val = (spi_read8() << 16);
val += (spi_read8() << 8);
val += spi_read8();
gpio_set1(GPIO_OUTPUT_VALUE, GPIO_FLASH_CS);
usleep(SPI_DELAY);
return val;
}
#define FCL_CTRL 0xB00
#define FCL_STATUS 0xB04
#define FCL_IODATA_IN 0xB08
#define FCL_IODATA_OUT 0xB0C
#define FCL_EN 0xB10
#define GPIO_DIRECTION_MODE 0xA04
#define GPIO_OUTPUT_ENABLE 0xA08
#define GPIO_OUTPUT_VALUE 0xA0C
#define GPIO_INPUT_VALUE 0xA10
#define SPRI_CLKOUT 0
#define SPRI_DATAOUT 1
#define SPRI_CONFIG 2
#define SPRI_DONE 3
#define SPRI_XI_SWAP 4
#define SPRI_STATUS 5
#define GPIO_SPRI_DIN 13
#define GPIO_FLASH_CS 12
#define GPIO_BOOTSEL0 15
#define GPIO_BOOTSEL1 14
#define SPI_DELAY 50
#define FLASH_WREN 0x06
#define FLASH_WRDI 0x04
#define FLASH_RDID 0x9F
#define FLASH_RDSR 0x05
#define FLASH_WRSR 0x01
#define FLASH_READ 0x03
#define FLASH_FAST_READ 0x0B
#define FLASH_PP 0x02
#define FLASH_SE 0xD8
#define FLASH_BE 0xC7
#define GENNUM_FLASH 1
#define GENNUM_FPGA 2
#define FPGA_FLASH 3
void gpio_init(void);
void gpio_bootselect(uint8_t select);
uint8_t flash_read_status(void);
uint32_t flash_read_id(void);
#!/bin/bash
cat pfc_top.bin | ./gnurabbit/user/flip > /lib/firmware/pfc_top_flipped.bin
#rmmod rawrabbit
insmod gnurabbit/kernel/rawrabbit.ko
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include "../kernel/rawrabbit.h"
#include "rr_io.h"
#define DEVNAME "/dev/rawrabbit"
static int fd;
int rr_init()
{
struct rr_devsel devsel;
int ret = -EINVAL;
fd = open(DEVNAME, O_RDWR);
if (fd < 0) {
return -1;
}
return 0;
}
int rr_writel(uint32_t data, uint32_t addr)
{
struct rr_iocmd iocmd;
iocmd.datasize = 4;
iocmd.address = addr;
iocmd.address |= __RR_SET_BAR(0);
iocmd.data32 = data;
ioctl(fd, RR_WRITE, &iocmd);
}
uint32_t rr_readl(uint32_t addr)
{
struct rr_iocmd iocmd;
iocmd.datasize = 4;
iocmd.address = addr;
iocmd.address |= __RR_SET_BAR(0);
ioctl(fd, RR_READ, &iocmd);
return iocmd.data32;
}
void gennum_writel(uint32_t data, uint32_t addr)
{
struct rr_iocmd iocmd;
iocmd.datasize = 4;
iocmd.address = addr;
iocmd.address |= __RR_SET_BAR(4);
iocmd.data32 = data;
ioctl(fd, RR_WRITE, &iocmd);
}
uint32_t gennum_readl(uint32_t addr)
{
struct rr_iocmd iocmd;
iocmd.datasize = 4;
iocmd.address = addr;
iocmd.address |= __RR_SET_BAR(4);
ioctl(fd, RR_READ, &iocmd);
return iocmd.data32;
}
static inline int64_t get_tics()
{
struct timezone tz= {0,0};
struct timeval tv;
gettimeofday(&tv, &tz);
return (int64_t)tv.tv_sec * 1000000LL + (int64_t) tv.tv_usec;
}
int rr_load_bitstream(const void *data, int size8)
{
int i, ctrl, done = 0, wrote = 0;
unsigned long j;
uint8_t val8;
const uint8_t *data8 = data;
const uint32_t *data32 = data;
int size32 = (size8 + 3) >> 2;
// fprintf(stderr,"Loading %d bytes...\n", size8);
if (1) {
/*
* Hmmm.... revers bits for xilinx images?
* We can't do in kernel space anyways, as the pages are RO
*/
uint8_t *d8 = (uint8_t *)data8; /* Horrible: kill const */
for (i = 0; i < size8; i++) {
val8 = d8[i];
d8[i] = 0
| ((val8 & 0x80) >> 7)
| ((val8 & 0x40) >> 5)
| ((val8 & 0x20) >> 3)
| ((val8 & 0x10) >> 1)
| ((val8 & 0x08) << 1)
| ((val8 & 0x04) << 3)
| ((val8 & 0x02) << 5)
| ((val8 & 0x01) << 7);
}
}
/* Do real stuff */
gennum_writel(0x00, FCL_CLK_DIV);
gennum_writel(0x40, FCL_CTRL); /* Reset */
i = gennum_readl( FCL_CTRL);
if (i != 0x40) {
fprintf(stderr, "%s: %i: error\n", __func__, __LINE__);
return;
}
gennum_writel(0x00, FCL_CTRL);
gennum_writel(0x00, FCL_IRQ); /* clear pending irq */
switch(size8 & 3) {
case 3: ctrl = 0x116; break;
case 2: ctrl = 0x126; break;
case 1: ctrl = 0x136; break;
case 0: ctrl = 0x106; break;
}
gennum_writel(ctrl, FCL_CTRL);
gennum_writel(0x00, FCL_CLK_DIV); /* again? maybe 1 or 2? */
gennum_writel(0x00, FCL_TIMER_CTRL); /* "disable FCL timer func" */
gennum_writel(0x10, FCL_TIMER_0); /* "pulse width" */
gennum_writel(0x00, FCL_TIMER_1);
/* Set delay before data and clock is applied by FCL after SPRI_STATUS is
detected being assert.
*/
gennum_writel(0x08, FCL_TIMER2_0); /* "delay before data/clock..." */
gennum_writel(0x00, FCL_TIMER2_1);
gennum_writel(0x17, FCL_EN); /* "output enable" */
ctrl |= 0x01; /* "start FSM configuration" */
gennum_writel(ctrl, FCL_CTRL);
while(size32 > 0)
{
/* Check to see if FPGA configuation has error */
i = gennum_readl( FCL_IRQ);
if ( (i & 8) && wrote) {
done = 1;
// fprintf(stderr,"%s: %idone after %i\n", __func__, __LINE__, wrote);
} else if ( (i & 0x4) && !done) {
fprintf(stderr,"Error after %i\n", wrote);
return -1;
}
// fprintf(stderr,".");
while(gennum_readl(FCL_IRQ) & (1<<5)); // wait until at least 1/2 of the FIFO is empty
/* Write 64 dwords into FIFO at a time. */
for (i = 0; size32 && i < 32; i++) {
gennum_writel(*data32, FCL_FIFO);
data32++; size32--; wrote++;
// udelay(20);
}
}
gennum_writel(0x186, FCL_CTRL); /* "last data written" */
int64_t tstart = get_tics();
//j = jiffies + 2 * HZ;
/* Wait for DONE interrupt */
while(!done) {
// fprintf(stderr,stderr, "Wait!");
i = gennum_readl( FCL_IRQ);
if (i & 0x8) {
// fprintf(stderr,"done after %i\n", wrote);
done = 1;
} else if( (i & 0x4) && !done) {
fprintf(stderr,"Error after %i\n", wrote);
return -1;
}
usleep(10000);
if(get_tics() - tstart > 1000000LL)
{
fprintf(stderr,"Loader: DONE timeout. Did you choose proper bitgen options?\n");
return;
}
/*if (time_after(jiffies, j)) {
printk("%s: %i: tout after %i\n", __func__, __LINE__,
wrote);
return;
} */
}
return done?0:-1;
}
int rr_load_bitstream_from_file(const char *file_name)
{
uint8_t *buf;
FILE *f;
uint32_t size;
f=fopen(file_name,"rb");
if(!f) return -1;
fseek(f, 0, SEEK_END);
size = ftell(f);
buf = malloc(size);
if(!buf)
{
fclose(f);
return -1;
}
fseek(f, 0, SEEK_SET);
fread(buf, 1, size, f);
fclose(f);
int rval = rr_load_bitstream(buf, size);
free(buf);
return rval;
}
#ifndef __RR_IO_H
#define __RR_IO_H
#include <stdint.h>
int rr_init();
int rr_writel(uint32_t data, uint32_t addr);
uint32_t rr_readl(uint32_t addr);
void gennum_writel(uint32_t data, uint32_t addr);
uint32_t gennum_readl(uint32_t addr);
int rr_load_bitstream(const void *data, int size8);
int rr_load_bitstream_from_file(const char *file_name);
#endif
/*
* User space frontend (command line) for the raw I/O interface
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "rawrabbit.h"
#define DEVNAME "/dev/rawrabbit"
char *prgname;
void help(void)
{
fprintf(stderr, "%s: use like this (all numbers are hex):\n"
" %s [<vendor:device>[/<subvendor>:<subdev>]"
"[@<bus>:<devfn>]] <cmd>\n", prgname, prgname);
fprintf(stderr, " <cmd> = info\n");
fprintf(stderr, " <cmd> = irqwait\n");
fprintf(stderr, " <cmd> = irqena\n");
fprintf(stderr, " <cmd> = getdmasize\n");
fprintf(stderr, " <cmd> = getplist\n");
fprintf(stderr, " <cmd> = r[<sz>] <bar>:<addr>\n");
fprintf(stderr, " <cmd> = w[<sz>] <bar>:<addr> <val>\n");
fprintf(stderr, " <sz> = 1, 2, 4, 8 (default = 4)\n");
fprintf(stderr, " <bar> = 0, 2, 4, c (c == dma buffer)\n");
exit(1);
}
int parse_devsel(int fd, char *arg)
{
struct rr_devsel devsel;
int n;
devsel.subvendor = RR_DEVSEL_UNUSED;
devsel.bus = RR_DEVSEL_UNUSED;
if (strlen(arg) > 32) /* to prevent overflow with strtol */
return -EINVAL;
n = sscanf(arg, "%hx:%hx/%hx:%hx@%hx:%hx",
&devsel.vendor, &devsel.device,
&devsel.subvendor, &devsel.subdevice,
&devsel.bus, &devsel.devfn);
switch(n) {
case 6: /* all info */
case 4: /* id/subid but no busdev */
break;
case 2:
/* check if bus/dev is there */
n = sscanf(arg, "%hx:%hx@%hx:%hx",
&devsel.vendor, &devsel.device,
&devsel.bus, &devsel.devfn);
if (n == 4 || n == 2)
break;
/* fall through */
default:
printf("%s: can't parse \"%s\"\n", prgname, arg);
return -EINVAL;
}
/* Now try to do the change */
if (ioctl(fd, RR_DEVSEL, &devsel) < 0) {
fprintf(stderr, "%s: %s: ioctl(DEVSEL): %s\n", prgname, DEVNAME,
strerror(errno));
return -EIO;
}
return 0;
}
int do_iocmd(int fd, char *cmdname, char *addr, char *datum)
{
char rest[32];
struct rr_iocmd iocmd;
int i, ret;
unsigned bar;
__u64 d;
char cmd;
if (strlen(cmdname) >= sizeof(rest))
return -EINVAL;
if (strlen(addr) >= sizeof(rest))
return -EINVAL;
if (datum && strlen(addr) >= sizeof(rest))
return -EINVAL;
/* parse command and size */
i = sscanf(cmdname, "%c%i%s\n", &cmd, &iocmd.datasize, rest);
if (cmd != 'r' && cmd != 'w')
return -EINVAL;
if (i == 3)
return -EINVAL;
if (i == 1)
iocmd.datasize = 4;
/* parse address */
i = sscanf(addr, "%x:%x%s", &bar, &iocmd.address, rest);
if (i != 2)
return -EINVAL;
iocmd.address |= __RR_SET_BAR(bar);
if (!rr_is_valid_bar(iocmd.address))
return -EINVAL;
/* parse datum */
if (datum) {
i = sscanf(datum, "%llx%s", &d, rest);
if (i == 2)
return -EINVAL;
switch(iocmd.datasize) {
case 1:
iocmd.data8 = d;
break;
case 2:
iocmd.data16 = d;
break;
case 4:
iocmd.data32 = d;
break;
case 8:
iocmd.data64 = d;
break;
default:
return -EINVAL;
}
}
if (datum)
ret = ioctl(fd, RR_WRITE, &iocmd);
else
ret = ioctl(fd, RR_READ, &iocmd);
if (ret < 0)
return -errno;
if (!datum && !ret) {
switch(iocmd.datasize) {
case 1:
printf("0x%02x\n", (unsigned int)iocmd.data8);
break;
case 2:
printf("0x%04x\n", (unsigned int)iocmd.data16);
break;
case 4:
printf("0x%08lx\n", (unsigned long)iocmd.data32);
break;
case 8:
printf("0x%016llx\n", (unsigned long long)iocmd.data64);
break;
default:
return -EINVAL;
}
}
return ret;
}
int do_getplist(int fd)
{
uintptr_t plist[RR_PLIST_LEN];
int i, size;
size = ioctl(fd, RR_GETDMASIZE);
if (size < 0)
return -errno;
i = ioctl(fd, RR_GETPLIST, plist);
if (i < 0)
return -errno;
for (i = 0; i < size/RR_PLIST_SIZE; i++)
printf("buf 0x%08x: pfn 0x%08x, addr 0x%012llx\n",
i * RR_PLIST_SIZE, plist[i],
(unsigned long long)plist[i] << 12);
return 0;
}
int main(int argc, char **argv)
{
struct rr_devsel devsel;
int fd, ret = -EINVAL;
prgname = argv[0];
fd = open(DEVNAME, O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: %s: %s\n", prgname, DEVNAME,
strerror(errno));
exit(1);
}
/* parse argv[1] for devsel */
if (argc > 1 && strchr(argv[1], ':')) {
ret = parse_devsel(fd, argv[1]);
if (!ret) {
argc--, argv++;
}
}
if (argc > 1 && !strcmp(argv[1], "info")) {
if (ioctl(fd, RR_DEVGET, &devsel) < 0) {
if (errno == ENODEV) {
printf("%s: not bound\n", DEVNAME);
exit(0);
}
fprintf(stderr, "%s: %s: ioctl(DEVGET): %s\n", prgname,
DEVNAME, strerror(errno));
exit(1);
}
printf("%s: bound to %04x:%04x/%04x:%04x@%04x:%04x\n", DEVNAME,
devsel.vendor, devsel.device,
devsel.subvendor, devsel.subdevice,
devsel.bus, devsel.devfn);
ret = 0;
} else if (argc > 1 && !strcmp(argv[1], "irqwait")) {
ret = ioctl(fd, RR_IRQWAIT);
if (ret < 0)
fprintf(stderr, "%s: ioctl(IRQWAIT): %s\n", argv[0],
strerror(errno));
} else if (argc > 1 && !strcmp(argv[1], "irqena")) {
ret = ioctl(fd, RR_IRQENA);
if (ret < 0) {
fprintf(stderr, "%s: ioctl(IRQENA): %s\n", argv[0],
strerror(errno));
} else {
printf("delay: %i ns\n", ret);
ret = 0;
}
} else if (argc > 1 && !strcmp(argv[1], "getdmasize")) {
ret = ioctl(fd, RR_GETDMASIZE);
printf("dmasize: %i (0x%x -- %g MB)\n", ret, ret,
ret / (double)(1024*1024));
ret = 0;
} else if (argc > 1 && !strcmp(argv[1], "getplist")) {
ret = do_getplist(fd);
} else if (argc == 3 || argc == 4) {
ret = do_iocmd(fd, argv[1], argv[2], argv[3] /* may be NULL */);
} else if (argc > 4) {
ret = -EINVAL;
}
/* subroutines return "invalid argument" to ask for help */
if (ret == -EINVAL)
help();
if (ret) {
fprintf(stderr, "%s: command returned \"%s\"\n", prgname,
strerror(errno));
exit(1);
}
return 0;
}
CFLAGS = -Wall -ggdb -I../kernel
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
ALL = rrcmd flip loadfile
all: $(ALL)
loadfile: load-main.o loader-ll.o
$(CC) $^ $(LDFLAGS) -o $@
loader-ll.o: ../kernel/loader-ll.c
$(CC) $(CFLAGS) $^ -c -o $@
clean:
rm -f $(ALL) *.o *~
/* Trivial.... */
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned int c;
while ( (c = getchar()) != EOF)
putchar( 0
| ((c & 0x80) >> 7)
| ((c & 0x40) >> 5)
| ((c & 0x20) >> 3)
| ((c & 0x10) >> 1)
| ((c & 0x08) << 1)
| ((c & 0x04) << 3)
| ((c & 0x02) << 5)
| ((c & 0x01) << 7));
return 0;
}
/* Trivial frontend for the gennum loader (../kernel/loader-ll.c) */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "rawrabbit.h"
#include "loader-ll.h"
#define DEVNAME "/dev/rawrabbit"
static char buf[64*1024*1024]; /* 64 MB binary? */
int main(int argc, char **argv)
{
int fd;
FILE *f;
int nbytes, rval;
if (argc != 2) {
fprintf(stderr, "%s: Use \"%s <firmware-file>\n",
argv[0], argv[0]);
exit(1);
}
f = fopen(argv[1], "r");
if (!f) {
fprintf(stderr, "%s: %s: %s\n",
argv[0], argv[1], strerror(errno));
exit(1);
}
fd = open(DEVNAME, O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: %s: %s\n",
argv[0], DEVNAME, strerror(errno));
exit(1);
}
nbytes = fread(buf, 1, sizeof(buf), f);
fclose(f);
if (nbytes < 0) {
fprintf(stderr, "%s: %s: %s\n",
argv[0], argv[1], strerror(errno));
exit(1);
}
printf("Programming %i bytes of binary gateware\n", nbytes);
rval = loader_low_level(fd, NULL, buf, nbytes);
if (rval < 0) {
fprintf(stderr, "%s: load_firmware: %s\n",
argv[0], strerror(-rval));
exit(1);
}
/* We must now wait for the "done" interrupt bit */
{
unsigned long t = time(NULL) + 3;
int i, done = 0;
struct rr_iocmd iocmd = {
.datasize = 4,
.address = FCL_IRQ | __RR_SET_BAR(4),
};
if (ioctl(fd, RR_READ, &iocmd) < 0) perror("ioctl");
while (time(NULL) < t) {
if (ioctl(fd, RR_READ, &iocmd) < 0) perror("ioctl");
i = iocmd.data32;
if (i & 0x8) {
done = 1;
break;
}
if (i & 0x4) {
fprintf(stderr,"Error after %i words\n", rval);
exit(1);
}
usleep(100*1000);
}
}
return 0;
}
/*
* User space frontend (command line) for the raw I/O interface
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "rawrabbit.h"
#define DEVNAME "/dev/rawrabbit"
char *prgname;
void help(void)
{
fprintf(stderr, "%s: use like this (all numbers are hex):\n"
" %s [<vendor:device>[/<subvendor>:<subdev>]"
"[@<bus>:<devfn>]] <cmd>\n", prgname, prgname);
fprintf(stderr, " <cmd> = info\n");
fprintf(stderr, " <cmd> = irqwait\n");
fprintf(stderr, " <cmd> = irqena\n");
fprintf(stderr, " <cmd> = getdmasize\n");
fprintf(stderr, " <cmd> = getplist\n");
fprintf(stderr, " <cmd> = r[<sz>] <bar>:<addr>\n");
fprintf(stderr, " <cmd> = w[<sz>] <bar>:<addr> <val>\n");
fprintf(stderr, " <sz> = 1, 2, 4, 8 (default = 4)\n");
fprintf(stderr, " <bar> = 0, 2, 4, c (c == dma buffer)\n");
exit(1);
}
int parse_devsel(int fd, char *arg)
{
struct rr_devsel devsel;
int n;
devsel.subvendor = RR_DEVSEL_UNUSED;
devsel.bus = RR_DEVSEL_UNUSED;
if (strlen(arg) > 32) /* to prevent overflow with strtol */
return -EINVAL;
n = sscanf(arg, "%hx:%hx/%hx:%hx@%hx:%hx",
&devsel.vendor, &devsel.device,
&devsel.subvendor, &devsel.subdevice,
&devsel.bus, &devsel.devfn);
switch(n) {
case 6: /* all info */
case 4: /* id/subid but no busdev */
break;
case 2:
/* check if bus/dev is there */
n = sscanf(arg, "%hx:%hx@%hx:%hx",
&devsel.vendor, &devsel.device,
&devsel.bus, &devsel.devfn);
if (n == 4 || n == 2)
break;
/* fall through */
default:
printf("%s: can't parse \"%s\"\n", prgname, arg);
return -EINVAL;
}
/* Now try to do the change */
if (ioctl(fd, RR_DEVSEL, &devsel) < 0) {
fprintf(stderr, "%s: %s: ioctl(DEVSEL): %s\n", prgname, DEVNAME,
strerror(errno));
return -EIO;
}
return 0;
}
int do_iocmd(int fd, char *cmdname, char *addr, char *datum)
{
char rest[32];
struct rr_iocmd iocmd;
int i, ret;
unsigned bar;
__u64 d;
char cmd;
if (strlen(cmdname) >= sizeof(rest))
return -EINVAL;
if (strlen(addr) >= sizeof(rest))
return -EINVAL;
if (datum && strlen(addr) >= sizeof(rest))
return -EINVAL;
/* parse command and size */
i = sscanf(cmdname, "%c%i%s\n", &cmd, &iocmd.datasize, rest);
if (cmd != 'r' && cmd != 'w')
return -EINVAL;
if (i == 3)
return -EINVAL;
if (i == 1)
iocmd.datasize = 4;
/* parse address */
i = sscanf(addr, "%x:%x%s", &bar, &iocmd.address, rest);
if (i != 2)
return -EINVAL;
iocmd.address |= __RR_SET_BAR(bar);
if (!rr_is_valid_bar(iocmd.address))
return -EINVAL;
/* parse datum */
if (datum) {
i = sscanf(datum, "%llx%s", &d, rest);
if (i == 2)
return -EINVAL;
switch(iocmd.datasize) {
case 1:
iocmd.data8 = d;
break;
case 2:
iocmd.data16 = d;
break;
case 4:
iocmd.data32 = d;
break;
case 8:
iocmd.data64 = d;
break;
default:
return -EINVAL;
}
}
if (datum)
ret = ioctl(fd, RR_WRITE, &iocmd);
else
ret = ioctl(fd, RR_READ, &iocmd);
if (ret < 0)
return -errno;
if (!datum && !ret) {
switch(iocmd.datasize) {
case 1:
printf("0x%02x\n", (unsigned int)iocmd.data8);
break;
case 2:
printf("0x%04x\n", (unsigned int)iocmd.data16);
break;
case 4:
printf("0x%08lx\n", (unsigned long)iocmd.data32);
break;
case 8:
printf("0x%016llx\n", (unsigned long long)iocmd.data64);
break;
default:
return -EINVAL;
}
}
return ret;
}
int do_getplist(int fd)
{
uintptr_t plist[RR_PLIST_LEN];
int i, size;
size = ioctl(fd, RR_GETDMASIZE);
if (size < 0)
return -errno;
i = ioctl(fd, RR_GETPLIST, plist);
if (i < 0)
return -errno;
for (i = 0; i < size/RR_PLIST_SIZE; i++)
printf("buf 0x%08x: pfn 0x%08x, addr 0x%012llx\n",
i * RR_PLIST_SIZE, plist[i],
(unsigned long long)plist[i] << 12);
return 0;
}
int main(int argc, char **argv)
{
struct rr_devsel devsel;
int fd, ret = -EINVAL;
prgname = argv[0];
fd = open(DEVNAME, O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: %s: %s\n", prgname, DEVNAME,
strerror(errno));
exit(1);
}
/* parse argv[1] for devsel */
if (argc > 1 && strchr(argv[1], ':')) {
ret = parse_devsel(fd, argv[1]);
if (!ret) {
argc--, argv++;
}
}
if (argc > 1 && !strcmp(argv[1], "info")) {
if (ioctl(fd, RR_DEVGET, &devsel) < 0) {
if (errno == ENODEV) {
printf("%s: not bound\n", DEVNAME);
exit(0);
}
fprintf(stderr, "%s: %s: ioctl(DEVGET): %s\n", prgname,
DEVNAME, strerror(errno));
exit(1);
}
printf("%s: bound to %04x:%04x/%04x:%04x@%04x:%04x\n", DEVNAME,
devsel.vendor, devsel.device,
devsel.subvendor, devsel.subdevice,
devsel.bus, devsel.devfn);
ret = 0;
} else if (argc > 1 && !strcmp(argv[1], "irqwait")) {
ret = ioctl(fd, RR_IRQWAIT);
if (ret < 0)
fprintf(stderr, "%s: ioctl(IRQWAIT): %s\n", argv[0],
strerror(errno));
} else if (argc > 1 && !strcmp(argv[1], "irqena")) {
ret = ioctl(fd, RR_IRQENA);
if (ret < 0) {
fprintf(stderr, "%s: ioctl(IRQENA): %s\n", argv[0],
strerror(errno));
} else {
printf("delay: %i ns\n", ret);
ret = 0;
}
} else if (argc > 1 && !strcmp(argv[1], "getdmasize")) {
ret = ioctl(fd, RR_GETDMASIZE);
printf("dmasize: %i (0x%x -- %g MB)\n", ret, ret,
ret / (double)(1024*1024));
ret = 0;
} else if (argc > 1 && !strcmp(argv[1], "getplist")) {
ret = do_getplist(fd);
} else if (argc == 3 || argc == 4) {
ret = do_iocmd(fd, argv[1], argv[2], argv[3] /* may be NULL */);
} else if (argc > 4) {
ret = -EINVAL;
}
/* subroutines return "invalid argument" to ask for help */
if (ret == -EINVAL)
help();
if (ret) {
fprintf(stderr, "%s: command returned \"%s\"\n", prgname,
strerror(errno));
exit(1);
}
return 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