Import wmediumd
Test: n/a
Bug: 191918323
Change-Id: I906c45cb04c2aa8cb556adff5a233e27b8114d8b
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+ 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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..f74f9cf
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,13 @@
+name: "wmediumd"
+description:
+ "This is a wireless medium simulation tool for Linux."
+
+third_party {
+ url {
+ type: GIT
+ value: "https://github.com/bcopeland/wmediumd"
+ }
+ version: "717e5d7fcc23eecbc8e32bd897a8fd4b1e3ba640"
+ last_upgrade_date { year: 2021 month: 6 day: 24 }
+ license_type: RESTRICTED
+}
diff --git a/MODULE_LICENSE_GPL b/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_GPL
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ff67257
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+SHELL=/bin/sh
+MAKE = make
+SUBDIRS ?= wmediumd
+BIN = wmediumd/wmediumd
+PREFIX ?= /usr
+BINDIR ?= /bin
+
+all:
+
+ @for i in $(SUBDIRS); do \
+ echo "make all in $$i..."; \
+ (cd $$i; $(MAKE) all); done
+
+clean:
+
+ @for i in $(SUBDIRS); do \
+ echo "Clearing in $$i..."; \
+ (cd $$i; $(MAKE) clean); done
+
+install: all
+ install -Dm 0755 $(BIN) $(DESTDIR)$(PREFIX)$(BINDIR)
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..b9f5251
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,4 @@
+jeongik@google.com
+jaeman@google.com
+sundongahn@google.com
+jooyung@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..354aa1e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,230 @@
+# Introduction
+
+This is a wireless medium simulation tool for Linux, based on the netlink API
+implemented in the `mac80211_hwsim` kernel driver. Unlike the default in-kernel
+forwarding mode of `mac80211_hwsim`, wmediumd allows simulating frame loss and
+delay.
+
+This version is forked from an earlier version, hosted here:
+
+ https://github.com/cozybit/wmediumd
+
+# Prerequisites
+
+First, you need a recent Linux kernel with the `mac80211_hwsim` module
+available. If you do not have this module, you may be able to build it using
+the [backports project](https://backports.wiki.kernel.org/index.php/Main_Page).
+
+Wmediumd requires libnl3.0.
+
+# Building
+```
+cd wmediumd && make
+```
+
+# Using Wmediumd
+
+Starting wmediumd with an appropriate config file is enough to make frames
+pass through wmediumd:
+```
+sudo modprobe mac80211_hwsim radios=2
+sudo ./wmediumd/wmediumd -c tests/2node.cfg &
+# run some hwsim test
+```
+However, please see the next section on some potential pitfalls.
+
+A complete example using network namespaces is given at the end of
+this document.
+
+# Configuration
+
+Wmediumd supports multiple ways of configuring the wireless medium.
+
+## Perfect medium
+
+With this configuration, all traffic flows between the configured interfaces, identified by their mac address:
+
+```
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+};
+```
+
+## Per-link loss probability model
+
+You can simulate a slightly more realistic channel by assigning fixed error
+probabilities to each link.
+
+```
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+};
+
+model:
+{
+ type = "prob";
+
+ default_prob = 1.0;
+ links = (
+ (0, 2, 0.000000),
+ (2, 3, 0.000000)
+ );
+};
+```
+
+The above configuration would assign 0% loss probability (perfect medium) to
+all frames flowing between nodes 0 and 2, and 100% loss probability to all
+other links. Unless both directions of a link are configured, the loss
+probability will be symmetric.
+
+This is a very simplistic model that does not take into account that losses
+depend on transmission rates and signal-to-noise ratio. For that, keep reading.
+
+## Per-link signal-to-noise ratio (SNR) model
+
+You can model different signal-to-noise ratios for each link by including a
+list of link tuples in the form of (sta1, sta2, snr).
+
+```
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+
+ links = (
+ (0, 1, 0),
+ (0, 2, 0),
+ (2, 0, 10),
+ (0, 3, 0),
+ (1, 2, 30),
+ (1, 3, 10),
+ (2, 3, 20)
+ );
+};
+```
+The snr will affect the maximum data rates that are successfully transmitted
+over the link.
+
+If only one direction of a link is configured, then the link will be
+symmetric. For asymmetric links, configure both directions, as in the
+above example where the path between 0 and 2 is usable in only one
+direction.
+
+The packet loss error probabilities are derived from this snr. See function
+`get_error_prob_from_snr()`. Or you can provide a packet-error-rate table like
+the one in `tests/signal_table_ieee80211ax`
+
+## Path loss model
+
+The path loss model derives signal-to-noise and probabilities from the
+coordinates of each node. This is an example configuration file for it.
+
+```
+ifaces : {...};
+model :
+{
+ type = "path_loss";
+ positions = (
+ (-50.0, 0.0),
+ ( 0.0, 40.0),
+ ( 0.0, -70.0),
+ ( 50.0, 0.0)
+ );
+ directions = (
+ ( 0.0, 0.0),
+ ( 0.0, 10.0),
+ ( 0.0, 10.0),
+ ( 0.0, 0.0)
+ );
+ tx_powers = (15.0, 15.0, 15.0, 15.0);
+
+ model_name = "log_distance";
+ path_loss_exp = 3.5;
+ xg = 0.0;
+};
+```
+
+## Gotchas
+
+### Allowable MAC addresses
+
+The kernel only allows wmediumd to work on the second available hardware
+address, which has bit 6 set in the most significant octet
+(i.e. 42:00:00:xx:xx:xx, not 02:00:00:xx:xx:xx). Set this appropriately
+using 'ip link set address'.
+
+This issue was fixed in commit cd37a90b2a417e5882414e19954eeed174aa4d29
+in Linux, released in kernel 4.1.0.
+
+### Rates
+
+wmediumd's rate table is currently hardcoded to 802.11a OFDM rates.
+Therefore, either operate wmediumd networks in 5 GHz channels, or supply
+a rateset for the BSS with no CCK rates.
+
+### Send-to-self
+
+By default, traffic between local devices in Linux will not go over
+the wire / wireless medium. This is true of vanilla hwsim as well.
+In order to make this happen, you need to either run the hwsim interfaces
+in separate network namespaces, or you need to set up routing rules with
+the hwsim devices at a higher priority than local forwarding.
+
+`tests/test-001.sh` contains an example of the latter setup.
+
+# Example session
+
+The following sequence of commands establishes a two-node mesh using network
+namespaces.
+```
+sudo modprobe -r mac80211_hwsim
+sudo modprobe mac80211_hwsim
+sudo ./wmediumd/wmediumd -c ./tests/2node.cfg
+
+# in window 2
+sudo lxc-unshare -s NETWORK bash
+ps | grep bash # note pid
+
+# in window 1
+sudo iw phy phy2 set netns $pid
+
+sudo ip link set wlan1 down
+sudo iw dev wlan1 set type mp
+sudo ip link set addr 42:00:00:00:00:00 dev wlan1
+sudo ip link set wlan1 up
+sudo ip addr add 10.10.10.1/24 dev wlan1
+sudo iw dev wlan1 set channel 149
+sudo iw dev wlan1 mesh join meshabc
+
+# in window 2
+ip link set lo
+
+sudo ip link set wlan2 down
+sudo iw dev wlan2 set type mp
+sudo ip link set addr 42:00:00:00:01:00 dev wlan2
+sudo ip link set wlan2 up
+sudo ip addr add 10.10.10.2/24 dev wlan2
+sudo iw dev wlan2 set channel 149
+sudo iw dev wlan2 mesh join meshabc
+
+iperf -u -s -i 10 -B 10.10.10.2
+
+# in window 1
+iperf -u -c 10.10.10.2 -b 100M -i 10 -t 120
+```
diff --git a/tests/2node.cfg b/tests/2node.cfg
new file mode 100644
index 0000000..3ce3bea
--- /dev/null
+++ b/tests/2node.cfg
@@ -0,0 +1,5 @@
+ifaces :
+{
+ count = 2;
+ ids = ["42:00:00:00:00:00", "42:00:00:00:01:00" ];
+};
diff --git a/tests/diamond.sh b/tests/diamond.sh
new file mode 100755
index 0000000..b34d512
--- /dev/null
+++ b/tests/diamond.sh
@@ -0,0 +1,117 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+if [[ $# -eq 0 ]]; then
+ freq=2412
+else
+ freq=$1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+iw reg set US
+
+for i in `seq 0 $((num_nodes-1))`; do
+ addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+
+ links = (
+ (0, 1, 10),
+ (0, 2, 20),
+ (0, 3, 0),
+ (1, 2, 30),
+ (1, 3, 10),
+ (2, 3, 20)
+ );
+};
+__EOM
+
+tmux new -s $session -d
+# find out the index of the first window as we can't assume zero-indexing
+first_idx=`tmux list-windows -t $session | head -n1 | cut -d: -f1`
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+ phy=`addr2phy $addr`
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ phys[$i]=$phy
+ devs[$i]=$dev
+
+ ip=${subnet}.$((10 + i))
+
+ # put this phy in own netns and tmux window, and start a mesh node
+ tmux new-window -t $session
+
+ # start netns
+ pidfile=/tmp/netns.pid.$i
+ win=$session:$((first_idx+i+1))
+ tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+ tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+ # wait for netns to exist
+ while [[ ! -e $pidfile ]]; do
+ echo "Waiting for netns $i -- $pidfile"
+ sleep 0.5
+ done
+
+ tmux send-keys -t $session:$first_idx \
+ 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+ # wait for phy to exist in netns
+ while [[ -e /sys/class/ieee80211/$phy ]]; do
+ echo "Waiting for $phy to move to netns..."
+ sleep 0.5
+ done
+
+ # start mesh node
+ tmux send-keys -t $win '. func' C-m
+ tmux send-keys -t $win 'meshup-iw '$dev' diamond '$freq' '$ip C-m
+
+ i=$((i+1))
+done
+
+# start wmediumd
+tmux send-keys -t $session:$first_idx '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.13
+node_idx=$((first_idx + 4))
+tmux send-keys -t $session:$node_idx 'iperf -s' C-m
+
+# enable monitor
+tmux new-window -t $session
+cap_idx=$((first_idx + 5))
+tmux send-keys -t $session:$cap_idx 'ip link set hwsim0 up' C-m
+# capture traffic as normal user (if possible) or root
+CAP_USER=${SUDO_USER:-root}
+tmux send-keys -t $session:$cap_idx "sudo -u $CAP_USER dumpcap -i hwsim0" C-m
+
+node_idx=$((first_idx + 1))
+tmux select-window -t $session:$node_idx
+tmux send-keys -t $session:$node_idx 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:$node_idx 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/diamond_direction.sh b/tests/diamond_direction.sh
new file mode 100755
index 0000000..969d879
--- /dev/null
+++ b/tests/diamond_direction.sh
@@ -0,0 +1,115 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# node 1 and 2 moves along with y axis
+# ping will be lost until switching node 1 to node 2
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+ addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+};
+
+model :
+{
+ type = "path_loss";
+ positions = (
+ (-50.0, 0.0),
+ ( 0.0, 40.0),
+ ( 0.0, -70.0),
+ ( 50.0, 0.0)
+ );
+ directions = (
+ ( 0.0, 0.0),
+ ( 0.0, 10.0),
+ ( 0.0, 10.0),
+ ( 0.0, 0.0)
+ );
+ tx_powers = (15.0, 15.0, 15.0, 15.0);
+
+ model_name = "log_distance";
+ path_loss_exp = 3.5;
+ xg = 0.0;
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+ phy=`addr2phy $addr`
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ phys[$i]=$phy
+ devs[$i]=$dev
+
+ ip=${subnet}.$((10 + i))
+
+ # put this phy in own netns and tmux window, and start a mesh node
+ win=$session:$((i+1)).0
+ tmux new-window -t $session -n $ip
+
+ # start netns
+ pidfile=/tmp/netns.pid.$i
+ tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+ tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+ # wait for netns to exist
+ while [[ ! -e $pidfile ]]; do
+ echo "Waiting for netns $i -- $pidfile"
+ sleep 0.5
+ done
+
+ tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+ # wait for phy to exist in netns
+ while [[ -e /sys/class/ieee80211/$phy ]]; do
+ echo "Waiting for $phy to move to netns..."
+ sleep 0.5
+ done
+
+ # start mesh node
+ tmux send-keys -t $win '. func' C-m
+ tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+ i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 15 10.10.10.13' C-m
+
+tmux attach
diff --git a/tests/diamond_error_prob.sh b/tests/diamond_error_prob.sh
new file mode 100755
index 0000000..9ff6480
--- /dev/null
+++ b/tests/diamond_error_prob.sh
@@ -0,0 +1,106 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+ addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+};
+
+model:
+{
+ type = "prob";
+
+ default_prob = 1.0;
+ links = (
+ (0, 2, 0.000000),
+ (2, 3, 0.000000)
+ );
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+ phy=`addr2phy $addr`
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ phys[$i]=$phy
+ devs[$i]=$dev
+
+ ip=${subnet}.$((10 + i))
+
+ # put this phy in own netns and tmux window, and start a mesh node
+ win=$session:$((i+1)).0
+ tmux new-window -t $session -n $ip
+
+ # start netns
+ pidfile=/tmp/netns.pid.$i
+ tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+ tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+ # wait for netns to exist
+ while [[ ! -e $pidfile ]]; do
+ echo "Waiting for netns $i -- $pidfile"
+ sleep 0.5
+ done
+
+ tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+ # wait for phy to exist in netns
+ while [[ -e /sys/class/ieee80211/$phy ]]; do
+ echo "Waiting for $phy to move to netns..."
+ sleep 0.5
+ done
+
+ # start mesh node
+ tmux send-keys -t $win '. func' C-m
+ tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+ i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.13
+tmux send-keys -t $session:4 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:1 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/diamond_fading.sh b/tests/diamond_fading.sh
new file mode 100755
index 0000000..7f0fca1
--- /dev/null
+++ b/tests/diamond_fading.sh
@@ -0,0 +1,109 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+ addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+};
+
+model:
+{
+ type = "snr"
+ links = (
+ (0, 1, 10),
+ (0, 2, 20),
+ (0, 3, 0),
+ (1, 2, 30),
+ (1, 3, 10),
+ (2, 3, 20)
+ );
+ fading_coefficient = 1;
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+ phy=`addr2phy $addr`
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ phys[$i]=$phy
+ devs[$i]=$dev
+
+ ip=${subnet}.$((10 + i))
+
+ # put this phy in own netns and tmux window, and start a mesh node
+ win=$session:$((i+1)).0
+ tmux new-window -t $session -n $ip
+
+ # start netns
+ pidfile=/tmp/netns.pid.$i
+ tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+ tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+ # wait for netns to exist
+ while [[ ! -e $pidfile ]]; do
+ echo "Waiting for netns $i -- $pidfile"
+ sleep 0.5
+ done
+
+ tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+ # wait for phy to exist in netns
+ while [[ -e /sys/class/ieee80211/$phy ]]; do
+ echo "Waiting for $phy to move to netns..."
+ sleep 0.5
+ done
+
+ # start mesh node
+ tmux send-keys -t $win '. func' C-m
+ tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+ i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.13
+tmux send-keys -t $session:4 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:1 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/diamond_log_distance.sh b/tests/diamond_log_distance.sh
new file mode 100755
index 0000000..fdd78cb
--- /dev/null
+++ b/tests/diamond_log_distance.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+ addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+};
+
+model :
+{
+ type = "path_loss";
+ positions = (
+ (-50.0, 0.0),
+ ( 0.0, 10.0),
+ ( 0.0, 0.0),
+ ( 50.0, 0.0)
+ );
+ tx_powers = (15.0, 15.0, 15.0, 15.0);
+
+ model_name = "log_distance";
+ path_loss_exp = 3.5;
+ xg = 0.0;
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+ phy=`addr2phy $addr`
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ phys[$i]=$phy
+ devs[$i]=$dev
+
+ ip=${subnet}.$((10 + i))
+
+ # put this phy in own netns and tmux window, and start a mesh node
+ win=$session:$((i+1)).0
+ tmux new-window -t $session -n $ip
+
+ # start netns
+ pidfile=/tmp/netns.pid.$i
+ tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+ tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+ # wait for netns to exist
+ while [[ ! -e $pidfile ]]; do
+ echo "Waiting for netns $i -- $pidfile"
+ sleep 0.5
+ done
+
+ tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+ # wait for phy to exist in netns
+ while [[ -e /sys/class/ieee80211/$phy ]]; do
+ echo "Waiting for $phy to move to netns..."
+ sleep 0.5
+ done
+
+ # start mesh node
+ tmux send-keys -t $win '. func' C-m
+ tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+ i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.13
+tmux send-keys -t $session:4 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:1 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/diamond_per_matrix.sh b/tests/diamond_per_matrix.sh
new file mode 100755
index 0000000..bf6f3d5
--- /dev/null
+++ b/tests/diamond_per_matrix.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+ addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00"
+ ];
+
+ links = (
+ (0, 1, 10),
+ (0, 2, 20),
+ (0, 3, 0),
+ (1, 2, 30),
+ (1, 3, 10),
+ (2, 3, 20)
+ );
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+ phy=`addr2phy $addr`
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ phys[$i]=$phy
+ devs[$i]=$dev
+
+ ip=${subnet}.$((10 + i))
+
+ # put this phy in own netns and tmux window, and start a mesh node
+ win=$session:$((i+1)).0
+ tmux new-window -t $session -n $ip
+
+ # start netns
+ pidfile=/tmp/netns.pid.$i
+ tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+ tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+ # wait for netns to exist
+ while [[ ! -e $pidfile ]]; do
+ echo "Waiting for netns $i -- $pidfile"
+ sleep 0.5
+ done
+
+ tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+ # wait for phy to exist in netns
+ while [[ -e /sys/class/ieee80211/$phy ]]; do
+ echo "Waiting for $phy to move to netns..."
+ sleep 0.5
+ done
+
+ # start mesh node
+ tmux send-keys -t $win '. func' C-m
+ tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+ i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg -x signal_table_ieee80211ax' C-m
+
+# start iperf server on 10.10.10.13
+tmux send-keys -t $session:4 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:1 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/func b/tests/func
new file mode 100644
index 0000000..85ddf00
--- /dev/null
+++ b/tests/func
@@ -0,0 +1,119 @@
+function freq_to_chan {
+ local freq=$1
+
+ if [[ $freq -ge 2412 && $freq -le 2472 ]]; then
+ band="11g"
+ chan=$(( ($freq - 2412) / 5 + 1 ))
+ else
+ band="11a"
+ chan=$(( ($freq - 5000) / 5 ))
+ fi
+ echo "$chan $band"
+}
+
+
+function meshup-iw {
+ local if=$1
+ local meshid=$2
+ local freq=$3
+ local ip=$4
+
+ ip link set $if down
+ iw dev $if set type mp
+ ip link set $if up
+ iw dev $if mesh join $meshid freq $freq
+ ip addr add $ip/24 dev $if 2>/dev/null
+}
+
+function meshup-wpas-open {
+ local if=$1
+ local meshid=$2
+ local freq=$3
+ local ip=$4
+
+ ip link set $if down
+ iw dev $if set type mp
+ ip link set $if up
+
+ cat<<EOM > /tmp/wpas-$if.conf
+network={
+ ssid="wmediumd-mesh"
+ mode=5
+ frequency=$freq
+ key_mgmt=NONE
+}
+EOM
+ wpa_supplicant -i $if -c /tmp/wpas-$if.conf &
+ ip addr add $ip/24 dev $if 2>/dev/null
+}
+
+function meshup-wpas {
+ local if=$1;
+ local meshid=$2;
+ local freq=$3;
+ local ip=$4;
+
+ ip link set $if down
+ iw dev $if set type mp
+ ip link set $if up
+
+ cat<<EOM > /tmp/wpas-$if.conf
+network={
+ ssid="wmediumd-mesh-sec"
+ mode=5
+ frequency=$freq
+ key_mgmt=SAE
+ psk="some passphrase"
+}
+EOM
+ wpa_supplicant -i $if -c /tmp/wpas-$if.conf &
+ ip addr add $ip/24 dev $if 2>/dev/null
+}
+
+function meshup-authsae {
+ local if=$1;
+ local meshid=$2;
+ local freq=$3;
+ local ip=$4;
+
+ ip link set $if down
+ iw dev $if set type mp
+ ip link set $if up
+
+ chan_params=$(freq_to_chan $freq)
+ read -ra ch <<< "$chan_params"
+
+ cat<<EOM > /tmp/authsae-$if.conf
+authsae:
+{
+ sae:
+ {
+ debug = 480;
+ password = "some passphrase";
+ group = [19, 26, 21, 25, 20];
+ blacklist = 5;
+ thresh = 5;
+ lifetime = 3600;
+ };
+ meshd:
+ {
+ meshid = "wmediumd-mesh-sec";
+ interface = "wlan0";
+ passive = 0;
+ secured = 1;
+ debug = 1;
+ mediaopt = 1;
+ band = "${ch[1]}";
+ channel = ${ch[0]};
+ };
+};
+EOM
+ meshd-nl80211 -i $if -c /tmp/authsae-$if.conf &
+ ip addr add $ip/24 dev $if 2>/dev/null
+}
+
+function addr2phy {
+ local addr=$1;
+ grep -l $addr /sys/class/ieee80211/phy*/macaddress | \
+ awk -F '/' '{print $(NF-1)}'
+}
diff --git a/tests/interference.sh b/tests/interference.sh
new file mode 100755
index 0000000..9be3c7e
--- /dev/null
+++ b/tests/interference.sh
@@ -0,0 +1,126 @@
+#!/bin/bash
+# 3 mesh nodes in a linear topology
+# 4 additional mesh nodes exists to prevent transmission
+# When enable_interference=true, ping always fails.
+# (This test is not perfect because of random values)
+
+num_nodes=7
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+iw reg set US
+
+for i in `seq 0 $((num_nodes-1))`; do
+ addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+ ids = [
+ "02:00:00:00:00:00",
+ "02:00:00:00:01:00",
+ "02:00:00:00:02:00",
+ "02:00:00:00:03:00",
+ "02:00:00:00:04:00",
+ "02:00:00:00:05:00",
+ "02:00:00:00:06:00"
+ ];
+ enable_interference = true;
+};
+
+model :
+{
+ band = 2;
+ type = "path_loss";
+ positions = (
+ (-69.0, 0.0),
+ ( 0.0, 0.0),
+ ( 69.0, 0.0),
+ (130.0, -2.0),
+ (130.0, -1.0),
+ (130.0, 2.0),
+ (130.0, 1.0)
+ );
+ tx_powers = (15.0, 15.0, 15.0, 11.0, 11.0, 11.0, 11.0);
+
+ model_name = "log_distance";
+ path_loss_exp = 3.5;
+ xg = 0.0;
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+ phy=`addr2phy $addr`
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ phys[$i]=$phy
+ devs[$i]=$dev
+
+ ip=${subnet}.$((10 + i))
+
+ # put this phy in own netns and tmux window, and start a mesh node
+ win=$session:$((i+1)).0
+ tmux new-window -t $session -n $ip
+
+ # start netns
+ pidfile=/tmp/netns.pid.$i
+ tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+ tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+ # wait for netns to exist
+ while [[ ! -e $pidfile ]]; do
+ echo "Waiting for netns $i -- $pidfile"
+ sleep 0.5
+ done
+
+ tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+ # wait for phy to exist in netns
+ while [[ -e /sys/class/ieee80211/$phy ]]; do
+ echo "Waiting for $phy to move to netns..."
+ sleep 0.5
+ done
+
+ # start mesh node
+ tmux send-keys -t $win '. func' C-m
+ tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+ i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.14
+tmux send-keys -t $session:5 'iperf -s' C-m
+# start iperf server on 10.10.10.16
+tmux send-keys -t $session:7 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux send-keys -t $session:4 'iperf -u -b 100M -c 10.10.10.14 -t 10' C-m
+tmux send-keys -t $session:6 'iperf -u -b 100M -c 10.10.10.16 -t 10' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'sleep 2; ping -c 5 -W 1 10.10.10.12' C-m
+
+tmux attach
diff --git a/tests/n-linear-mesh.sh b/tests/n-linear-mesh.sh
new file mode 100755
index 0000000..736d229
--- /dev/null
+++ b/tests/n-linear-mesh.sh
@@ -0,0 +1,102 @@
+#!/bin/bash
+# run multiple mesh nodes in a linear topology
+# each node is in the same coverage area, so
+# total throughput is divided by n.
+
+num_nodes=${1:-4}
+daemon=${2:-iw}
+
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+ addrs[$i]=`printf $macfmt $i`
+done
+
+echo "ifaces: { count = $num_nodes; ids = [" > linear.cfg
+for addr in "${addrs[@]}"; do
+ echo -n '"'$addr'"' >> linear.cfg
+ if [[ $addr != ${addrs[$((num_nodes-1))]} ]]; then
+ echo ", " >> linear.cfg
+ fi
+done
+echo "]; }" >> linear.cfg
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+ phy=`addr2phy $addr`
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ phys[$i]=$phy
+ devs[$i]=$dev
+
+ ip=${subnet}.$((10 + i))
+
+ # put this phy in own netns and tmux window, and start a mesh node
+ win=$session:$((i+1)).0
+ tmux new-window -t $session -n $ip
+
+ # start netns
+ pidfile=/tmp/netns.pid.$i
+ tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+ tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+ # wait for netns to exist
+ while [[ ! -e $pidfile ]]; do
+ echo "Waiting for netns $i -- $pidfile"
+ sleep 0.5
+ done
+
+ tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+ # wait for phy to exist in netns
+ while [[ -e /sys/class/ieee80211/$phy ]]; do
+ echo "Waiting for $phy to move to netns..."
+ sleep 0.5
+ done
+
+ # start mesh node
+ tmux send-keys -t $win '. func' C-m
+ tmux send-keys -t $win 'meshup-'$daemon ' ' $dev' linear 2412 '$ip C-m
+
+ i=$((i+1))
+done
+winct=$i
+
+# wait a few beacon periods for everyone to discover each other
+sleep 3
+
+# force a linear topology
+for i in `seq 0 $((${#addrs[@]} - 1))`; do
+ win=$session:$((i+1)).0
+ addr=${addrs[$i]}
+ dev=${devs[$i]}
+
+ for j in `seq 0 $((${#addrs[@]} - 1))`; do
+ oaddr=${addrs[$j]}
+ if [[ $j -lt $((i-1)) || $j -gt $((i+1)) ]]; then
+ tmux send-keys -t $win 'iw dev '$dev' station set '$oaddr' plink_action block' C-m
+ fi
+ done
+done
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c linear.cfg' C-m
+
+tmux attach
diff --git a/tests/signal_table_ieee80211ax b/tests/signal_table_ieee80211ax
new file mode 100644
index 0000000..8c107ad
--- /dev/null
+++ b/tests/signal_table_ieee80211ax
@@ -0,0 +1,44 @@
+# RSSI vs MCS vs PER table, based on 11-14-0571-12-00ax-evaluation-methodology.docx assuming No is -91dBm.
+# bitrate 1Mbps 2Mbps 5.5Mbps 11Mbps 6Mbps 9Mbps 12Mbps 18Mbps 24Mbps 36Mbps 48Mbps 54Mbps
+# RSSI [dBm] 0 1 2 3 4 5 6 7 8 9 10 11
+-100 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-99 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-98 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-97 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-96 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-95 0.9995 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-94 0.529 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-93 0.0427 0.9194 0.9995 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-92 0.0014 0.1765 0.529 1.00E+00 0.9995 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-91 0.00E+00 0.0086 0.0427 1.00E+00 0.529 0.9995 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-90 0.00E+00 0.0001 0.0014 0.9995 0.0427 0.529 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-89 0.00E+00 0.00E+00 0.00E+00 0.529 0.0014 0.0427 0.9997 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-88 0.00E+00 0.00E+00 0.00E+00 0.0427 0.00E+00 0.0014 0.5462 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-87 0.00E+00 0.00E+00 0.00E+00 0.0014 0.00E+00 0.00E+00 0.0439 1.00E+00 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-86 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.0016 0.9597 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-85 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.2239 1.00E+00 1.00E+00 1.00E+00 1.00E+00
+-84 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.0117 0.8908 1.00E+00 1.00E+00 1.00E+00
+-83 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.2343 1.00E+00 1.00E+00 1.00E+00
+-82 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.024 1.00E+00 1.00E+00 1.00E+00
+-81 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 1.00E+00 1.00E+00 1.00E+00
+-80 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.979 1.00E+00 1.00E+00
+-79 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.3536 1.00E+00 1.00E+00
+-78 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.0356 1.00E+00 1.00E+00
+-77 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.0018 1.00E+00 1.00E+00
+-76 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.9496 1.00E+00
+-75 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.379 0.9981
+-74 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.061 0.6465
+-73 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.0057 0.1343
+-72 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.0004 0.0145
+-71 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.0007
+-70 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-69 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-68 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-67 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-66 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-65 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-64 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-63 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-62 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-61 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
+-60 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00 0.00E+00
diff --git a/tests/test-001.sh b/tests/test-001.sh
new file mode 100755
index 0000000..619eb99
--- /dev/null
+++ b/tests/test-001.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+SUBNET=10.10.10
+NUM_PHYS=2
+
+if [[ $UID -ne 0 ]]; then
+ echo "Sorry, run me as root."
+ exit 1
+fi
+
+function cleanup() {
+ echo "Cleaning up!"
+ # restore default routing rules
+ echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore
+ for i in `seq 0 $NUM_PHYS`; do
+ prio=$((i+10))
+ prio2=$((256+prio))
+ tbl=$prio2
+
+ ip rule del priority $prio2 2> /dev/null
+ ip rule del priority $prio 2> /dev/null
+ ip route flush table $tbl 2> /dev/null
+ done
+ ip rule del priority 1000
+ ip rule add priority 0 table local
+
+ # kill whatever we started
+ killall wmediumd
+}
+
+trap 'cleanup' INT TERM EXIT
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$NUM_PHYS
+
+# routing-based send-to-self (Patrick McHardy)
+# lower priority of kernel local table
+ip rule add priority 1000 lookup local
+ip rule del priority 0 &>/dev/null
+
+# only arp reply for self
+echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
+
+i=0
+# Assume most recently modified phys are hwsim phys (hence the ls -t)
+for phy in `ls -t /sys/class/ieee80211 | head -$NUM_PHYS`; do
+ # The usual stuff
+ dev=`ls /sys/class/ieee80211/$phy/device/net`
+ ip=${SUBNET}.$((10 + i))
+
+ ip link set $dev down
+ ip link set address 42:00:00:00:0${i}:00 dev $dev
+ iw dev $dev set type mesh
+ iw dev $dev set channel 36
+ ip link set $dev up
+ iw dev $dev mesh join meshtest
+
+ ip addr flush dev $dev
+ ip addr add $ip/24 dev $dev
+
+ # set up local delivery
+ prio=$((i+10))
+ prio2=$((256+prio))
+ tbl=$prio2
+
+ # incoming traffic to us delivered via local table
+ echo 1 > /proc/sys/net/ipv4/conf/$dev/accept_local
+ ip rule del priority $prio 2> /dev/null
+ ip rule add priority $prio iif $dev lookup local
+
+ # outgoing frames with our ip will be generated on our interface
+ # and go over the wire.
+ ip rule del priority $prio2 2> /dev/null
+ ip rule add priority $prio2 from $ip table $tbl
+ ip route flush table $tbl 2> /dev/null
+ ip route add default dev $dev table $tbl
+
+ i=$((i+1))
+done
+
+# enable wmediumd
+../wmediumd/wmediumd -c 2node.cfg > wmediumd.log &
+
+# see if we can establish a mesh path
+ping -i 1 -c 5 -I ${SUBNET}.10 ${SUBNET}.11 || { echo FAIL; exit 1; }
+
+echo PASS
diff --git a/wmediumd/Makefile b/wmediumd/Makefile
new file mode 100644
index 0000000..fa94eb0
--- /dev/null
+++ b/wmediumd/Makefile
@@ -0,0 +1,71 @@
+VERSION_STR="\"0.3.1\""
+
+# Look for libnl libraries
+PKG_CONFIG ?= pkg-config
+NL2FOUND := $(shell $(PKG_CONFIG) --atleast-version=2 libnl-2.0 && echo Y)
+NL3FOUND := $(shell $(PKG_CONFIG) --atleast-version=3 libnl-3.0 && echo Y)
+NL31FOUND := $(shell $(PKG_CONFIG) --exact-version=3.1 libnl-3.1 && echo Y)
+NL3xFOUND := $(shell $(PKG_CONFIG) --atleast-version=3.2 libnl-3.0 && echo Y)
+
+CFLAGS += -g -Wall -Wextra -Wno-unused-parameter -O2 -Iinc
+CFLAGS += -MMD -MP
+CFLAGS += -Wno-format-zero-length
+LDFLAGS += -lm
+
+ifeq ($(NL2FOUND),Y)
+CFLAGS += -DCONFIG_LIBNL20
+LDFLAGS += -lnl-genl
+NLLIBNAME = libnl-2.0
+endif
+
+ifeq ($(NL3xFOUND),Y)
+# libnl 3.2 might be found as 3.2 and 3.0
+NL3FOUND = N
+CFLAGS += -DCONFIG_LIBNL30
+LDFLAGS += -lnl-genl-3
+NLLIBNAME = libnl-3.0
+endif
+
+ifeq ($(NL3FOUND),Y)
+CFLAGS += -DCONFIG_LIBNL30
+LDFLAGS += -lnl-genl
+NLLIBNAME = libnl-3.0
+endif
+
+# nl-3.1 has a broken libnl-gnl-3.1.pc file
+# as show by pkg-config --debug --libs --cflags --exact-version=3.1 libnl-genl-3.1;echo $?
+ifeq ($(NL31FOUND),Y)
+CFLAGS += -DCONFIG_LIBNL30
+LDFLAGS += -lnl-genl
+NLLIBNAME = libnl-3.1
+endif
+
+ifeq ($(NLLIBNAME),)
+$(error Cannot find development files for any supported version of libnl)
+endif
+
+LDFLAGS += $(shell $(PKG_CONFIG) --libs $(NLLIBNAME))
+CFLAGS += $(shell $(PKG_CONFIG) --cflags $(NLLIBNAME))
+
+CFLAGS+=-DVERSION_STR=$(VERSION_STR)
+LDFLAGS+=-lconfig
+OBJECTS=wmediumd.o config.o per.o
+OBJECTS += lib/loop.o lib/sched.o lib/schedctrl.o
+OBJECTS += lib/uds.o lib/vhost.o lib/wallclock.o
+
+ifeq ($(SANITIZE),1)
+CFLAGS += -fsanitize=undefined,address
+# apparently these have to come first for some reason
+override LDFLAGS := -lasan -lubsan -lstdc++ $(LDFLAGS)
+endif
+
+all: wmediumd
+
+wmediumd: $(OBJECTS)
+ $(CC) -o $@ $(OBJECTS) $(LDFLAGS)
+
+DEPS := $(patsubst %.o,%.d,$(OBJECTS))
+clean:
+ rm -f $(OBJECTS) $(DEPS) wmediumd
+
+-include *.d lib/*.d
diff --git a/wmediumd/api.h b/wmediumd/api.h
new file mode 100644
index 0000000..24e9548
--- /dev/null
+++ b/wmediumd/api.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _WMEDIUMD_API_H
+#define _WMEDIUMD_API_H
+#include <stdint.h>
+
+enum wmediumd_message {
+ /* invalid message */
+ WMEDIUMD_MSG_INVALID,
+
+ /* ACK, returned for each message for synchronisation */
+ WMEDIUMD_MSG_ACK,
+
+ /*
+ * Register/unregister for frames, this may be a pure control
+ * socket which doesn't want to see frames.
+ */
+ WMEDIUMD_MSG_REGISTER,
+ WMEDIUMD_MSG_UNREGISTER,
+
+ /*
+ * netlink message, the data is the entire netlink message,
+ * this is used to communicate frame TX/RX in the familiar
+ * netlink format, to avoid having a special format
+ */
+ WMEDIUMD_MSG_NETLINK,
+
+ /* control message, see struct wmediumd_message_control */
+ WMEDIUMD_MSG_SET_CONTROL,
+
+ /*
+ * Indicates TX start if WMEDIUMD_RX_CTL_NOTIFY_TX_START is set,
+ * with struct wmediumd_tx_start as the payload.
+ */
+ WMEDIUMD_MSG_TX_START,
+};
+
+struct wmediumd_message_header {
+ /* type of message - see enum wmediumd_message */
+ uint32_t type;
+ /* data length */
+ uint32_t data_len;
+
+ /* variable-length data according to the message type */
+ uint8_t data[];
+};
+
+enum wmediumd_control_flags {
+ WMEDIUMD_CTL_NOTIFY_TX_START = 1 << 0,
+ WMEDIUMD_CTL_RX_ALL_FRAMES = 1 << 1,
+};
+
+struct wmediumd_message_control {
+ uint32_t flags;
+
+ /*
+ * For compatibility, wmediumd is meant to understand shorter
+ * (and ignore unknown parts of longer) control messages than
+ * what's sent to it, so always take care to have defaults as
+ * zero since that's what it assumes.
+ */
+};
+
+struct wmediumd_tx_start {
+ /*
+ * The cookie is set only when telling the sender, otherwise
+ * it's set to 0.
+ */
+ uint64_t cookie;
+ uint32_t freq;
+ uint32_t reserved[3];
+};
+
+#endif /* _WMEDIUMD_API_H */
diff --git a/wmediumd/config.c b/wmediumd/config.c
new file mode 100644
index 0000000..c9a6f5c
--- /dev/null
+++ b/wmediumd/config.c
@@ -0,0 +1,657 @@
+/*
+ * wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ * Copyright (c) 2011 cozybit Inc.
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * Author: Javier Lopez <jlopex@cozybit.com>
+ * Javier Cardona <javier@cozybit.com>
+ *
+ * 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.
+ */
+
+#include <sys/timerfd.h>
+#include <libconfig.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
+
+#include "wmediumd.h"
+
+static void string_to_mac_address(const char *str, u8 *addr)
+{
+ int a[ETH_ALEN];
+
+ sscanf(str, "%x:%x:%x:%x:%x:%x",
+ &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]);
+
+ addr[0] = (u8) a[0];
+ addr[1] = (u8) a[1];
+ addr[2] = (u8) a[2];
+ addr[3] = (u8) a[3];
+ addr[4] = (u8) a[4];
+ addr[5] = (u8) a[5];
+}
+
+static int get_link_snr_default(struct wmediumd *ctx, struct station *sender,
+ struct station *receiver)
+{
+ return SNR_DEFAULT;
+}
+
+static int get_link_snr_from_snr_matrix(struct wmediumd *ctx,
+ struct station *sender,
+ struct station *receiver)
+{
+ return ctx->snr_matrix[sender->index * ctx->num_stas + receiver->index];
+}
+
+static double _get_error_prob_from_snr(struct wmediumd *ctx, double snr,
+ unsigned int rate_idx, u32 freq,
+ int frame_len,
+ struct station *src, struct station *dst)
+{
+ return get_error_prob_from_snr(snr, rate_idx, freq, frame_len);
+}
+
+static double get_error_prob_from_matrix(struct wmediumd *ctx, double snr,
+ unsigned int rate_idx, u32 freq,
+ int frame_len, struct station *src,
+ struct station *dst)
+{
+ if (dst == NULL) // dst is multicast. returned value will not be used.
+ return 0.0;
+
+ return ctx->error_prob_matrix[ctx->num_stas * src->index + dst->index];
+}
+
+int use_fixed_random_value(struct wmediumd *ctx)
+{
+ return ctx->error_prob_matrix != NULL;
+}
+
+#define FREQ_1CH (2.412e9) // [Hz]
+#define SPEED_LIGHT (2.99792458e8) // [meter/sec]
+/*
+ * Calculate path loss based on a free-space path loss
+ *
+ * This function returns path loss [dBm].
+ */
+static int calc_path_loss_free_space(void *model_param,
+ struct station *dst, struct station *src)
+{
+ double PL, d;
+
+ d = sqrt((src->x - dst->x) * (src->x - dst->x) +
+ (src->y - dst->y) * (src->y - dst->y));
+
+ /*
+ * Calculate PL0 with Free-space path loss in decibels
+ *
+ * 20 * log10 * (4 * M_PI * d * f / c)
+ * d: distance [meter]
+ * f: frequency [Hz]
+ * c: speed of light in a vacuum [meter/second]
+ *
+ * https://en.wikipedia.org/wiki/Free-space_path_loss
+ */
+ PL = 20.0 * log10(4.0 * M_PI * d * FREQ_1CH / SPEED_LIGHT);
+ return PL;
+}
+/*
+ * Calculate path loss based on a log distance model
+ *
+ * This function returns path loss [dBm].
+ */
+static int calc_path_loss_log_distance(void *model_param,
+ struct station *dst, struct station *src)
+{
+ struct log_distance_model_param *param;
+ double PL, PL0, d;
+
+ param = model_param;
+
+ d = sqrt((src->x - dst->x) * (src->x - dst->x) +
+ (src->y - dst->y) * (src->y - dst->y));
+
+ /*
+ * Calculate PL0 with Free-space path loss in decibels
+ *
+ * 20 * log10 * (4 * M_PI * d * f / c)
+ * d: distance [meter]
+ * f: frequency [Hz]
+ * c: speed of light in a vacuum [meter/second]
+ *
+ * https://en.wikipedia.org/wiki/Free-space_path_loss
+ */
+ PL0 = 20.0 * log10(4.0 * M_PI * 1.0 * FREQ_1CH / SPEED_LIGHT);
+
+ /*
+ * Calculate signal strength with Log-distance path loss model
+ * https://en.wikipedia.org/wiki/Log-distance_path_loss_model
+ */
+ PL = PL0 + 10.0 * param->path_loss_exponent * log10(d) + param->Xg;
+
+ return PL;
+}
+/*
+ * Calculate path loss based on a itu model
+ *
+ * This function returns path loss [dBm].
+ */
+static int calc_path_loss_itu(void *model_param,
+ struct station *dst, struct station *src)
+{
+ struct itu_model_param *param;
+ double PL, d;
+ int N=28;
+
+ param = model_param;
+
+ d = sqrt((src->x - dst->x) * (src->x - dst->x) +
+ (src->y - dst->y) * (src->y - dst->y));
+
+ if (d>16)
+ N=38;
+ /*
+ * Calculate signal strength with ITU path loss model
+ * Power Loss Coefficient Based on the Paper
+ * Site-Specific Validation of ITU Indoor Path Loss Model at 2.4 GHz
+ * from Theofilos Chrysikos, Giannis Georgopoulos and Stavros Kotsopoulos
+ * LF: floor penetration loss factor
+ * nFLOORS: number of floors
+ */
+ PL = 20.0 * log10(FREQ_1CH) + N * log10(d) + param->LF * param->nFLOORS - 28;
+ return PL;
+}
+
+static void recalc_path_loss(struct wmediumd *ctx)
+{
+ int start, end, path_loss;
+
+ for (start = 0; start < ctx->num_stas; start++) {
+ for (end = 0; end < ctx->num_stas; end++) {
+ if (start == end)
+ continue;
+
+ path_loss = ctx->calc_path_loss(ctx->path_loss_param,
+ ctx->sta_array[end], ctx->sta_array[start]);
+ ctx->snr_matrix[ctx->num_stas * start + end] =
+ ctx->sta_array[start]->tx_power - path_loss -
+ NOISE_LEVEL;
+ }
+ }
+}
+
+static void move_stations_to_direction(struct usfstl_job *job)
+{
+ struct wmediumd *ctx = job->data;
+ struct station *station;
+
+ list_for_each_entry(station, &ctx->stations, list) {
+ station->x += station->dir_x;
+ station->y += station->dir_y;
+ }
+ recalc_path_loss(ctx);
+
+ job->start += MOVE_INTERVAL * 1000000;
+ usfstl_sched_add_job(&scheduler, job);
+}
+
+static int parse_path_loss(struct wmediumd *ctx, config_t *cf)
+{
+ struct station *station;
+ const config_setting_t *positions, *position;
+ const config_setting_t *directions, *direction;
+ const config_setting_t *tx_powers, *model;
+ const char *path_loss_model_name;
+
+ positions = config_lookup(cf, "model.positions");
+ if (!positions) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "No positions found in model\n");
+ return -EINVAL;
+ }
+ if (config_setting_length(positions) != ctx->num_stas) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Specify %d positions\n", ctx->num_stas);
+ return -EINVAL;
+ }
+
+ directions = config_lookup(cf, "model.directions");
+ if (directions) {
+ if (config_setting_length(directions) != ctx->num_stas) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Specify %d directions\n", ctx->num_stas);
+ return -EINVAL;
+ }
+ ctx->move_job.start = MOVE_INTERVAL * 1000000;
+ ctx->move_job.name = "move";
+ ctx->move_job.data = ctx;
+ ctx->move_job.callback = move_stations_to_direction;
+ usfstl_sched_add_job(&scheduler, &ctx->move_job);
+ }
+
+ tx_powers = config_lookup(cf, "model.tx_powers");
+ if (!tx_powers) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "No tx_powers found in model\n");
+ return -EINVAL;
+ }
+ if (config_setting_length(tx_powers) != ctx->num_stas) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Specify %d tx_powers\n", ctx->num_stas);
+ return -EINVAL;
+ }
+
+ model = config_lookup(cf, "model");
+ if (config_setting_lookup_string(model, "model_name",
+ &path_loss_model_name) != CONFIG_TRUE) {
+ w_flogf(ctx, LOG_ERR, stderr, "Specify model_name\n");
+ return -EINVAL;
+ }
+ if (strncmp(path_loss_model_name, "log_distance",
+ sizeof("log_distance")) == 0) {
+ struct log_distance_model_param *param;
+
+ ctx->calc_path_loss = calc_path_loss_log_distance;
+
+ param = malloc(sizeof(*param));
+ if (!param) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Out of memory(path_loss_param)\n");
+ return -EINVAL;
+ }
+
+ if (config_setting_lookup_float(model, "path_loss_exp",
+ ¶m->path_loss_exponent) != CONFIG_TRUE) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "path_loss_exponent not found\n");
+ return -EINVAL;
+ }
+
+ if (config_setting_lookup_float(model, "xg",
+ ¶m->Xg) != CONFIG_TRUE) {
+ w_flogf(ctx, LOG_ERR, stderr, "xg not found\n");
+ return -EINVAL;
+ }
+ ctx->path_loss_param = param;
+ }
+ else if (strncmp(path_loss_model_name, "free_space",
+ sizeof("free_space")) == 0) {
+ struct log_distance_model_param *param;
+ ctx->calc_path_loss = calc_path_loss_free_space;
+ param = malloc(sizeof(*param));
+ if (!param) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Out of memory(path_loss_param)\n");
+ return -EINVAL;
+ }
+ }
+ else if (strncmp(path_loss_model_name, "itu",
+ sizeof("itu")) == 0) {
+ struct itu_model_param *param;
+ ctx->calc_path_loss = calc_path_loss_itu;
+ param = malloc(sizeof(*param));
+ if (!param) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Out of memory(path_loss_param)\n");
+ return -EINVAL;
+ }
+
+ if (config_setting_lookup_int(model, "nFLOORS",
+ ¶m->nFLOORS) != CONFIG_TRUE) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "nFLOORS not found\n");
+ return -EINVAL;
+ }
+
+ if (config_setting_lookup_int(model, "LF",
+ ¶m->LF) != CONFIG_TRUE) {
+ w_flogf(ctx, LOG_ERR, stderr, "LF not found\n");
+ return -EINVAL;
+ }
+ ctx->path_loss_param = param;
+ }
+ else {
+ w_flogf(ctx, LOG_ERR, stderr, "No path loss model found\n");
+ return -EINVAL;
+ }
+
+ list_for_each_entry(station, &ctx->stations, list) {
+ position = config_setting_get_elem(positions, station->index);
+ if (config_setting_length(position) != 2) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Invalid position: expected (double,double)\n");
+ return -EINVAL;
+ }
+ station->x = config_setting_get_float_elem(position, 0);
+ station->y = config_setting_get_float_elem(position, 1);
+
+ if (directions) {
+ direction = config_setting_get_elem(directions,
+ station->index);
+ if (config_setting_length(direction) != 2) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Invalid direction: expected (double,double)\n");
+ return -EINVAL;
+ }
+ station->dir_x = config_setting_get_float_elem(
+ direction, 0);
+ station->dir_y = config_setting_get_float_elem(
+ direction, 1);
+ }
+
+ station->tx_power = config_setting_get_float_elem(
+ tx_powers, station->index);
+ }
+
+ recalc_path_loss(ctx);
+
+ return 0;
+}
+
+static double pseudo_normal_distribution(void)
+{
+ int i;
+ double normal = -6.0;
+
+ for (i = 0; i < 12; i++)
+ normal += drand48();
+
+ return normal;
+}
+
+static int _get_fading_signal(struct wmediumd *ctx)
+{
+ return ctx->fading_coefficient * pseudo_normal_distribution();
+}
+
+static int get_no_fading_signal(struct wmediumd *ctx)
+{
+ return 0;
+}
+
+/* Existing link is from from -> to; copy to other dir */
+static void mirror_link(struct wmediumd *ctx, int from, int to)
+{
+ ctx->snr_matrix[ctx->num_stas * to + from] =
+ ctx->snr_matrix[ctx->num_stas * from + to];
+
+ if (ctx->error_prob_matrix) {
+ ctx->error_prob_matrix[ctx->num_stas * to + from] =
+ ctx->error_prob_matrix[ctx->num_stas * from + to];
+ }
+}
+
+/*
+ * Loads a config file into memory
+ */
+int load_config(struct wmediumd *ctx, const char *file, const char *per_file)
+{
+ config_t cfg, *cf;
+ const config_setting_t *ids, *links, *model_type;
+ const config_setting_t *error_probs = NULL, *error_prob;
+ const config_setting_t *enable_interference;
+ const config_setting_t *fading_coefficient, *default_prob;
+ int count_ids, i, j;
+ int start, end, snr;
+ struct station *station;
+ const char *model_type_str;
+ float default_prob_value = 0.0;
+ bool *link_map = NULL;
+
+ /*initialize the config file*/
+ cf = &cfg;
+ config_init(cf);
+
+ /*read the file*/
+ if (!config_read_file(cf, file)) {
+ w_logf(ctx, LOG_ERR, "Error loading file %s at line:%d, reason: %s\n",
+ file,
+ config_error_line(cf),
+ config_error_text(cf));
+ config_destroy(cf);
+ return -EIO;
+ }
+
+ ids = config_lookup(cf, "ifaces.ids");
+ if (!ids) {
+ w_logf(ctx, LOG_ERR, "ids not found in config file\n");
+ return -EIO;
+ }
+ count_ids = config_setting_length(ids);
+
+ w_logf(ctx, LOG_NOTICE, "#_if = %d\n", count_ids);
+
+ /* Fill the mac_addr */
+ ctx->sta_array = calloc(count_ids, sizeof(struct station *));
+ if (!ctx->sta_array) {
+ w_flogf(ctx, LOG_ERR, stderr, "Out of memory(sta_array)!\n");
+ return -ENOMEM;
+ }
+ for (i = 0; i < count_ids; i++) {
+ u8 addr[ETH_ALEN];
+ const char *str = config_setting_get_string_elem(ids, i);
+ string_to_mac_address(str, addr);
+
+ station = calloc(1, sizeof(*station));
+ if (!station) {
+ w_flogf(ctx, LOG_ERR, stderr, "Out of memory!\n");
+ return -ENOMEM;
+ }
+ station->index = i;
+ memcpy(station->addr, addr, ETH_ALEN);
+ memcpy(station->hwaddr, addr, ETH_ALEN);
+ station->tx_power = SNR_DEFAULT;
+ station_init_queues(station);
+ list_add_tail(&station->list, &ctx->stations);
+ ctx->sta_array[i] = station;
+
+ w_logf(ctx, LOG_NOTICE, "Added station %d: " MAC_FMT "\n", i, MAC_ARGS(addr));
+ }
+ ctx->num_stas = count_ids;
+
+ enable_interference = config_lookup(cf, "ifaces.enable_interference");
+ if (enable_interference &&
+ config_setting_get_bool(enable_interference)) {
+ ctx->intf = calloc(ctx->num_stas * ctx->num_stas,
+ sizeof(struct intf_info));
+ if (!ctx->intf) {
+ w_flogf(ctx, LOG_ERR, stderr, "Out of memory(intf)\n");
+ return -ENOMEM;
+ }
+ for (i = 0; i < ctx->num_stas; i++)
+ for (j = 0; j < ctx->num_stas; j++)
+ ctx->intf[i * ctx->num_stas + j].signal = -200;
+ } else {
+ ctx->intf = NULL;
+ }
+
+ fading_coefficient =
+ config_lookup(cf, "model.fading_coefficient");
+ if (fading_coefficient &&
+ config_setting_get_int(fading_coefficient) > 0) {
+ ctx->get_fading_signal = _get_fading_signal;
+ ctx->fading_coefficient =
+ config_setting_get_int(fading_coefficient);
+ } else {
+ ctx->get_fading_signal = get_no_fading_signal;
+ ctx->fading_coefficient = 0;
+ }
+
+ /* create link quality matrix */
+ ctx->snr_matrix = calloc(sizeof(int), count_ids * count_ids);
+ if (!ctx->snr_matrix) {
+ w_flogf(ctx, LOG_ERR, stderr, "Out of memory!\n");
+ return -ENOMEM;
+ }
+ /* set default snrs */
+ for (i = 0; i < count_ids * count_ids; i++)
+ ctx->snr_matrix[i] = SNR_DEFAULT;
+
+ links = config_lookup(cf, "ifaces.links");
+ if (!links) {
+ model_type = config_lookup(cf, "model.type");
+ if (model_type) {
+ model_type_str = config_setting_get_string(model_type);
+ if (memcmp("snr", model_type_str, strlen("snr")) == 0) {
+ links = config_lookup(cf, "model.links");
+ } else if (memcmp("prob", model_type_str,
+ strlen("prob")) == 0) {
+ error_probs = config_lookup(cf, "model.links");
+ } else if (memcmp("path_loss", model_type_str,
+ strlen("path_loss")) == 0) {
+ /* calculate signal from positions */
+ if (parse_path_loss(ctx, cf))
+ goto fail;
+ }
+ }
+ }
+
+ if (per_file && error_probs) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "per_file and error_probs could not be used at the same time\n");
+ goto fail;
+ }
+
+ ctx->get_link_snr = get_link_snr_from_snr_matrix;
+ ctx->get_error_prob = _get_error_prob_from_snr;
+
+ ctx->per_matrix = NULL;
+ ctx->per_matrix_row_num = 0;
+ if (per_file && read_per_file(ctx, per_file))
+ goto fail;
+
+ ctx->error_prob_matrix = NULL;
+ if (error_probs) {
+ ctx->error_prob_matrix = calloc(sizeof(double),
+ count_ids * count_ids);
+ if (!ctx->error_prob_matrix) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Out of memory(error_prob_matrix)\n");
+ goto fail;
+ }
+
+ ctx->get_link_snr = get_link_snr_default;
+ ctx->get_error_prob = get_error_prob_from_matrix;
+
+ default_prob = config_lookup(cf, "model.default_prob");
+ if (default_prob) {
+ default_prob_value = config_setting_get_float(
+ default_prob);
+ if (default_prob_value < 0.0 ||
+ default_prob_value > 1.0) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "model.default_prob should be in [0.0, 1.0]\n");
+ goto fail;
+ }
+ }
+ }
+
+ link_map = calloc(sizeof(bool), count_ids * count_ids);
+ if (!link_map) {
+ w_flogf(ctx, LOG_ERR, stderr, "Out of memory\n");
+ goto fail;
+ }
+
+ /* read snr values */
+ for (i = 0; links && i < config_setting_length(links); i++) {
+ config_setting_t *link;
+
+ link = config_setting_get_elem(links, i);
+ if (config_setting_length(link) != 3) {
+ w_flogf(ctx, LOG_ERR, stderr, "Invalid link: expected (int,int,int)\n");
+ goto fail;
+ }
+ start = config_setting_get_int_elem(link, 0);
+ end = config_setting_get_int_elem(link, 1);
+ snr = config_setting_get_int_elem(link, 2);
+
+ if (start < 0 || start >= ctx->num_stas ||
+ end < 0 || end >= ctx->num_stas) {
+ w_flogf(ctx, LOG_ERR, stderr, "Invalid link [%d,%d,%d]: index out of range\n",
+ start, end, snr);
+ goto fail;
+ }
+ ctx->snr_matrix[ctx->num_stas * start + end] = snr;
+ link_map[ctx->num_stas * start + end] = true;
+ }
+
+ /* initialize with default_prob */
+ for (start = 0; error_probs && start < ctx->num_stas; start++)
+ for (end = start + 1; end < ctx->num_stas; end++) {
+ ctx->error_prob_matrix[ctx->num_stas *
+ start + end] =
+ ctx->error_prob_matrix[ctx->num_stas *
+ end + start] = default_prob_value;
+ }
+
+ /* read error probabilities */
+ for (i = 0; error_probs &&
+ i < config_setting_length(error_probs); i++) {
+ float error_prob_value;
+
+ error_prob = config_setting_get_elem(error_probs, i);
+ if (config_setting_length(error_prob) != 3) {
+ w_flogf(ctx, LOG_ERR, stderr, "Invalid error probability: expected (int,int,float)\n");
+ goto fail;
+ }
+
+ start = config_setting_get_int_elem(error_prob, 0);
+ end = config_setting_get_int_elem(error_prob, 1);
+ error_prob_value = config_setting_get_float_elem(error_prob, 2);
+
+ if (start < 0 || start >= ctx->num_stas ||
+ end < 0 || end >= ctx->num_stas ||
+ error_prob_value < 0.0 || error_prob_value > 1.0) {
+ w_flogf(ctx, LOG_ERR, stderr, "Invalid error probability [%d,%d,%f]\n",
+ start, end, error_prob_value);
+ goto fail;
+ }
+
+ ctx->error_prob_matrix[ctx->num_stas * start + end] =
+ error_prob_value;
+ link_map[ctx->num_stas * start + end] = true;
+ }
+
+ /*
+ * If any links are specified in only one direction, mirror them,
+ * making them symmetric. If specified in both directions they
+ * can be asymmetric.
+ */
+ for (i = 0; i < ctx->num_stas; i++) {
+ for (j = 0; j < ctx->num_stas; j++) {
+ if (i == j)
+ continue;
+
+ if (link_map[ctx->num_stas * i + j] &&
+ !link_map[ctx->num_stas * j + i]) {
+ mirror_link(ctx, i, j);
+ }
+ }
+ }
+
+ free(link_map);
+ config_destroy(cf);
+ return 0;
+
+fail:
+ free(ctx->snr_matrix);
+ free(ctx->error_prob_matrix);
+ config_destroy(cf);
+ return -EINVAL;
+}
diff --git a/wmediumd/config.h b/wmediumd/config.h
new file mode 100644
index 0000000..f9bcdf4
--- /dev/null
+++ b/wmediumd/config.h
@@ -0,0 +1,30 @@
+/*
+ * wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ * Copyright (c) 2011 cozybit Inc.
+ *
+ * Author: Javier Lopez <jlopex@cozybit.com>
+ * Javier Cardona <javier@cozybit.com>
+ *
+ * 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.
+ */
+
+#ifndef CONFIG_H_
+#define CONFIG_H_
+
+int load_config(struct wmediumd *ctx, const char *file, const char *per_file);
+int use_fixed_random_value(struct wmediumd *ctx);
+
+#endif /* CONFIG_H_ */
diff --git a/wmediumd/ieee80211.h b/wmediumd/ieee80211.h
new file mode 100644
index 0000000..f3deb4b
--- /dev/null
+++ b/wmediumd/ieee80211.h
@@ -0,0 +1,74 @@
+/*
+ * wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ * Copyright (c) 2011 cozybit Inc.
+ *
+ * Author: Javier Lopez <jlopex@cozybit.com>
+ * Javier Cardona <javier@cozybit.com>
+ *
+ * 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.
+ */
+
+#ifndef IEEE80211_H_
+#define IEEE80211_H_
+
+#define IEEE80211_AVAILABLE_RATES 12
+#define IEEE80211_TX_MAX_RATES 4
+#define IEEE80211_NUM_ACS 4
+
+#ifndef ETH_ALEN
+#define ETH_ALEN 6
+#endif
+
+#define FCTL_FTYPE 0x0c
+#define FCTL_TODS 0x01
+#define FCTL_FROMDS 0x02
+
+#define FTYPE_MGMT 0x00
+#define FTYPE_DATA 0x08
+
+#define STYPE_QOS_DATA 0x80
+
+#define QOS_CTL_TAG1D_MASK 0x07
+
+enum ieee80211_ac_number {
+ IEEE80211_AC_VO = 0,
+ IEEE80211_AC_VI = 1,
+ IEEE80211_AC_BE = 2,
+ IEEE80211_AC_BK = 3,
+};
+
+static const enum ieee80211_ac_number ieee802_1d_to_ac[8] = {
+ IEEE80211_AC_BE,
+ IEEE80211_AC_BK,
+ IEEE80211_AC_BK,
+ IEEE80211_AC_BE,
+ IEEE80211_AC_VI,
+ IEEE80211_AC_VI,
+ IEEE80211_AC_VO,
+ IEEE80211_AC_VO
+};
+
+struct ieee80211_hdr {
+ unsigned char frame_control[2];
+ unsigned char duration_id[2];
+ unsigned char addr1[ETH_ALEN];
+ unsigned char addr2[ETH_ALEN];
+ unsigned char addr3[ETH_ALEN];
+ unsigned char seq_ctrl[2];
+ unsigned char addr4[ETH_ALEN];
+};
+
+#endif /* IEEE80211_H_ */
diff --git a/wmediumd/inc/linux/um_timetravel.h b/wmediumd/inc/linux/um_timetravel.h
new file mode 100644
index 0000000..174057d
--- /dev/null
+++ b/wmediumd/inc/linux/um_timetravel.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _UAPI_LINUX_UM_TIMETRAVEL_H
+#define _UAPI_LINUX_UM_TIMETRAVEL_H
+#include <linux/types.h>
+
+/**
+ * struct um_timetravel_msg - UM time travel message
+ *
+ * This is the basic message type, going in both directions.
+ *
+ * This is the message passed between the host (user-mode Linux instance)
+ * and the calendar (the application on the other side of the socket) in
+ * order to implement common scheduling.
+ *
+ * Whenever UML has an event it will request runtime for it from the
+ * calendar, and then wait for its turn until it can run, etc. Note
+ * that it will only ever request the single next runtime, i.e. multiple
+ * REQUEST messages override each other.
+ */
+struct um_timetravel_msg {
+ /**
+ * @op: operation value from &enum um_timetravel_ops
+ */
+ __u32 op;
+
+ /**
+ * @seq: sequence number for the message - shall be reflected in
+ * the ACK response, and should be checked while processing
+ * the response to see if it matches
+ */
+ __u32 seq;
+
+ /**
+ * @time: time in nanoseconds
+ */
+ __u64 time;
+};
+
+/**
+ * enum um_timetravel_ops - Operation codes
+ */
+enum um_timetravel_ops {
+ /**
+ * @UM_TIMETRAVEL_ACK: response (ACK) to any previous message,
+ * this usually doesn't carry any data in the 'time' field
+ * unless otherwise specified below
+ */
+ UM_TIMETRAVEL_ACK = 0,
+
+ /**
+ * @UM_TIMETRAVEL_START: initialize the connection, the time
+ * field contains an (arbitrary) ID to possibly be able
+ * to distinguish the connections.
+ */
+ UM_TIMETRAVEL_START = 1,
+
+ /**
+ * @UM_TIMETRAVEL_REQUEST: request to run at the given time
+ * (host -> calendar)
+ */
+ UM_TIMETRAVEL_REQUEST = 2,
+
+ /**
+ * @UM_TIMETRAVEL_WAIT: Indicate waiting for the previously requested
+ * runtime, new requests may be made while waiting (e.g. due to
+ * interrupts); the time field is ignored. The calendar must process
+ * this message and later send a %UM_TIMETRAVEL_RUN message when
+ * the host can run again.
+ * (host -> calendar)
+ */
+ UM_TIMETRAVEL_WAIT = 3,
+
+ /**
+ * @UM_TIMETRAVEL_GET: return the current time from the calendar in the
+ * ACK message, the time in the request message is ignored
+ * (host -> calendar)
+ */
+ UM_TIMETRAVEL_GET = 4,
+
+ /**
+ * @UM_TIMETRAVEL_UPDATE: time update to the calendar, must be sent e.g.
+ * before kicking an interrupt to another calendar
+ * (host -> calendar)
+ */
+ UM_TIMETRAVEL_UPDATE = 5,
+
+ /**
+ * @UM_TIMETRAVEL_RUN: run time request granted, current time is in
+ * the time field
+ * (calendar -> host)
+ */
+ UM_TIMETRAVEL_RUN = 6,
+
+ /**
+ * @UM_TIMETRAVEL_FREE_UNTIL: Enable free-running until the given time,
+ * this is a message from the calendar telling the host that it can
+ * freely do its own scheduling for anything before the indicated
+ * time.
+ * Note that if a calendar sends this message once, the host may
+ * assume that it will also do so in the future, if it implements
+ * wraparound semantics for the time field.
+ * (calendar -> host)
+ */
+ UM_TIMETRAVEL_FREE_UNTIL = 7,
+
+ /**
+ * @UM_TIMETRAVEL_GET_TOD: Return time of day, typically used once at
+ * boot by the virtual machines to get a synchronized time from
+ * the simulation.
+ */
+ UM_TIMETRAVEL_GET_TOD = 8,
+};
+
+#endif /* _UAPI_LINUX_UM_TIMETRAVEL_H */
diff --git a/wmediumd/inc/usfstl/assert.h b/wmediumd/inc/usfstl/assert.h
new file mode 100644
index 0000000..bc46cd0
--- /dev/null
+++ b/wmediumd/inc/usfstl/assert.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_ASSERT_H_
+#define _USFSTL_ASSERT_H_
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+
+/*
+ * usfstl_abort - abort a test and print the given message(s)
+ */
+static inline void __attribute__((noreturn, format(printf, 4, 5)))
+usfstl_abort(const char *fn, unsigned int line, const char *cond, const char *msg, ...)
+{
+ va_list va;
+
+ printf("assertion failure in %s (line %d)\n", fn, line);
+ printf("'%s' failed\n", cond);
+ va_start(va, msg);
+ vprintf(msg, va);
+ va_end(va);
+ abort();
+}
+
+#define USFSTL_BUILD_BUG_ON(expr) extern void __bbo_##__LINE__(char[1 - 2*!!(expr)]);
+
+#define _USFSTL_2STR(x) #x
+#define USFSTL_2STR(x) _USFSTL_2STR(x)
+
+#define __USFSTL_COUNTDOWN 10,9,8,7,6,5,4,3,2,1,0
+#define __USFSTL_COUNT(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
+#define _USFSTL_COUNT(...) __USFSTL_COUNT(__VA_ARGS__)
+#define USFSTL_COUNT(...) _USFSTL_COUNT(, ##__VA_ARGS__, __USFSTL_COUNTDOWN)
+#define __USFSTL_ASSERT_HASARGS_LIST 1,1,1,1,1,1,1,1,1,1,0
+#define USFSTL_HASARGS(...) _USFSTL_COUNT(, ##__VA_ARGS__, __USFSTL_ASSERT_HASARGS_LIST)
+
+#define __usfstl_dispatch(func, numargs) \
+ func ## numargs
+#define _usfstl_dispatch(func, numargs) \
+ __usfstl_dispatch(func, numargs)
+#define usfstl_dispatch(func, ...) \
+ _usfstl_dispatch(func, USFSTL_HASARGS(__VA_ARGS__))
+
+#define _USFSTL_ASSERT_0(cstr, cond) do { \
+ if (!(cond)) \
+ usfstl_abort(__FILE__, __LINE__, cstr, ""); \
+} while (0)
+
+#define _USFSTL_ASSERT_1(cstr, cond, msg, ...) do { \
+ if (!(cond)) \
+ usfstl_abort(__FILE__, __LINE__, cstr, \
+ msg, ##__VA_ARGS__); \
+} while (0)
+
+/*
+ * assert, with or without message
+ *
+ * USFSTL_ASSERT(cond)
+ * USFSTL_ASSERT(cond, msg, format)
+ */
+#define USFSTL_ASSERT(cond, ...) \
+ usfstl_dispatch(_USFSTL_ASSERT_, __VA_ARGS__)(#cond, cond, ##__VA_ARGS__)
+
+#define _USFSTL_ASSERT_CMP_0(as, a, op, bs, b, fmt) do { \
+ typeof(a) _a = a; \
+ typeof(b) _b = b; \
+ if (!((_a) op (_b))) \
+ usfstl_abort(__FILE__, __LINE__, as " " #op " " bs, \
+ " " as " = " fmt "\n " bs " = " fmt "\n",\
+ _a, _b); \
+} while (0)
+
+#define _USFSTL_ASSERT_CMP_1(as, a, op, bs, b, fmt, prfn) do { \
+ typeof(a) _a = a; \
+ typeof(b) _b = b; \
+ if (!((_a) op (_b))) \
+ usfstl_abort(__FILE__, __LINE__, as " " #op " " bs, \
+ " " as " = " fmt "\n " bs " = " fmt "\n",\
+ prfn(_a), prfn(_b)); \
+} while (0)
+
+/*
+ * Assert that two values are equal.
+ *
+ * Note that this is a special case of USFSTL_ASSERT_CMP() below, so the
+ * documentation for that applies.
+ */
+#define USFSTL_ASSERT_EQ(a, b, fmt, ...) \
+ usfstl_dispatch(_USFSTL_ASSERT_CMP_, __VA_ARGS__)(#a, a, ==, \
+ #b, b, fmt, \
+ ##__VA_ARGS__)
+
+/*
+ * Assert a comparison is true.
+ *
+ * Given a value, comparison operator and another value it checks that
+ * the comparison is true, and aborts the test (or program, if used
+ * outside a test) otherwise.
+ *
+ * You must pass a format string suitable for printing the values.
+ *
+ * You may additionally pass a formatting macro that evaluates the
+ * data for the format string, e.g.
+ *
+ * #define val_and_addr(x) (x), &(x)
+ * ...
+ * int x = 1, y = 2;
+ * USFSTL_ASSERT_CMP(x, ==, y, "%d (at %p)", val_and_addr)
+ *
+ * Will result in the printout
+ * [...]
+ * x = 1 (at 0xffff1110)
+ * y = 2 (at 0xffff1114)
+ */
+#define USFSTL_ASSERT_CMP(a, op, b, fmt, ...) \
+ usfstl_dispatch(_USFSTL_ASSERT_CMP_, __VA_ARGS__)(#a, a, op, \
+ #b, b, fmt, \
+ ##__VA_ARGS__)
+
+#endif // _USFSTL_ASSERT_H_
diff --git a/wmediumd/inc/usfstl/list.h b/wmediumd/inc/usfstl/list.h
new file mode 100644
index 0000000..a07c9e9
--- /dev/null
+++ b/wmediumd/inc/usfstl/list.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_LIST_H_
+#define _USFSTL_LIST_H_
+#include <stddef.h>
+#include <stdbool.h>
+
+#ifndef offsetof
+#define offsetof __builtin_offsetof
+#endif
+
+#ifndef container_of
+#define container_of(ptr, type, member) ((type *)(void *)((char *)ptr - offsetof(type, member)))
+#endif
+
+struct usfstl_list_entry {
+ struct usfstl_list_entry *next, *prev;
+};
+
+struct usfstl_list {
+ struct usfstl_list_entry list;
+};
+
+#define USFSTL_LIST_INIT(name) { \
+ .list.next = &(name).list, \
+ .list.prev = &(name).list, \
+}
+#define USFSTL_LIST(name) struct usfstl_list name = USFSTL_LIST_INIT(name)
+
+static inline void usfstl_list_init(struct usfstl_list *list)
+{
+ list->list.next = &list->list;
+ list->list.prev = &list->list;
+}
+
+static inline void usfstl_list_insert_before(struct usfstl_list_entry *existing,
+ struct usfstl_list_entry *new)
+{
+ new->prev = existing->prev;
+ existing->prev->next = new;
+ existing->prev = new;
+ new->next = existing;
+}
+
+static inline void usfstl_list_append(struct usfstl_list *list,
+ struct usfstl_list_entry *new)
+{
+ usfstl_list_insert_before(&list->list, new);
+}
+
+#define usfstl_list_item(element, type, member) \
+ ((type *)container_of(element, type, member))
+
+#define usfstl_next_item(_list, entry, type, member) \
+ ((entry)->member.next != &(_list)->list ? \
+ usfstl_list_item((entry)->member.next, type, member) :\
+ NULL)
+
+#define usfstl_for_each_list_item(item, _list, member) \
+ for (item = usfstl_list_first_item(_list, typeof(*item), member); \
+ item; \
+ item = usfstl_next_item(_list, item, typeof(*item), member))
+
+#define usfstl_for_each_list_item_safe(item, next, _list, member) \
+ for (item = usfstl_list_first_item(_list, typeof(*item), member), \
+ next = item ? usfstl_next_item(_list, item, typeof(*next), member) : NULL; \
+ item; \
+ item = next, \
+ next = item ? usfstl_next_item(_list, next, typeof(*next), member) : NULL)
+
+#define usfstl_for_each_list_item_continue_safe(item, next, _list, member) \
+ for (item = item ? usfstl_next_item(_list, item, typeof(*item), member) : \
+ usfstl_list_first_item(_list, typeof(*item), member), \
+ next = item ? usfstl_next_item(_list, item, typeof(*item), member) : NULL; \
+ item; \
+ item = next, next = item ? usfstl_next_item(_list, next, typeof(*item), member) : NULL)
+
+static inline bool usfstl_list_empty(const struct usfstl_list *list)
+{
+ return list->list.next == &list->list;
+}
+
+#define usfstl_list_first_item(_list, type, member) \
+ (usfstl_list_empty(_list) ? NULL : usfstl_list_item((_list)->list.next, type, member))
+
+static inline void usfstl_list_item_remove(struct usfstl_list_entry *entry)
+{
+ entry->next->prev = entry->prev;
+ entry->prev->next = entry->next;
+ entry->next = NULL;
+ entry->prev = NULL;
+}
+
+#endif // _USFSTL_LIST_H_
diff --git a/wmediumd/inc/usfstl/loop.h b/wmediumd/inc/usfstl/loop.h
new file mode 100644
index 0000000..6d6204d
--- /dev/null
+++ b/wmediumd/inc/usfstl/loop.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+/*
+ * This defines a simple mainloop for reading from multiple sockets and handling
+ * the one that becomes readable. Note that on Windows, it can currently only
+ * handle sockets, not arbitrary descriptors, since we only need it for RPC.
+ */
+#ifndef _USFSTL_LOOP_H_
+#define _USFSTL_LOOP_H_
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include "list.h"
+
+#ifdef _WIN32
+typedef uintptr_t usfstl_fd_t;
+#else
+typedef int usfstl_fd_t;
+#endif
+
+/**
+ * struct usfstl_loop_entry - main loop entry
+ * @list: private
+ * @handler: handler to call when fd is readable
+ * @fd: file descriptor
+ * @priority: priority, higher is handled earlier;
+ * must not change while the entry is registered
+ * @data: user data
+ */
+struct usfstl_loop_entry {
+ struct usfstl_list_entry list;
+ void (*handler)(struct usfstl_loop_entry *);
+ usfstl_fd_t fd;
+ int priority;
+ void *data;
+};
+
+extern struct usfstl_list g_usfstl_loop_entries;
+
+/**
+ * g_usfstl_loop_pre_handler_fn - pre-handler function
+ *
+ * If assigned (defaults to %NULL) this handler will be called
+ * before every loop handler's handling function, e.g. in order
+ * to synchronize time with the wallclock scheduler.
+ */
+extern void (*g_usfstl_loop_pre_handler_fn)(void *data);
+extern void *g_usfstl_loop_pre_handler_fn_data;
+
+/**
+ * usfstl_loop_register - add an entry to the mainloop
+ * @entry: the entry to add, must be fully set up including
+ * the priority
+ */
+void usfstl_loop_register(struct usfstl_loop_entry *entry);
+
+/**
+ * usfstl_loop_unregister - remove an entry from the mainloop
+ * @entry: the entry to remove
+ */
+void usfstl_loop_unregister(struct usfstl_loop_entry *entry);
+
+/**
+ * usfstl_loop_wait_and_handle - wait and handle a single event
+ *
+ * Wait for, and handle, a single event, then return.
+ */
+void usfstl_loop_wait_and_handle(void);
+
+/**
+ * usfstl_loop_for_each_entry - iterate main loop entries
+ */
+#define usfstl_loop_for_each_entry(entry) \
+ usfstl_for_each_list_item(entry, &g_usfstl_loop_entries, list)
+
+/**
+ * usfstl_loop_for_each_entry_safe - iterate main loop entries safely
+ *
+ * Where "safely" means safe to concurrent modification.
+ */
+#define usfstl_loop_for_each_entry_safe(entry, tmp) \
+ usfstl_for_each_list_item_safe(entry, tmp, &g_usfstl_loop_entries, list)
+
+#endif // _USFSTL_LOOP_H_
diff --git a/wmediumd/inc/usfstl/sched.h b/wmediumd/inc/usfstl/sched.h
new file mode 100644
index 0000000..48e3826
--- /dev/null
+++ b/wmediumd/inc/usfstl/sched.h
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_SCHED_H_
+#define _USFSTL_SCHED_H_
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include "assert.h"
+#include "list.h"
+#include "loop.h"
+
+/*
+ * usfstl's simple scheduler
+ *
+ * usfstl's concept of time is just a free-running counter. You can use
+ * any units you like, but we recommend micro or nanoseconds, even
+ * with nanoseconds you can simulate ~580 years in a uint64_t time.
+ *
+ * The scheduler is just a really basic concept, you enter "jobs"
+ * and then call usfstl_sched_next() to run the next job. Usually,
+ * this job would schedule another job and call usfstl_sched_next()
+ * again, etc.
+ *
+ * The scheduler supports grouping jobs (currently into up to 32
+ * groups) and then blocking certain groups from being executed in
+ * the next iteration. This can be used, for example, to separate
+ * in-SIM and out-of-SIM jobs, or threads from IRQs, etc. Note
+ * that groups and priorities are two entirely separate concepts.
+ *
+ * In many cases, you'll probably be looking for usfstltask.h instead
+ * as that allows you to simulate a cooperative multithreading system.
+ * However, raw jobs may in that case be useful for things like e.g.
+ * interrupts that come into the simulated system (if they're always
+ * run-to-completion i.e. cannot yield to other jobs.)
+ *
+ * Additionally, the scheduler has APIs to integrate with another,
+ * external, scheduler to synchronize multiple components.
+ */
+
+/**
+ * struct usfstl_job - usfstl scheduler job
+ * @time: time this job fires
+ * @priority: priority of the job, in case of multiple happening
+ * at the same time; higher value means higher priority
+ * @group: group value, in range 0-31
+ * @name: job name
+ * @data: job data
+ * @callback: called when the job occurs
+ */
+struct usfstl_job {
+ uint64_t start;
+ uint32_t priority;
+ uint8_t group;
+ const char *name;
+
+ void *data;
+ void (*callback)(struct usfstl_job *job);
+
+ /* private: */
+ struct usfstl_list_entry entry;
+};
+
+/**
+ * struct usfstl_scheduler - usfstl scheduler structure
+ * @external_request: If external scheduler integration is required,
+ * set this function pointer appropriately to request the next
+ * run time from the external scheduler.
+ * @external_wait: For external scheduler integration, this must wait
+ * for the previously requested runtime being granted, and you
+ * must call usfstl_sched_set_time() before returning from this
+ * function.
+ * @external_sync_from: For external scheduler integration, return current
+ * time based on external time info.
+ * @time_advanced: Set this to have logging (or similar) when time
+ * advances. Note that the argument is relative to the previous
+ * time, if you need the current absolute time use
+ * usfstl_sched_current_time(), subtract @delta from that to
+ * obtain the time prior to the current advance.
+ *
+ * Use USFSTL_SCHEDULER() to declare (and initialize) a scheduler.
+ */
+struct usfstl_scheduler {
+ void (*external_request)(struct usfstl_scheduler *, uint64_t);
+ void (*external_wait)(struct usfstl_scheduler *);
+ uint64_t (*external_sync_from)(struct usfstl_scheduler *sched);
+ void (*time_advanced)(struct usfstl_scheduler *, uint64_t delta);
+
+/* private: */
+ void (*next_time_changed)(struct usfstl_scheduler *);
+ uint64_t current_time;
+ uint64_t prev_external_sync, next_external_sync;
+
+ struct usfstl_list joblist;
+ struct usfstl_list pending_jobs;
+ struct usfstl_job *allowed_job;
+
+ uint32_t blocked_groups;
+ uint8_t next_external_sync_set:1,
+ prev_external_sync_set:1,
+ waiting:1;
+
+ struct {
+ struct usfstl_loop_entry entry;
+ uint64_t start;
+ uint32_t nsec_per_tick;
+ uint8_t timer_triggered:1,
+ initialized:1;
+ } wallclock;
+
+ struct {
+ struct usfstl_scheduler *parent;
+ int64_t offset;
+ uint32_t tick_ratio;
+ struct usfstl_job job;
+ bool waiting;
+ } link;
+
+ struct {
+ void *ctrl;
+ } ext;
+};
+
+#define USFSTL_SCHEDULER(name) \
+ struct usfstl_scheduler name = { \
+ .joblist = USFSTL_LIST_INIT(name.joblist), \
+ .pending_jobs = USFSTL_LIST_INIT(name.pending_jobs), \
+ }
+
+#define usfstl_time_check(x) \
+ ({ uint64_t __t; typeof(x) __x; (void)(&__t == &__x); 1; })
+
+/**
+ * usfstl_time_cmp - compare time, wrap-around safe
+ * @a: first time
+ * @op: comparison operator (<, >, <=, >=)
+ * @b: second time
+ *
+ * Returns: (a op b), e.g. usfstl_time_cmp(a, >=, b) returns (a >= b)
+ * while accounting for wrap-around
+ */
+#define usfstl_time_cmp(a, op, b) \
+ (usfstl_time_check(a) && usfstl_time_check(b) && \
+ (0 op (int64_t)((b) - (a))))
+
+/**
+ * USFSTL_ASSERT_TIME_CMP - assert that the time comparison holds
+ * @a: first time
+ * @op: comparison operator
+ * @b: second time
+ */
+#define USFSTL_ASSERT_TIME_CMP(a, op, b) do { \
+ uint64_t _a = a; \
+ uint64_t _b = b; \
+ if (!usfstl_time_cmp(_a, op, _b)) \
+ usfstl_abort(__FILE__, __LINE__, \
+ "usfstl_time_cmp(" #a ", " #op ", " #b ")",\
+ " " #a " = %" PRIu64 "\n" \
+ " " #b " = %" PRIu64 "\n", \
+ _a, _b); \
+} while (0)
+
+/**
+ * usfstl_sched_current_time - return current time
+ * @sched: the scheduler to operate with
+ */
+uint64_t usfstl_sched_current_time(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_sched_add_job - add job execution
+ * @sched: the scheduler to operate with
+ * @job: job to add
+ *
+ * Add an job to the execution queue, at the time noted
+ * inside the job.
+ */
+void usfstl_sched_add_job(struct usfstl_scheduler *sched,
+ struct usfstl_job *job);
+
+/**
+ * @usfstl_sched_del_job - remove an job
+ * @sched: the scheduler to operate with
+ * @job: job to remove
+ *
+ * Remove an job from the execution queue, if present.
+ */
+void usfstl_sched_del_job(struct usfstl_job *job);
+
+/**
+ * usfstl_sched_start - start the scheduler
+ * @sched: the scheduler to operate with
+ *
+ * Start the scheduler, which initializes the scheduler data
+ * and syncs with the external scheduler if necessary.
+ */
+void usfstl_sched_start(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_sched_next - call next job
+ * @sched: the scheduler to operate with
+ *
+ * Go into the scheduler, forward time to the next job,
+ * and call its callback.
+ *
+ * Returns the job that was run.
+ */
+struct usfstl_job *usfstl_sched_next(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_job_scheduled - check if an job is scheduled
+ * @job: the job to check
+ *
+ * Returns: %true if the job is on the schedule list, %false otherwise.
+ */
+bool usfstl_job_scheduled(struct usfstl_job *job);
+
+/**
+ * usfstl_sched_next_pending - get first/next pending job
+ * @sched: the scheduler to operate with
+ * @job: %NULL or previously returned job
+ *
+ * This is used to implement usfstl_sched_for_each_pending()
+ * and usfstl_sched_for_each_pending_safe().
+ */
+struct usfstl_job *usfstl_sched_next_pending(struct usfstl_scheduler *sched,
+ struct usfstl_job *job);
+
+#define usfstl_sched_for_each_pending(sched, job) \
+ for (job = usfstl_sched_next_pending(sched, NULL); job; \
+ job = usfstl_sched_next_pending(sched, job))
+
+#define usfstl_sched_for_each_pending_safe(sched, job, tmp) \
+ for (job = usfstl_sched_next_pending(sched, NULL), \
+ tmp = usfstl_sched_next_pending(sched, job); \
+ job; \
+ job = tmp, tmp = usfstl_sched_next_pending(sched, tmp))
+
+struct usfstl_sched_block_data {
+ uint32_t groups;
+ struct usfstl_job *job;
+};
+
+/**
+ * usfstl_sched_block_groups - block groups from executing
+ * @sched: the scheduler to operate with
+ * @groups: groups to block, ORed with the currently blocked groups
+ * @job: single job that's allowed anyway, e.g. if the caller is
+ * part of the blocked group and must be allowed to continue
+ * @save: save data, use with usfstl_sched_restore_groups()
+ */
+void usfstl_sched_block_groups(struct usfstl_scheduler *sched, uint32_t groups,
+ struct usfstl_job *job,
+ struct usfstl_sched_block_data *save);
+
+/**
+ * usfstl_sched_restore_groups - restore blocked groups
+ * @sched: the scheduler to operate with
+ * @restore: data saved during usfstl_sched_block_groups()
+ */
+void usfstl_sched_restore_groups(struct usfstl_scheduler *sched,
+ struct usfstl_sched_block_data *restore);
+
+/**
+ * usfstl_sched_set_time - set time from external source
+ * @sched: the scheduler to operate with
+ * @time: time
+ *
+ * Set the scheduler time from the external source, use this
+ * before returning from the sched_external_wait() method but also when
+ * injecting any other kind of job like an interrupt from an
+ * external source (only applicable when running with external
+ * scheduling and other external interfaces.)
+ */
+void usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time);
+
+/**
+ * usfstl_sched_set_sync_time - set next external sync time
+ * @sched: the scheduler to operate with
+ * @time: time
+ *
+ * When cooperating with an external scheduler, it may be good to
+ * avoid ping-pong all the time, if there's nothing to do. This
+ * function facilitates that.
+ *
+ * Call it before returning from the sched_external_wait() method and
+ * the scheduler will not sync for each internal job again, but
+ * only when the next internal job would be at or later than the
+ * time given as the argument here.
+ *
+ * Note that then you also have to coordinate with the external
+ * scheduler every time there's any interaction with any other
+ * component also driven by the external scheduler.
+ *
+ * To understand this, consider the following timeline, where the
+ * letters indicate scheduler jobs:
+ * - component 1: A C D E F
+ * - component 2: B G
+ * Without calling this function, component 1 will always sync
+ * with the external scheduler in component 2, for every job
+ * it has. However, after the sync for job/time C, component
+ * 2 knows that it will not have any job until G, so if it
+ * tells component 1 (whatever RPC there is calls this function)
+ * then component 1 will sync again only after F.
+ *
+ * However, this also necessitates that whenever the components
+ * interact, this function could be called again. If you imagine
+ * jobs C-F to just be empty ticks that do nothing then this
+ * might not happen. However, if one of them causes interaction
+ * with component 2 (say a network packet in the simulation) the
+ * interaction may cause a new job to be inserted into the
+ * scheduler timeline of component 2. Let's say, for example,
+ * the job D was a transmission from component 1 to 2, and in
+ * component 2 that causes an interrupt and a rescheduling. The
+ * above timeline will thus change to:
+ * - component 1: A C D E F
+ * - component 2: B N G
+ * inserting the job N. Thus, this very interaction needs to
+ * call this function again with the time of N which will be at
+ * or shortly after the time of D, rather than at G.
+ *
+ * This optimises things if jobs C-F don't cause interaction
+ * with other components, and if considered properly in the RPC
+ * protocol will not cause any degradation.
+ *
+ * If not supported, just never call this function and each and
+ * every job will require external synchronisation.
+ */
+void usfstl_sched_set_sync_time(struct usfstl_scheduler *sched, uint64_t time);
+
+/**
+ * g_usfstl_top_scheduler - top level scheduler
+ *
+ * There can be multiple schedulers in an usfstl binary, in particular
+ * when the multi-binary support code is used. In any case, this will
+ * will point to the top-level scheduler to facilitate another level
+ * of integration if needed.
+ */
+extern struct usfstl_scheduler *g_usfstl_top_scheduler;
+
+/**
+ * usfstl_sched_wallclock_init - initialize wall-clock integration
+ * @sched: the scheduler to initialize, it must not have external
+ * integration set up yet
+ * @ns_per_tick: nanoseconds per scheduler tick
+ *
+ * You can use this function to set up a scheduler to run at roughly
+ * wall clock speed (per the @ns_per_tick setting).
+ *
+ * This is compatible with the usfstlloop abstraction, so you can also
+ * add other things to the event loop and they'll be handled while
+ * the scheduler is waiting for time to pass.
+ *
+ * NOTE: This is currently Linux-only.
+ */
+void usfstl_sched_wallclock_init(struct usfstl_scheduler *sched,
+ unsigned int ns_per_tick);
+
+/**
+ * usfstl_sched_wallclock_exit - remove wall-clock integration
+ * @sched: scheduler to remove wall-clock integration from
+ *
+ * This releases any resources used for the wall-clock integration.
+ */
+void usfstl_sched_wallclock_exit(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_sched_wallclock_wait_and_handle - wait for external events
+ * @sched: scheduler that's integrated with the wallclock
+ *
+ * If no scheduler events are pending, this will wait for external
+ * events using usfstl_wait_and_handle() and synchronize the time it
+ * took for such an event to arrive into the given scheduler.
+ */
+void usfstl_sched_wallclock_wait_and_handle(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_sched_link - link a scheduler to another one
+ * @sched: the scheduler to link, must not already use the external
+ * request methods, of course. Should also not be running.
+ * @parent: the parent scheduler to link to
+ * @tick_ratio: "tick_ratio" parent ticks == 1 of our ticks;
+ * e.g. 1000 for if @sched should have microseconds, while @parent
+ * uses nanoseconds.
+ *
+ * This links two schedulers together, and requesting any runtime in the
+ * inner scheduler (@sched) depends on the parent scheduler (@parent)
+ * granting it.
+ *
+ * Time in the inner scheduler is adjusted in two ways:
+ * 1) there's a "tick_ratio" as described above
+ * 2) at the time of linking, neither scheduler changes its current
+ * time, instead an offset between the two is maintained, so the
+ * inner scheduler can be at e.g. zero and be linked to a parent
+ * that has already been running for a while.
+ */
+void usfstl_sched_link(struct usfstl_scheduler *sched,
+ struct usfstl_scheduler *parent,
+ uint32_t tick_ratio);
+
+/**
+ * usfstl_sched_unlink - unlink a scheduler again
+ * @sched: the scheduler to unlink, must be linked
+ */
+void usfstl_sched_unlink(struct usfstl_scheduler *sched);
+
+#endif // _USFSTL_SCHED_H_
diff --git a/wmediumd/inc/usfstl/schedctrl.h b/wmediumd/inc/usfstl/schedctrl.h
new file mode 100644
index 0000000..c8b1cb6
--- /dev/null
+++ b/wmediumd/inc/usfstl/schedctrl.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_SCHEDCTRL_H_
+#define _USFSTL_SCHEDCTRL_H_
+#include <stdint.h>
+#include <inttypes.h>
+#include "loop.h"
+#include "sched.h"
+
+struct usfstl_sched_ctrl {
+ struct usfstl_scheduler *sched;
+ uint64_t ack_time;
+ int64_t offset;
+ uint32_t nsec_per_tick;
+ int fd;
+ unsigned int waiting:1, acked:1, frozen:1, started:1;
+ uint32_t expected_ack_seq;
+};
+
+void usfstl_sched_ctrl_start(struct usfstl_sched_ctrl *ctrl,
+ const char *socket,
+ uint32_t nsec_per_tick,
+ uint64_t client_id,
+ struct usfstl_scheduler *sched);
+void usfstl_sched_ctrl_sync_to(struct usfstl_sched_ctrl *ctrl);
+void usfstl_sched_ctrl_sync_from(struct usfstl_sched_ctrl *ctrl);
+void usfstl_sched_ctrl_stop(struct usfstl_sched_ctrl *ctrl);
+
+/**
+ * usfstl_sched_ctrl_set_frozen - freeze/thaw scheduler interaction
+ * @ctrl: scheduler control
+ * @frozen: whether or not the scheduler interaction is frozen
+ *
+ * When the scheduler control connection is frozen, then any remote
+ * updates will not reflect in the local scheduler, but instead will
+ * just modify the offset vs. the remote.
+ *
+ * This allows scheduling repeatedly at "time zero" while the remote
+ * side is actually running, e.g. to ensure pre-firmware boot hardware
+ * simulation doesn't affect the firmware simulation time.
+ */
+void usfstl_sched_ctrl_set_frozen(struct usfstl_sched_ctrl *ctrl, bool frozen);
+
+#endif // _USFSTL_SCHEDCTRL_H_
diff --git a/wmediumd/inc/usfstl/uds.h b/wmediumd/inc/usfstl/uds.h
new file mode 100644
index 0000000..622e122
--- /dev/null
+++ b/wmediumd/inc/usfstl/uds.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+/*
+ * This defines a simple unix domain socket listen abstraction.
+ */
+#ifndef _USFSTL_UDS_H_
+#define _USFSTL_UDS_H_
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include "list.h"
+
+void usfstl_uds_create(const char *path, void (*connected)(int, void *),
+ void *data);
+void usfstl_uds_remove(const char *path);
+
+int usfstl_uds_connect(const char *path, void (*readable)(int, void *),
+ void *data);
+void usfstl_uds_disconnect(int fd);
+
+#endif // _USFSTL_UDS_H_
diff --git a/wmediumd/inc/usfstl/vhost.h b/wmediumd/inc/usfstl/vhost.h
new file mode 100644
index 0000000..f07a33d
--- /dev/null
+++ b/wmediumd/inc/usfstl/vhost.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_VHOST_H_
+#define _USFSTL_VHOST_H_
+
+#include "list.h"
+#include "sched.h"
+#include "schedctrl.h"
+#include "vhostproto.h"
+
+struct usfstl_vhost_user_buf {
+ unsigned int n_in_sg, n_out_sg;
+ struct iovec *in_sg, *out_sg;
+ size_t written;
+ unsigned int idx;
+ bool allocated;
+};
+
+struct usfstl_vhost_user_dev {
+ uint64_t features, protocol_features;
+ struct usfstl_vhost_user_server *server;
+ void *data;
+};
+
+struct usfstl_vhost_user_ops {
+ void (*connected)(struct usfstl_vhost_user_dev *dev);
+ void (*handle)(struct usfstl_vhost_user_dev *dev,
+ struct usfstl_vhost_user_buf *buf,
+ unsigned int vring);
+ void (*disconnected)(struct usfstl_vhost_user_dev *dev);
+};
+
+/**
+ * struct usfstl_vhost_user_server: vhost-user device server
+ */
+struct usfstl_vhost_user_server {
+ /**
+ * @ops: various ops for the server/devices
+ */
+ const struct usfstl_vhost_user_ops *ops;
+
+ /**
+ * @name: socket name to use
+ */
+ char *socket;
+
+ /**
+ * @interrupt_latency: interrupt latency to model, in
+ * scheduler ticks (actual time then depends on
+ * the scheduler unit)
+ */
+ unsigned int interrupt_latency;
+
+ /**
+ * @max_queues: max number of virt queues supported
+ */
+ unsigned int max_queues;
+
+ /**
+ * @input_queues: bitmap of input queues (where to handle interrupts)
+ */
+ uint64_t input_queues;
+
+ /**
+ * @scheduler: the scheduler to integrate with,
+ * may be %NULL
+ */
+ struct usfstl_scheduler *scheduler;
+
+ /**
+ * @ctrl: external scheduler control to integrate with,
+ * may be %NULL
+ */
+ struct usfstl_sched_ctrl *ctrl;
+
+ /**
+ * @features: user features
+ */
+ uint64_t features;
+
+ /**
+ * @protocol_features: protocol features
+ */
+ uint64_t protocol_features;
+
+ /**
+ * @config: config data, if supported
+ */
+ const void *config;
+
+ /**
+ * @config_len: length of config data
+ */
+ size_t config_len;
+
+ /**
+ * @data: arbitrary user data
+ */
+ void *data;
+};
+
+/**
+ * usfstl_vhost_user_server_start - start the server
+ */
+void usfstl_vhost_user_server_start(struct usfstl_vhost_user_server *server);
+
+/**
+ * usfstl_vhost_user_server_stop - stop the server
+ *
+ * Note that this doesn't stop the existing devices, and it thus
+ * may be used from the connected callback, if only one connection
+ * is allowed.
+ */
+void usfstl_vhost_user_server_stop(struct usfstl_vhost_user_server *server);
+
+/**
+ * usfstl_vhost_user_dev_notify - send a message on a vring
+ * @dev: device to send to
+ * @vring: vring index to send on
+ * @buf: buffer to send
+ * @buflen: length of the buffer
+ */
+void usfstl_vhost_user_dev_notify(struct usfstl_vhost_user_dev *dev,
+ unsigned int vring,
+ const uint8_t *buf, size_t buflen);
+
+/**
+ * usfstl_vhost_user_config_changed - notify host of a config change event
+ * @dev: device to send to
+ */
+void usfstl_vhost_user_config_changed(struct usfstl_vhost_user_dev *dev);
+
+/**
+ * usfstl_vhost_user_to_va - translate address
+ * @dev: device to translate address for
+ * @addr: guest-side virtual addr
+ */
+void *usfstl_vhost_user_to_va(struct usfstl_vhost_user_dev *dev, uint64_t addr);
+
+/* also some IOV helpers */
+size_t iov_len(struct iovec *sg, unsigned int nsg);
+size_t iov_fill(struct iovec *sg, unsigned int nsg,
+ const void *buf, size_t buflen);
+size_t iov_read(void *buf, size_t buflen,
+ struct iovec *sg, unsigned int nsg);
+
+#endif // _USFSTL_VHOST_H_
diff --git a/wmediumd/inc/usfstl/vhostproto.h b/wmediumd/inc/usfstl/vhostproto.h
new file mode 100644
index 0000000..8e7a76e
--- /dev/null
+++ b/wmediumd/inc/usfstl/vhostproto.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_VHOST_PROTO_H_
+#define _USFSTL_VHOST_PROTO_H_
+
+#define MAX_REGIONS 2
+
+/* these are from the vhost-user spec */
+
+struct vhost_user_msg_hdr {
+ uint32_t request;
+
+#define VHOST_USER_MSG_FLAGS_VERSION 0x3
+#define VHOST_USER_VERSION 1
+#define VHOST_USER_MSG_FLAGS_REPLY 0x4
+#define VHOST_USER_MSG_FLAGS_NEED_REPLY 0x8
+ uint32_t flags;
+
+ uint32_t size;
+};
+
+struct vhost_user_region {
+ uint64_t guest_phys_addr;
+ uint64_t size;
+ uint64_t user_addr;
+ uint64_t mmap_offset;
+};
+
+struct vhost_user_msg {
+ struct vhost_user_msg_hdr hdr;
+ union {
+#define VHOST_USER_U64_VRING_IDX_MSK 0x7f
+#define VHOST_USER_U64_NO_FD 0x80
+ uint64_t u64;
+ struct {
+ uint32_t idx, num;
+ } vring_state;
+ struct {
+ uint32_t idx, flags;
+ uint64_t descriptor;
+ uint64_t used;
+ uint64_t avail;
+ uint64_t log;
+ } vring_addr;
+ struct {
+ uint32_t n_regions;
+ uint32_t reserved;
+ struct vhost_user_region regions[MAX_REGIONS];
+ } mem_regions;
+ struct {
+ uint32_t offset;
+ uint32_t size;
+#define VHOST_USER_CFG_SPACE_WRITABLE 0x1
+#define VHOST_USER_CFG_SPACE_MIGRATION 0x2
+ uint32_t flags;
+ uint8_t payload[0];
+ } cfg_space;
+ struct {
+ uint64_t idx_flags;
+ uint64_t size;
+ uint64_t offset;
+ } vring_area;
+ } __attribute__((packed)) payload;
+};
+
+#define VHOST_USER_GET_FEATURES 1
+#define VHOST_USER_SET_FEATURES 2
+#define VHOST_USER_SET_OWNER 3
+#define VHOST_USER_SET_MEM_TABLE 5
+#define VHOST_USER_SET_VRING_NUM 8
+#define VHOST_USER_SET_VRING_ADDR 9
+#define VHOST_USER_SET_VRING_BASE 10
+#define VHOST_USER_SET_VRING_KICK 12
+#define VHOST_USER_SET_VRING_CALL 13
+#define VHOST_USER_GET_PROTOCOL_FEATURES 15
+#define VHOST_USER_SET_VRING_ENABLE 18
+#define VHOST_USER_SET_PROTOCOL_FEATURES 16
+#define VHOST_USER_SET_SLAVE_REQ_FD 21
+#define VHOST_USER_GET_CONFIG 24
+#define VHOST_USER_VRING_KICK 35
+
+#define VHOST_USER_SLAVE_CONFIG_CHANGE_MSG 2
+#define VHOST_USER_SLAVE_VRING_CALL 4
+
+#define VHOST_USER_F_PROTOCOL_FEATURES 30
+
+#define VHOST_USER_PROTOCOL_F_MQ 0
+#define VHOST_USER_PROTOCOL_F_LOG_SHMFD 1
+#define VHOST_USER_PROTOCOL_F_RARP 2
+#define VHOST_USER_PROTOCOL_F_REPLY_ACK 3
+#define VHOST_USER_PROTOCOL_F_MTU 4
+#define VHOST_USER_PROTOCOL_F_SLAVE_REQ 5
+#define VHOST_USER_PROTOCOL_F_CROSS_ENDIAN 6
+#define VHOST_USER_PROTOCOL_F_CRYPTO_SESSION 7
+#define VHOST_USER_PROTOCOL_F_PAGEFAULT 8
+#define VHOST_USER_PROTOCOL_F_CONFIG 9
+#define VHOST_USER_PROTOCOL_F_SLAVE_SEND_FD 10
+#define VHOST_USER_PROTOCOL_F_H_OST_NOTIFIER 11
+#define VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD 12
+#define VHOST_USER_PROTOCOL_F_RESET_DEVICE 13
+#define VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS 14
+
+#endif // _USFSTL_VHOST_PROTO_H_
diff --git a/wmediumd/lib/internal.h b/wmediumd/lib/internal.h
new file mode 100644
index 0000000..709a1c4
--- /dev/null
+++ b/wmediumd/lib/internal.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_INTERNAL_H_
+#define _USFSTL_INTERNAL_H_
+#include <stdarg.h>
+#include <stdbool.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <usfstl/sched.h>
+
+/* byteswap helper */
+#define __swap32(v) \
+ ((((v) & 0xff000000) >> 24) | \
+ (((v) & 0x00ff0000) >> 8) | \
+ (((v) & 0x0000ff00) << 8) | \
+ (((v) & 0x000000ff) << 24))
+
+static inline uint32_t swap32(uint32_t v)
+{
+ return __swap32(v);
+}
+
+#define DIV_ROUND_UP(a, b) ({ \
+ typeof(a) _a = a; \
+ typeof(b) _b = b; \
+ (_a + _b - 1) / _b; \
+})
+
+/* scheduler */
+void _usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time);
+
+/* main loop */
+extern struct usfstl_list g_usfstl_loop_entries;
+
+#endif // _USFSTL_INTERNAL_H_
diff --git a/wmediumd/lib/loop.c b/wmediumd/lib/loop.c
new file mode 100644
index 0000000..e2b28ef
--- /dev/null
+++ b/wmediumd/lib/loop.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <usfstl/loop.h>
+#include <usfstl/list.h>
+#include <assert.h>
+#ifdef _WIN32
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600
+#include <winsock2.h>
+#include <windows.h>
+#else
+#include <sys/select.h>
+#endif
+
+struct usfstl_list g_usfstl_loop_entries =
+ USFSTL_LIST_INIT(g_usfstl_loop_entries);
+void (*g_usfstl_loop_pre_handler_fn)(void *);
+void *g_usfstl_loop_pre_handler_fn_data;
+
+
+void usfstl_loop_register(struct usfstl_loop_entry *entry)
+{
+ struct usfstl_loop_entry *tmp;
+
+ usfstl_loop_for_each_entry(tmp) {
+ if (entry->priority >= tmp->priority) {
+ usfstl_list_insert_before(&tmp->list, &entry->list);
+ return;
+ }
+ }
+
+ usfstl_list_append(&g_usfstl_loop_entries, &entry->list);
+}
+
+void usfstl_loop_unregister(struct usfstl_loop_entry *entry)
+{
+ usfstl_list_item_remove(&entry->list);
+}
+
+void usfstl_loop_wait_and_handle(void)
+{
+ while (true) {
+ struct usfstl_loop_entry *tmp;
+ fd_set rd_set, exc_set;
+ unsigned int max = 0, num;
+
+ FD_ZERO(&rd_set);
+ FD_ZERO(&exc_set);
+
+ usfstl_loop_for_each_entry(tmp) {
+ FD_SET(tmp->fd, &rd_set);
+ FD_SET(tmp->fd, &exc_set);
+ if ((unsigned int)tmp->fd > max)
+ max = tmp->fd;
+ }
+
+ num = select(max + 1, &rd_set, NULL, &exc_set, NULL);
+ assert(num > 0);
+
+ usfstl_loop_for_each_entry(tmp) {
+ void *data = g_usfstl_loop_pre_handler_fn_data;
+
+ if (!FD_ISSET(tmp->fd, &rd_set) &&
+ !FD_ISSET(tmp->fd, &exc_set))
+ continue;
+
+ if (g_usfstl_loop_pre_handler_fn)
+ g_usfstl_loop_pre_handler_fn(data);
+ tmp->handler(tmp);
+ return;
+ }
+ }
+}
diff --git a/wmediumd/lib/sched.c b/wmediumd/lib/sched.c
new file mode 100644
index 0000000..7d0618b
--- /dev/null
+++ b/wmediumd/lib/sched.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <usfstl/assert.h>
+#include <usfstl/sched.h>
+#include <usfstl/list.h>
+#include "internal.h"
+
+uint64_t usfstl_sched_current_time(struct usfstl_scheduler *sched)
+{
+ uint64_t current_time;
+
+ if (!sched->external_sync_from || !sched->waiting)
+ return sched->current_time;
+
+ current_time = sched->external_sync_from(sched);
+
+ /* update current time after sync */
+ usfstl_sched_set_time(sched, current_time);
+
+ return current_time;
+}
+
+static bool usfstl_sched_external_request(struct usfstl_scheduler *sched,
+ uint64_t time)
+{
+ if (!sched->external_request)
+ return false;
+
+ /*
+ * If we received a next_external_sync point, we don't need to ask for
+ * runtime for anything earlier than that point, we're allowed to run.
+ * However, note that this only applies if we're not currently waiting,
+ * if we are in fact waiting for permission, then of course we need to
+ * ask for any earlier time, regardless of the next_external_sync, as
+ * we won't schedule until we get called to run, and that usually won't
+ * happen if we don't ask for it.
+ */
+ if (!sched->waiting && sched->next_external_sync_set &&
+ usfstl_time_cmp(time, <, sched->next_external_sync))
+ return false;
+
+ /* If we asked for this time slot already, don't ask again but wait. */
+ if (sched->prev_external_sync_set && time == sched->prev_external_sync)
+ return true;
+
+ sched->prev_external_sync = time;
+ sched->prev_external_sync_set = 1;
+ sched->external_request(sched, time);
+
+ return true;
+}
+
+static void usfstl_sched_external_wait(struct usfstl_scheduler *sched)
+{
+ /*
+ * Once we wait for the external scheduler, we have to ask again
+ * even if for some reason we end up asking for the same time.
+ */
+ sched->prev_external_sync_set = 0;
+ sched->waiting = 1;
+ sched->external_wait(sched);
+ sched->waiting = 0;
+}
+
+void usfstl_sched_add_job(struct usfstl_scheduler *sched, struct usfstl_job *job)
+{
+ struct usfstl_job *tmp;
+
+ USFSTL_ASSERT_TIME_CMP(job->start, >=, sched->current_time);
+ USFSTL_ASSERT(!usfstl_job_scheduled(job),
+ "cannot add a job that's already scheduled");
+ USFSTL_ASSERT_CMP(job->group, <, 32, "%u");
+
+ if ((1 << job->group) & sched->blocked_groups &&
+ job != sched->allowed_job) {
+ job->start = 0;
+ usfstl_list_append(&sched->pending_jobs, &job->entry);
+ return;
+ }
+
+ usfstl_for_each_list_item(tmp, &sched->joblist, entry) {
+ if (usfstl_time_cmp(tmp->start, >, job->start))
+ break;
+ if (tmp->start == job->start &&
+ tmp->priority < job->priority)
+ break;
+ }
+
+ /* insert after previous entry */
+ if (!tmp)
+ usfstl_list_append(&sched->joblist, &job->entry);
+ else
+ usfstl_list_insert_before(&tmp->entry, &job->entry);
+
+ /*
+ * Request the new job's runtime from the external scheduler
+ * (if configured); if this job doesn't request any earlier
+ * runtime than previously requested, this does nothing. It
+ * may, however, request earlier runtime if this is due to
+ * an interrupt we got from outside while waiting for the
+ * external scheduler.
+ */
+ usfstl_sched_external_request(sched, job->start);
+
+ if (sched->next_time_changed)
+ sched->next_time_changed(sched);
+}
+
+bool usfstl_job_scheduled(struct usfstl_job *job)
+{
+ return job->entry.next != NULL;
+}
+
+void usfstl_sched_del_job(struct usfstl_job *job)
+{
+ if (!usfstl_job_scheduled(job))
+ return;
+
+ usfstl_list_item_remove(&job->entry);
+}
+
+void _usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time)
+{
+ uint64_t delta;
+
+ if (sched->current_time == time)
+ return;
+
+ // check that we at least don't move backwards
+ USFSTL_ASSERT_TIME_CMP(time, >=, sched->current_time);
+
+ delta = time - sched->current_time;
+ sched->current_time = time;
+
+ if (sched->time_advanced)
+ sched->time_advanced(sched, delta);
+}
+
+void usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time)
+{
+ /*
+ * also check that we're not getting set to something later than what
+ * we requested, that'd be a bug since we want to run something at an
+ * earlier time than what we just got set to; unless we have nothing
+ * to do and thus don't care at all.
+ */
+ USFSTL_ASSERT(usfstl_list_empty(&sched->joblist) ||
+ usfstl_time_cmp(time, <=, sched->prev_external_sync),
+ "scheduler time moves further (to %" PRIu64 ") than requested (%" PRIu64 ")",
+ time, sched->prev_external_sync);
+
+ _usfstl_sched_set_time(sched, time);
+}
+
+static void usfstl_sched_forward(struct usfstl_scheduler *sched, uint64_t until)
+{
+ USFSTL_ASSERT_TIME_CMP(until, >=, sched->current_time);
+
+ if (usfstl_sched_external_request(sched, until)) {
+ usfstl_sched_external_wait(sched);
+ /*
+ * The external_wait() method must call
+ * usfstl_sched_set_time() before returning,
+ * so we don't in this case.
+ */
+ return;
+ }
+
+ _usfstl_sched_set_time(sched, until);
+}
+
+void usfstl_sched_start(struct usfstl_scheduler *sched)
+{
+ if (usfstl_sched_external_request(sched, sched->current_time))
+ usfstl_sched_external_wait(sched);
+}
+
+struct usfstl_job *usfstl_sched_next(struct usfstl_scheduler *sched)
+{
+ while (true) {
+ struct usfstl_job *job = usfstl_sched_next_pending(sched, NULL);
+
+ if (!job) {
+ /*
+ * If external scheduler is active, we might get here
+ * with nothing to do, so we just need to wait for an
+ * external input/job which will add a job to our
+ * scheduler.
+ *
+ * Due to the fact that we don't have any API for
+ * cancelling external time requests, we might have
+ * requested time from the external scheduler for a
+ * job that subsequently got removed, ending up here
+ * without a job, or one further in the future which
+ * would cause usfstl_sched_forward() to wait again.
+ *
+ * Additionally, we might only remove the job we just
+ * found during the usfstl_sched_forward() below, if
+ * that causes the main loop to run and we detect an
+ * event that causes a job removal (such as a client
+ * disconnecting from a server), so the job pointer we
+ * have might go stale. Hence, all of this needs to be
+ * checked in the overall loop.
+ */
+ if (sched->external_request) {
+ usfstl_sched_external_wait(sched);
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * Forward, but only if job isn't in the past - this
+ * can happen if some job was inserted while we
+ * were in fact waiting for the external scheduler, i.e.
+ * some sort of external job happened while we thought
+ * there was nothing to do.
+ */
+ if (usfstl_time_cmp(job->start, >, sched->current_time))
+ usfstl_sched_forward(sched, job->start);
+
+ /*
+ * Some sort of external job might have come to us (while
+ * we were stuck waiting for the external scheduler), and
+ * might have inserted an earlier job into the timeline.
+ * If it's not this job's turn yet, reinsert it and check
+ * what's up next in the next loop iteration.
+ *
+ * Also, 'job' might now have been removed, see above.
+ */
+ if (usfstl_sched_next_pending(sched, NULL) != job)
+ continue;
+
+ /*
+ * Otherwise we've actually reached this job, so remove
+ * and call it.
+ */
+ usfstl_sched_del_job(job);
+ job->callback(job);
+ return job;
+ }
+
+ /*
+ * We must not get here, if there's no job whatsoever the
+ * simulation has basically ended in an undefined state, even
+ * the main thread can no longer make progress.
+ */
+ USFSTL_ASSERT(0, "scheduling while there's nothing to do");
+}
+
+void usfstl_sched_set_sync_time(struct usfstl_scheduler *sched, uint64_t time)
+{
+ USFSTL_ASSERT_TIME_CMP(time, >=, sched->current_time);
+ sched->next_external_sync = time;
+ sched->next_external_sync_set = 1;
+}
+
+static void usfstl_sched_block_job(struct usfstl_scheduler *sched,
+ struct usfstl_job *job)
+{
+ usfstl_sched_del_job(job);
+ usfstl_list_append(&sched->pending_jobs, &job->entry);
+}
+
+struct usfstl_job *usfstl_sched_next_pending(struct usfstl_scheduler *sched,
+ struct usfstl_job *job)
+{
+ return job ? usfstl_next_item(&sched->joblist, job, struct usfstl_job, entry) :
+ usfstl_list_first_item(&sched->joblist, struct usfstl_job, entry);
+}
+
+static void usfstl_sched_remove_blocked_jobs(struct usfstl_scheduler *sched)
+{
+ struct usfstl_job *job = NULL, *next;
+
+ usfstl_for_each_list_item_continue_safe(job, next, &sched->joblist,
+ entry) {
+ if (job == sched->allowed_job)
+ continue;
+ if ((1 << job->group) & sched->blocked_groups)
+ usfstl_sched_block_job(sched, job);
+ }
+}
+
+static void usfstl_sched_restore_job(struct usfstl_scheduler *sched,
+ struct usfstl_job *job)
+{
+ usfstl_sched_del_job(job);
+ if (usfstl_time_cmp(job->start, <, sched->current_time))
+ job->start = sched->current_time;
+ usfstl_sched_add_job(sched, job);
+}
+
+static void usfstl_sched_restore_blocked_jobs(struct usfstl_scheduler *sched)
+{
+ struct usfstl_job *job = NULL, *next;
+
+ usfstl_for_each_list_item_continue_safe(job, next, &sched->pending_jobs,
+ entry) {
+ if (job == sched->allowed_job ||
+ !((1 << job->group) & sched->blocked_groups))
+ usfstl_sched_restore_job(sched, job);
+ }
+}
+
+void usfstl_sched_block_groups(struct usfstl_scheduler *sched, uint32_t groups,
+ struct usfstl_job *job,
+ struct usfstl_sched_block_data *save)
+{
+ save->groups = sched->blocked_groups;
+ save->job = sched->allowed_job;
+
+ // it makes no sense to allow a job unless its group is blocked
+ USFSTL_ASSERT(!job || (1 << job->group) & groups,
+ "allowed job group %d must be part of blocked groups (0x%x\n)",
+ job->group, groups);
+
+ sched->blocked_groups |= groups;
+ sched->allowed_job = job;
+
+ usfstl_sched_remove_blocked_jobs(sched);
+}
+
+void usfstl_sched_restore_groups(struct usfstl_scheduler *sched,
+ struct usfstl_sched_block_data *restore)
+{
+ sched->blocked_groups = restore->groups;
+ sched->allowed_job = restore->job;
+
+ usfstl_sched_restore_blocked_jobs(sched);
+ usfstl_sched_remove_blocked_jobs(sched);
+}
+
+static void usfstl_sched_link_job_callback(struct usfstl_job *job)
+{
+ struct usfstl_scheduler *sched = job->data;
+
+ sched->link.waiting = false;
+}
+
+static uint64_t usfstl_sched_link_external_sync_from(struct usfstl_scheduler *sched)
+{
+ uint64_t parent_time;
+
+ parent_time = usfstl_sched_current_time(sched->link.parent);
+
+ return DIV_ROUND_UP(parent_time - sched->link.offset,
+ sched->link.tick_ratio);
+}
+
+static void usfstl_sched_link_external_wait(struct usfstl_scheduler *sched)
+{
+ sched->link.waiting = true;
+
+ while (sched->link.waiting)
+ usfstl_sched_next(sched->link.parent);
+
+ usfstl_sched_set_time(sched, usfstl_sched_current_time(sched));
+}
+
+static void usfstl_sched_link_external_request(struct usfstl_scheduler *sched,
+ uint64_t time)
+{
+ uint64_t parent_time;
+ struct usfstl_job *job = &sched->link.job;
+
+ parent_time = sched->link.tick_ratio * time + sched->link.offset;
+
+ usfstl_sched_del_job(job);
+ job->start = parent_time;
+ usfstl_sched_add_job(sched->link.parent, job);
+}
+
+void usfstl_sched_link(struct usfstl_scheduler *sched,
+ struct usfstl_scheduler *parent,
+ uint32_t tick_ratio)
+{
+ struct usfstl_job *job;
+
+ USFSTL_ASSERT(tick_ratio, "a ratio must be set");
+ USFSTL_ASSERT(!sched->link.parent, "must not be linked");
+
+ USFSTL_ASSERT_EQ(sched->external_request, NULL, "%p");
+ sched->external_request = usfstl_sched_link_external_request;
+
+ USFSTL_ASSERT_EQ(sched->external_wait, NULL, "%p");
+ sched->external_wait = usfstl_sched_link_external_wait;
+
+ USFSTL_ASSERT_EQ(sched->external_sync_from, NULL, "%p");
+ sched->external_sync_from = usfstl_sched_link_external_sync_from;
+
+ sched->link.tick_ratio = tick_ratio;
+ sched->link.parent = parent;
+
+ sched->link.job.callback = usfstl_sched_link_job_callback;
+ sched->link.job.data = sched;
+
+ /* current_time = (parent_time - offset) / tick_ratio */
+ sched->link.offset = sched->link.parent->current_time -
+ sched->current_time * sched->link.tick_ratio;
+
+ /* if we have a job already, request to run it */
+ job = usfstl_sched_next_pending(sched, NULL);
+ if (job)
+ usfstl_sched_external_request(sched, job->start);
+}
+
+void usfstl_sched_unlink(struct usfstl_scheduler *sched)
+{
+ USFSTL_ASSERT(sched->link.parent, "must be linked");
+
+ sched->external_sync_from = NULL;
+ sched->external_wait = NULL;
+ sched->external_request = NULL;
+
+ usfstl_sched_del_job(&sched->link.job);
+ memset(&sched->link, 0, sizeof(sched->link));
+}
diff --git a/wmediumd/lib/schedctrl.c b/wmediumd/lib/schedctrl.c
new file mode 100644
index 0000000..bcd5f89
--- /dev/null
+++ b/wmediumd/lib/schedctrl.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <usfstl/uds.h>
+#include <usfstl/schedctrl.h>
+#include <linux/um_timetravel.h>
+#include "internal.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+static void _usfstl_sched_ctrl_send_msg(struct usfstl_sched_ctrl *ctrl,
+ enum um_timetravel_ops op,
+ uint64_t time, uint32_t seq)
+{
+ struct um_timetravel_msg msg = {
+ .op = op,
+ .seq = seq,
+ .time = time,
+ };
+
+ USFSTL_ASSERT_EQ((int)write(ctrl->fd, &msg, sizeof(msg)),
+ (int)sizeof(msg), "%d");
+}
+
+static void usfstl_sched_ctrl_sock_read(int fd, void *data)
+{
+ struct usfstl_sched_ctrl *ctrl = data;
+ struct um_timetravel_msg msg;
+ int sz = read(fd, &msg, sizeof(msg));
+ uint64_t time;
+
+ USFSTL_ASSERT_EQ(sz, (int)sizeof(msg), "%d");
+
+ switch (msg.op) {
+ case UM_TIMETRAVEL_ACK:
+ if (msg.seq == ctrl->expected_ack_seq) {
+ ctrl->acked = 1;
+ ctrl->ack_time = msg.time;
+ }
+ return;
+ case UM_TIMETRAVEL_RUN:
+ time = DIV_ROUND_UP(msg.time - ctrl->offset,
+ ctrl->nsec_per_tick);
+ usfstl_sched_set_time(ctrl->sched, time);
+ ctrl->waiting = 0;
+ break;
+ case UM_TIMETRAVEL_FREE_UNTIL:
+ /* round down here, so we don't overshoot */
+ time = (msg.time - ctrl->offset) / ctrl->nsec_per_tick;
+ usfstl_sched_set_sync_time(ctrl->sched, time);
+ break;
+ case UM_TIMETRAVEL_START:
+ case UM_TIMETRAVEL_REQUEST:
+ case UM_TIMETRAVEL_WAIT:
+ case UM_TIMETRAVEL_GET:
+ case UM_TIMETRAVEL_UPDATE:
+ case UM_TIMETRAVEL_GET_TOD:
+ USFSTL_ASSERT(0);
+ return;
+ }
+
+ _usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_ACK, 0, msg.seq);
+}
+
+static void usfstl_sched_ctrl_send_msg(struct usfstl_sched_ctrl *ctrl,
+ enum um_timetravel_ops op,
+ uint64_t time)
+{
+ static uint32_t seq, old_expected;
+
+ do {
+ seq++;
+ } while (seq == 0);
+
+ _usfstl_sched_ctrl_send_msg(ctrl, op, time, seq);
+ old_expected = ctrl->expected_ack_seq;
+ ctrl->expected_ack_seq = seq;
+
+ USFSTL_ASSERT_EQ((int)ctrl->acked, 0, "%d");
+
+ /*
+ * Race alert!
+ *
+ * UM_TIMETRAVEL_WAIT basically passes the run "token" to the
+ * controller, which passes it to another participant of the
+ * simulation. This other participant might immediately send
+ * us another message on a different channel, e.g. if this
+ * code is used in a vhost-user device.
+ *
+ * If here we were to use use usfstl_loop_wait_and_handle(),
+ * we could actually get and process the vhost-user message
+ * before the ACK for the WAIT message here, depending on the
+ * (host) kernel's message ordering and select() handling etc.
+ *
+ * To avoid this, directly read the ACK message for the WAIT,
+ * without handling any other sockets (first).
+ */
+ if (op == UM_TIMETRAVEL_WAIT) {
+ usfstl_sched_ctrl_sock_read(ctrl->fd, ctrl);
+ USFSTL_ASSERT(ctrl->acked);
+ }
+
+ while (!ctrl->acked)
+ usfstl_loop_wait_and_handle();
+ ctrl->acked = 0;
+ ctrl->expected_ack_seq = old_expected;
+
+ if (op == UM_TIMETRAVEL_GET) {
+ if (ctrl->frozen) {
+ uint64_t local;
+
+ local = ctrl->sched->current_time * ctrl->nsec_per_tick;
+ ctrl->offset = ctrl->ack_time - local;
+ } else {
+ uint64_t time;
+
+ time = DIV_ROUND_UP(ctrl->ack_time - ctrl->offset,
+ ctrl->nsec_per_tick);
+ usfstl_sched_set_time(ctrl->sched, time);
+ }
+ }
+}
+
+static void usfstl_sched_ctrl_request(struct usfstl_scheduler *sched, uint64_t time)
+{
+ struct usfstl_sched_ctrl *ctrl = sched->ext.ctrl;
+
+ if (!ctrl->started)
+ return;
+
+ usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_REQUEST,
+ time * ctrl->nsec_per_tick + ctrl->offset);
+}
+
+static void usfstl_sched_ctrl_wait(struct usfstl_scheduler *sched)
+{
+ struct usfstl_sched_ctrl *ctrl = sched->ext.ctrl;
+
+ ctrl->waiting = 1;
+ usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_WAIT, -1);
+
+ while (ctrl->waiting)
+ usfstl_loop_wait_and_handle();
+}
+
+#define JOB_ASSERT_VAL(j) (j) ? (j)->name : "<NULL>"
+
+void usfstl_sched_ctrl_start(struct usfstl_sched_ctrl *ctrl,
+ const char *socket,
+ uint32_t nsec_per_tick,
+ uint64_t client_id,
+ struct usfstl_scheduler *sched)
+{
+ struct usfstl_job *job;
+
+ USFSTL_ASSERT_EQ(ctrl->sched, NULL, "%p");
+ USFSTL_ASSERT_EQ(sched->ext.ctrl, NULL, "%p");
+
+ memset(ctrl, 0, sizeof(*ctrl));
+
+ /*
+ * The remote side assumes we start at 0, so if we don't have 0 right
+ * now keep the difference in our own offset (in nsec).
+ */
+ ctrl->offset = -sched->current_time * nsec_per_tick;
+
+ ctrl->nsec_per_tick = nsec_per_tick;
+ ctrl->sched = sched;
+ sched->ext.ctrl = ctrl;
+
+ USFSTL_ASSERT_EQ(usfstl_sched_next_pending(sched, NULL),
+ (struct usfstl_job *)NULL, "%s", JOB_ASSERT_VAL);
+ USFSTL_ASSERT_EQ(sched->external_request, NULL, "%p");
+ USFSTL_ASSERT_EQ(sched->external_wait, NULL, "%p");
+
+ sched->external_request = usfstl_sched_ctrl_request;
+ sched->external_wait = usfstl_sched_ctrl_wait;
+
+ ctrl->fd = usfstl_uds_connect(socket, usfstl_sched_ctrl_sock_read,
+ ctrl);
+
+ /* tell the other side we're starting */
+ usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_START, client_id);
+ ctrl->started = 1;
+
+ /* if we have a job already, request it */
+ job = usfstl_sched_next_pending(sched, NULL);
+ if (job)
+ usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_REQUEST,
+ job->start * nsec_per_tick);
+
+ /*
+ * At this point, we're allowed to do further setup work and can
+ * request schedule time etc. but must eventually start scheduling
+ * the linked scheduler - the remote side is blocked until we do.
+ */
+}
+
+void usfstl_sched_ctrl_sync_to(struct usfstl_sched_ctrl *ctrl)
+{
+ uint64_t time;
+
+ USFSTL_ASSERT(ctrl->started, "cannot sync to scheduler until started");
+
+ time = usfstl_sched_current_time(ctrl->sched) * ctrl->nsec_per_tick;
+ time += ctrl->offset;
+
+ usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_UPDATE, time);
+}
+
+void usfstl_sched_ctrl_sync_from(struct usfstl_sched_ctrl *ctrl)
+{
+ if (!ctrl->started)
+ return;
+ usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_GET, -1);
+}
+
+void usfstl_sched_ctrl_stop(struct usfstl_sched_ctrl *ctrl)
+{
+ USFSTL_ASSERT_EQ(ctrl, ctrl->sched->ext.ctrl, "%p");
+ usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_WAIT, -1);
+ usfstl_uds_disconnect(ctrl->fd);
+ ctrl->sched->ext.ctrl = NULL;
+ ctrl->sched->external_request = NULL;
+ ctrl->sched->external_wait = NULL;
+ ctrl->sched = NULL;
+}
+
+void usfstl_sched_ctrl_set_frozen(struct usfstl_sched_ctrl *ctrl, bool frozen)
+{
+ ctrl->frozen = frozen;
+}
diff --git a/wmediumd/lib/uds.c b/wmediumd/lib/uds.c
new file mode 100644
index 0000000..fc0fe2a
--- /dev/null
+++ b/wmediumd/lib/uds.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <usfstl/assert.h>
+#include <usfstl/loop.h>
+#include <usfstl/list.h>
+#include "internal.h"
+
+struct usfstl_uds_server {
+ struct usfstl_loop_entry entry;
+ void (*connected)(int fd, void *data);
+ void *data;
+ char name[];
+};
+
+void usfstl_uds_accept_handler(struct usfstl_loop_entry *entry)
+{
+ struct usfstl_uds_server *uds;
+ int fd;
+
+ uds = container_of(entry, struct usfstl_uds_server, entry);
+ fd = accept(uds->entry.fd, NULL, NULL);
+
+ uds->connected(fd, uds->data);
+}
+
+void usfstl_uds_create(const char *path, void (*connected)(int, void *),
+ void *data)
+{
+ struct usfstl_uds_server *uds = malloc(sizeof(*uds) + strlen(path) + 1);
+ struct stat buf;
+ int ret = stat(path, &buf), fd;
+ struct sockaddr_un un = {
+ .sun_family = AF_UNIX,
+ };
+
+ USFSTL_ASSERT(uds);
+ strcpy(uds->name, path);
+ uds->data = data;
+ uds->connected = connected;
+
+ if (ret == 0) {
+ USFSTL_ASSERT_EQ((int)(buf.st_mode & S_IFMT), S_IFSOCK, "%d");
+ USFSTL_ASSERT_EQ(unlink(path), 0, "%d");
+ } else {
+ USFSTL_ASSERT_EQ(errno, ENOENT, "%d");
+ }
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ uds->entry.fd = fd;
+
+ strcpy(un.sun_path, path);
+ USFSTL_ASSERT_EQ(bind(fd, (void *)&un, sizeof(un)), 0, "%d");
+
+ USFSTL_ASSERT_EQ(listen(fd, 1000), 0, "%d");
+
+ uds->entry.handler = usfstl_uds_accept_handler;
+
+ usfstl_loop_register(&uds->entry);
+}
+
+void usfstl_uds_remove(const char *path)
+{
+ struct usfstl_loop_entry *tmp;
+ struct usfstl_uds_server *uds, *found = NULL;
+
+ usfstl_loop_for_each_entry(tmp) {
+ if (tmp->handler != usfstl_uds_accept_handler)
+ continue;
+
+ uds = container_of(tmp, struct usfstl_uds_server, entry);
+ if (strcmp(uds->name, path) == 0) {
+ found = uds;
+ break;
+ }
+ }
+
+ USFSTL_ASSERT(found);
+
+ close(found->entry.fd);
+ usfstl_loop_unregister(&found->entry);
+ unlink(path);
+ free(found);
+}
+
+struct usfstl_uds_client {
+ struct usfstl_loop_entry entry;
+ void (*readable)(int fd, void *data);
+ void *data;
+};
+
+void usfstl_uds_readable_handler(struct usfstl_loop_entry *entry)
+{
+ struct usfstl_uds_client *uds;
+
+ uds = container_of(entry, struct usfstl_uds_client, entry);
+
+ uds->readable(uds->entry.fd, uds->data);
+}
+
+int usfstl_uds_connect(const char *path, void (*readable)(int, void *),
+ void *data)
+{
+ struct usfstl_uds_client *client;
+ struct sockaddr_un sock;
+ int fd, err;
+
+ client = calloc(1, sizeof(*client));
+ USFSTL_ASSERT(client);
+ client->entry.handler = usfstl_uds_readable_handler;
+ client->readable = readable;
+ client->data = data;
+
+ sock.sun_family = AF_UNIX;
+ strcpy(sock.sun_path, path);
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ USFSTL_ASSERT(fd >= 0);
+
+ err = connect(fd, (struct sockaddr *) &sock, sizeof(sock));
+ USFSTL_ASSERT(err == 0);
+
+ client->entry.fd = fd;
+ usfstl_loop_register(&client->entry);
+
+ return fd;
+}
+
+void usfstl_uds_disconnect(int fd)
+{
+ struct usfstl_loop_entry *tmp;
+ struct usfstl_uds_client *uds, *found = NULL;
+
+ usfstl_loop_for_each_entry(tmp) {
+ if (tmp->handler != usfstl_uds_readable_handler)
+ continue;
+
+ uds = container_of(tmp, struct usfstl_uds_client, entry);
+ if (uds->entry.fd == fd) {
+ found = uds;
+ break;
+ }
+ }
+
+ USFSTL_ASSERT(found);
+
+ close(fd);
+ usfstl_loop_unregister(&found->entry);
+ free(found);
+}
diff --git a/wmediumd/lib/vhost.c b/wmediumd/lib/vhost.c
new file mode 100644
index 0000000..9d46441
--- /dev/null
+++ b/wmediumd/lib/vhost.c
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stdlib.h>
+#include <usfstl/list.h>
+#include <usfstl/loop.h>
+#include <usfstl/uds.h>
+#include <sys/socket.h>
+#include <sys/mman.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <usfstl/vhost.h>
+#include <linux/virtio_ring.h>
+#include <linux/virtio_config.h>
+#include <endian.h>
+
+#define MAX_REGIONS 2
+#define SG_STACK_PREALLOC 5
+
+struct usfstl_vhost_user_dev_int {
+ struct usfstl_list fds;
+ struct usfstl_job irq_job;
+
+ struct usfstl_loop_entry entry;
+
+ struct usfstl_vhost_user_dev ext;
+
+ unsigned int n_regions;
+ struct vhost_user_region regions[MAX_REGIONS];
+ int region_fds[MAX_REGIONS];
+ void *region_vaddr[MAX_REGIONS];
+
+ int req_fd;
+
+ struct {
+ struct usfstl_loop_entry entry;
+ bool enabled;
+ bool triggered;
+ struct vring virtq;
+ int call_fd;
+ uint16_t last_avail_idx;
+ } virtqs[];
+};
+
+#define CONV(bits) \
+static inline uint##bits##_t __attribute__((used)) \
+cpu_to_virtio##bits(struct usfstl_vhost_user_dev_int *dev, \
+ uint##bits##_t v) \
+{ \
+ if (dev->ext.features & (1ULL << VIRTIO_F_VERSION_1)) \
+ return htole##bits(v); \
+ return v; \
+} \
+static inline uint##bits##_t __attribute__((used)) \
+virtio_to_cpu##bits(struct usfstl_vhost_user_dev_int *dev, \
+ uint##bits##_t v) \
+{ \
+ if (dev->ext.features & (1ULL << VIRTIO_F_VERSION_1)) \
+ return le##bits##toh(v); \
+ return v; \
+}
+
+CONV(16)
+CONV(32)
+CONV(64)
+
+static struct usfstl_vhost_user_buf *
+usfstl_vhost_user_get_virtq_buf(struct usfstl_vhost_user_dev_int *dev,
+ unsigned int virtq_idx,
+ struct usfstl_vhost_user_buf *fixed)
+{
+ struct usfstl_vhost_user_buf *buf = fixed;
+ struct vring *virtq = &dev->virtqs[virtq_idx].virtq;
+ uint16_t avail_idx = virtio_to_cpu16(dev, virtq->avail->idx);
+ uint16_t idx, desc_idx;
+ struct vring_desc *desc;
+ unsigned int n_in = 0, n_out = 0;
+ bool more;
+
+ if (avail_idx == dev->virtqs[virtq_idx].last_avail_idx)
+ return NULL;
+
+ /* ensure we read the descriptor after checking the index */
+ __sync_synchronize();
+
+ idx = dev->virtqs[virtq_idx].last_avail_idx++;
+ idx %= virtq->num;
+ desc_idx = virtio_to_cpu16(dev, virtq->avail->ring[idx]);
+ USFSTL_ASSERT(desc_idx < virtq->num);
+
+ desc = &virtq->desc[desc_idx];
+ do {
+ more = virtio_to_cpu16(dev, desc->flags) & VRING_DESC_F_NEXT;
+
+ if (virtio_to_cpu16(dev, desc->flags) & VRING_DESC_F_WRITE)
+ n_in++;
+ else
+ n_out++;
+ desc = &virtq->desc[virtio_to_cpu16(dev, desc->next)];
+ } while (more);
+
+ if (n_in > fixed->n_in_sg || n_out > fixed->n_out_sg) {
+ size_t sz = sizeof(*buf);
+ struct iovec *vec;
+
+ sz += (n_in + n_out) * sizeof(*vec);
+
+ buf = calloc(1, sz);
+ if (!buf)
+ return NULL;
+
+ vec = (void *)(buf + 1);
+ buf->in_sg = vec;
+ buf->out_sg = vec + n_in;
+ buf->allocated = true;
+ }
+
+ buf->n_in_sg = 0;
+ buf->n_out_sg = 0;
+ buf->idx = desc_idx;
+
+ desc = &virtq->desc[desc_idx];
+ do {
+ struct iovec *vec;
+ uint64_t addr;
+
+ more = virtio_to_cpu16(dev, desc->flags) & VRING_DESC_F_NEXT;
+
+ if (virtio_to_cpu16(dev, desc->flags) & VRING_DESC_F_WRITE) {
+ vec = &buf->in_sg[buf->n_in_sg];
+ buf->n_in_sg++;
+ } else {
+ vec = &buf->out_sg[buf->n_out_sg];
+ buf->n_out_sg++;
+ }
+
+ addr = virtio_to_cpu64(dev, desc->addr);
+ vec->iov_base = usfstl_vhost_user_to_va(&dev->ext, addr);
+ vec->iov_len = virtio_to_cpu32(dev, desc->len);
+
+ desc = &virtq->desc[virtio_to_cpu16(dev, desc->next)];
+ } while (more);
+
+ return buf;
+}
+
+static void usfstl_vhost_user_free_buf(struct usfstl_vhost_user_buf *buf)
+{
+ if (buf->allocated)
+ free(buf);
+}
+
+static void usfstl_vhost_user_readable_handler(struct usfstl_loop_entry *entry)
+{
+ usfstl_loop_unregister(entry);
+ entry->fd = -1;
+}
+
+static int usfstl_vhost_user_read_msg(int fd, struct msghdr *msghdr)
+{
+ struct iovec msg_iov;
+ struct msghdr hdr2 = {
+ .msg_iov = &msg_iov,
+ .msg_iovlen = 1,
+ .msg_control = msghdr->msg_control,
+ .msg_controllen = msghdr->msg_controllen,
+ };
+ struct vhost_user_msg_hdr *hdr;
+ size_t i;
+ size_t maxlen = 0;
+ ssize_t len;
+
+ USFSTL_ASSERT(msghdr->msg_iovlen >= 1);
+ USFSTL_ASSERT(msghdr->msg_iov[0].iov_len >= sizeof(*hdr));
+
+ hdr = msghdr->msg_iov[0].iov_base;
+ msg_iov.iov_base = hdr;
+ msg_iov.iov_len = sizeof(*hdr);
+
+ len = recvmsg(fd, &hdr2, 0);
+ if (len < 0)
+ return -errno;
+ if (len == 0)
+ return -ENOTCONN;
+
+ for (i = 0; i < msghdr->msg_iovlen; i++)
+ maxlen += msghdr->msg_iov[i].iov_len;
+ maxlen -= sizeof(*hdr);
+
+ USFSTL_ASSERT_EQ((int)len, (int)sizeof(*hdr), "%d");
+ USFSTL_ASSERT(hdr->size <= maxlen);
+
+ if (!hdr->size)
+ return 0;
+
+ msghdr->msg_control = NULL;
+ msghdr->msg_controllen = 0;
+ msghdr->msg_iov[0].iov_base += sizeof(*hdr);
+ msghdr->msg_iov[0].iov_len -= sizeof(*hdr);
+ len = recvmsg(fd, msghdr, 0);
+
+ /* restore just in case the user needs it */
+ msghdr->msg_iov[0].iov_base -= sizeof(*hdr);
+ msghdr->msg_iov[0].iov_len += sizeof(*hdr);
+ msghdr->msg_control = hdr2.msg_control;
+ msghdr->msg_controllen = hdr2.msg_controllen;
+
+ if (len < 0)
+ return -errno;
+ if (len == 0)
+ return -ENOTCONN;
+
+ USFSTL_ASSERT_EQ(hdr->size, (uint32_t)len, "%u");
+
+ return 0;
+}
+
+static void usfstl_vhost_user_send_msg(struct usfstl_vhost_user_dev_int *dev,
+ struct vhost_user_msg *msg)
+{
+ size_t msgsz = sizeof(msg->hdr) + msg->hdr.size;
+ bool ack = dev->ext.protocol_features &
+ (1ULL << VHOST_USER_PROTOCOL_F_REPLY_ACK);
+ ssize_t written;
+
+ if (ack)
+ msg->hdr.flags |= VHOST_USER_MSG_FLAGS_NEED_REPLY;
+
+ written = write(dev->req_fd, msg, msgsz);
+ USFSTL_ASSERT_EQ(written, (ssize_t)msgsz, "%zd");
+
+ if (ack) {
+ struct usfstl_loop_entry entry = {
+ .fd = dev->req_fd,
+ .priority = 0x7fffffff, // max
+ .handler = usfstl_vhost_user_readable_handler,
+ };
+ struct iovec msg_iov = {
+ .iov_base = msg,
+ .iov_len = sizeof(*msg),
+ };
+ struct msghdr msghdr = {
+ .msg_iovlen = 1,
+ .msg_iov = &msg_iov,
+ };
+
+ /*
+ * Wait for the fd to be readable - we may have to
+ * handle other simulation (time) messages while
+ * waiting ...
+ */
+ usfstl_loop_register(&entry);
+ while (entry.fd != -1)
+ usfstl_loop_wait_and_handle();
+ USFSTL_ASSERT_EQ(usfstl_vhost_user_read_msg(dev->req_fd,
+ &msghdr),
+ 0, "%d");
+ }
+}
+
+static void usfstl_vhost_user_send_virtq_buf(struct usfstl_vhost_user_dev_int *dev,
+ struct usfstl_vhost_user_buf *buf,
+ int virtq_idx)
+{
+ struct vring *virtq = &dev->virtqs[virtq_idx].virtq;
+ unsigned int idx, widx;
+ int call_fd = dev->virtqs[virtq_idx].call_fd;
+ ssize_t written;
+ uint64_t e = 1;
+
+ if (dev->ext.server->ctrl)
+ usfstl_sched_ctrl_sync_to(dev->ext.server->ctrl);
+
+ idx = virtio_to_cpu16(dev, virtq->used->idx);
+ widx = idx + 1;
+
+ idx %= virtq->num;
+ virtq->used->ring[idx].id = cpu_to_virtio32(dev, buf->idx);
+ virtq->used->ring[idx].len = cpu_to_virtio32(dev, buf->written);
+
+ /* write buffers / used table before flush */
+ __sync_synchronize();
+
+ virtq->used->idx = cpu_to_virtio16(dev, widx);
+
+ if (call_fd < 0 &&
+ dev->ext.protocol_features &
+ (1ULL << VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS) &&
+ dev->ext.protocol_features &
+ (1ULL << VHOST_USER_PROTOCOL_F_SLAVE_REQ)) {
+ struct vhost_user_msg msg = {
+ .hdr.request = VHOST_USER_SLAVE_VRING_CALL,
+ .hdr.flags = VHOST_USER_VERSION,
+ .hdr.size = sizeof(msg.payload.vring_state),
+ .payload.vring_state = {
+ .idx = virtq_idx,
+ },
+ };
+
+ usfstl_vhost_user_send_msg(dev, &msg);
+ return;
+ }
+
+ written = write(dev->virtqs[virtq_idx].call_fd, &e, sizeof(e));
+ USFSTL_ASSERT_EQ(written, (ssize_t)sizeof(e), "%zd");
+}
+
+static void usfstl_vhost_user_handle_queue(struct usfstl_vhost_user_dev_int *dev,
+ unsigned int virtq_idx)
+{
+ /* preallocate on the stack for most cases */
+ struct iovec in_sg[SG_STACK_PREALLOC] = { };
+ struct iovec out_sg[SG_STACK_PREALLOC] = { };
+ struct usfstl_vhost_user_buf _buf = {
+ .in_sg = in_sg,
+ .n_in_sg = SG_STACK_PREALLOC,
+ .out_sg = out_sg,
+ .n_out_sg = SG_STACK_PREALLOC,
+ };
+ struct usfstl_vhost_user_buf *buf;
+
+ while ((buf = usfstl_vhost_user_get_virtq_buf(dev, virtq_idx, &_buf))) {
+ dev->ext.server->ops->handle(&dev->ext, buf, virtq_idx);
+
+ usfstl_vhost_user_send_virtq_buf(dev, buf, virtq_idx);
+ usfstl_vhost_user_free_buf(buf);
+ }
+}
+
+static void usfstl_vhost_user_job_callback(struct usfstl_job *job)
+{
+ struct usfstl_vhost_user_dev_int *dev = job->data;
+ unsigned int virtq;
+
+ for (virtq = 0; virtq < dev->ext.server->max_queues; virtq++) {
+ if (!dev->virtqs[virtq].triggered)
+ continue;
+ dev->virtqs[virtq].triggered = false;
+
+ usfstl_vhost_user_handle_queue(dev, virtq);
+ }
+}
+
+static void usfstl_vhost_user_virtq_kick(struct usfstl_vhost_user_dev_int *dev,
+ unsigned int virtq)
+{
+ if (!(dev->ext.server->input_queues & (1ULL << virtq)))
+ return;
+
+ dev->virtqs[virtq].triggered = true;
+
+ if (usfstl_job_scheduled(&dev->irq_job))
+ return;
+
+ if (!dev->ext.server->scheduler) {
+ usfstl_vhost_user_job_callback(&dev->irq_job);
+ return;
+ }
+
+ if (dev->ext.server->ctrl)
+ usfstl_sched_ctrl_sync_from(dev->ext.server->ctrl);
+
+ dev->irq_job.start = usfstl_sched_current_time(dev->ext.server->scheduler) +
+ dev->ext.server->interrupt_latency;
+ usfstl_sched_add_job(dev->ext.server->scheduler, &dev->irq_job);
+}
+
+static void usfstl_vhost_user_virtq_fdkick(struct usfstl_loop_entry *entry)
+{
+ struct usfstl_vhost_user_dev_int *dev = entry->data;
+ unsigned int virtq;
+ uint64_t v;
+
+ for (virtq = 0; virtq < dev->ext.server->max_queues; virtq++) {
+ if (entry == &dev->virtqs[virtq].entry)
+ break;
+ }
+
+ USFSTL_ASSERT(virtq < dev->ext.server->max_queues);
+
+ USFSTL_ASSERT_EQ((int)read(entry->fd, &v, sizeof(v)),
+ (int)sizeof(v), "%d");
+
+ usfstl_vhost_user_virtq_kick(dev, virtq);
+}
+
+static void usfstl_vhost_user_clear_mappings(struct usfstl_vhost_user_dev_int *dev)
+{
+ unsigned int idx;
+ for (idx = 0; idx < MAX_REGIONS; idx++) {
+ if (dev->region_vaddr[idx]) {
+ munmap(dev->region_vaddr[idx],
+ dev->regions[idx].size + dev->regions[idx].mmap_offset);
+ dev->region_vaddr[idx] = NULL;
+ }
+
+ if (dev->region_fds[idx] != -1) {
+ close(dev->region_fds[idx]);
+ dev->region_fds[idx] = -1;
+ }
+ }
+}
+
+static void usfstl_vhost_user_setup_mappings(struct usfstl_vhost_user_dev_int *dev)
+{
+ unsigned int idx;
+
+ for (idx = 0; idx < dev->n_regions; idx++) {
+ USFSTL_ASSERT(!dev->region_vaddr[idx]);
+
+ /*
+ * Cannot rely on the offset being page-aligned, I think ...
+ * adjust for it later when we translate addresses instead.
+ */
+ dev->region_vaddr[idx] = mmap(NULL,
+ dev->regions[idx].size +
+ dev->regions[idx].mmap_offset,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ dev->region_fds[idx], 0);
+ USFSTL_ASSERT(dev->region_vaddr[idx] != (void *)-1,
+ "mmap() failed (%d) for fd %d", errno, dev->region_fds[idx]);
+ }
+}
+
+static void
+usfstl_vhost_user_update_virtq_kick(struct usfstl_vhost_user_dev_int *dev,
+ unsigned int virtq, int fd)
+{
+ if (dev->virtqs[virtq].entry.fd != -1) {
+ usfstl_loop_unregister(&dev->virtqs[virtq].entry);
+ close(dev->virtqs[virtq].entry.fd);
+ }
+
+ if (fd != -1) {
+ dev->virtqs[virtq].entry.fd = fd;
+ usfstl_loop_register(&dev->virtqs[virtq].entry);
+ }
+}
+
+static void usfstl_vhost_user_dev_free(struct usfstl_vhost_user_dev_int *dev)
+{
+ unsigned int virtq;
+
+ usfstl_loop_unregister(&dev->entry);
+ usfstl_sched_del_job(&dev->irq_job);
+
+ for (virtq = 0; virtq < dev->ext.server->max_queues; virtq++) {
+ usfstl_vhost_user_update_virtq_kick(dev, virtq, -1);
+ if (dev->virtqs[virtq].call_fd != -1)
+ close(dev->virtqs[virtq].call_fd);
+ }
+
+ usfstl_vhost_user_clear_mappings(dev);
+
+ if (dev->req_fd != -1)
+ close(dev->req_fd);
+
+ if (dev->ext.server->ops->disconnected)
+ dev->ext.server->ops->disconnected(&dev->ext);
+
+ if (dev->entry.fd != -1)
+ close(dev->entry.fd);
+
+ free(dev);
+}
+
+static void usfstl_vhost_user_get_msg_fds(struct msghdr *msghdr,
+ int *outfds, int max_fds)
+{
+ struct cmsghdr *msg;
+ int fds;
+
+ for (msg = CMSG_FIRSTHDR(msghdr); msg; msg = CMSG_NXTHDR(msghdr, msg)) {
+ if (msg->cmsg_level != SOL_SOCKET)
+ continue;
+ if (msg->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ fds = (msg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ USFSTL_ASSERT(fds <= max_fds);
+ memcpy(outfds, CMSG_DATA(msg), fds * sizeof(int));
+ break;
+ }
+}
+
+static void usfstl_vhost_user_handle_msg(struct usfstl_loop_entry *entry)
+{
+ struct usfstl_vhost_user_dev_int *dev;
+ struct vhost_user_msg msg;
+ uint8_t data[256]; // limits the config space size ...
+ struct iovec msg_iov[3] = {
+ [0] = {
+ .iov_base = &msg.hdr,
+ .iov_len = sizeof(msg.hdr),
+ },
+ [1] = {
+ .iov_base = &msg.payload,
+ .iov_len = sizeof(msg.payload),
+ },
+ [2] = {
+ .iov_base = data,
+ .iov_len = sizeof(data),
+ },
+ };
+ uint8_t msg_control[CMSG_SPACE(sizeof(int) * MAX_REGIONS)] = { 0 };
+ struct msghdr msghdr = {
+ .msg_iov = msg_iov,
+ .msg_iovlen = 3,
+ .msg_control = msg_control,
+ .msg_controllen = sizeof(msg_control),
+ };
+ ssize_t len;
+ size_t reply_len = 0;
+ unsigned int virtq;
+ int fd;
+
+ dev = container_of(entry, struct usfstl_vhost_user_dev_int, entry);
+
+ if (usfstl_vhost_user_read_msg(entry->fd, &msghdr)) {
+ usfstl_vhost_user_dev_free(dev);
+ return;
+ }
+ len = msg.hdr.size;
+
+ USFSTL_ASSERT((msg.hdr.flags & VHOST_USER_MSG_FLAGS_VERSION) ==
+ VHOST_USER_VERSION);
+
+ switch (msg.hdr.request) {
+ case VHOST_USER_GET_FEATURES:
+ USFSTL_ASSERT_EQ(len, (ssize_t)0, "%zd");
+ reply_len = sizeof(uint64_t);
+ msg.payload.u64 = dev->ext.server->features;
+ msg.payload.u64 |= 1ULL << VHOST_USER_F_PROTOCOL_FEATURES;
+ break;
+ case VHOST_USER_SET_FEATURES:
+ USFSTL_ASSERT_EQ(len, (ssize_t)sizeof(msg.payload.u64), "%zd");
+ dev->ext.features = msg.payload.u64;
+ break;
+ case VHOST_USER_SET_OWNER:
+ USFSTL_ASSERT_EQ(len, (ssize_t)0, "%zd");
+ /* nothing to be done */
+ break;
+ case VHOST_USER_SET_MEM_TABLE:
+ USFSTL_ASSERT(len >= (int)sizeof(msg.payload.mem_regions));
+ USFSTL_ASSERT(msg.payload.mem_regions.n_regions <= MAX_REGIONS);
+ usfstl_vhost_user_clear_mappings(dev);
+ memcpy(dev->regions, msg.payload.mem_regions.regions,
+ msg.payload.mem_regions.n_regions *
+ sizeof(dev->regions[0]));
+ dev->n_regions = msg.payload.mem_regions.n_regions;
+ usfstl_vhost_user_get_msg_fds(&msghdr, dev->region_fds, MAX_REGIONS);
+ usfstl_vhost_user_setup_mappings(dev);
+ break;
+ case VHOST_USER_SET_VRING_NUM:
+ USFSTL_ASSERT(len == (int)sizeof(msg.payload.vring_state));
+ USFSTL_ASSERT(msg.payload.vring_state.idx <
+ dev->ext.server->max_queues);
+ dev->virtqs[msg.payload.vring_state.idx].virtq.num =
+ msg.payload.vring_state.num;
+ break;
+ case VHOST_USER_SET_VRING_ADDR:
+ USFSTL_ASSERT(len == (int)sizeof(msg.payload.vring_addr));
+ USFSTL_ASSERT(msg.payload.vring_addr.idx <=
+ dev->ext.server->max_queues);
+ USFSTL_ASSERT_EQ(msg.payload.vring_addr.flags, (uint32_t)0, "0x%x");
+ USFSTL_ASSERT(!dev->virtqs[msg.payload.vring_addr.idx].enabled);
+ dev->virtqs[msg.payload.vring_addr.idx].last_avail_idx = 0;
+ dev->virtqs[msg.payload.vring_addr.idx].virtq.desc =
+ usfstl_vhost_user_to_va(&dev->ext,
+ msg.payload.vring_addr.descriptor);
+ dev->virtqs[msg.payload.vring_addr.idx].virtq.used =
+ usfstl_vhost_user_to_va(&dev->ext,
+ msg.payload.vring_addr.used);
+ dev->virtqs[msg.payload.vring_addr.idx].virtq.avail =
+ usfstl_vhost_user_to_va(&dev->ext,
+ msg.payload.vring_addr.avail);
+ USFSTL_ASSERT(dev->virtqs[msg.payload.vring_addr.idx].virtq.avail &&
+ dev->virtqs[msg.payload.vring_addr.idx].virtq.desc &&
+ dev->virtqs[msg.payload.vring_addr.idx].virtq.used);
+ break;
+ case VHOST_USER_SET_VRING_BASE:
+ /* ignored - logging not supported */
+ /*
+ * FIXME: our Linux UML virtio implementation
+ * shouldn't send this
+ */
+ break;
+ case VHOST_USER_SET_VRING_KICK:
+ USFSTL_ASSERT(len == (int)sizeof(msg.payload.u64));
+ virtq = msg.payload.u64 & VHOST_USER_U64_VRING_IDX_MSK;
+ USFSTL_ASSERT(virtq <= dev->ext.server->max_queues);
+ if (msg.payload.u64 & VHOST_USER_U64_NO_FD)
+ fd = -1;
+ else
+ usfstl_vhost_user_get_msg_fds(&msghdr, &fd, 1);
+ usfstl_vhost_user_update_virtq_kick(dev, virtq, fd);
+ break;
+ case VHOST_USER_SET_VRING_CALL:
+ USFSTL_ASSERT(len == (int)sizeof(msg.payload.u64));
+ virtq = msg.payload.u64 & VHOST_USER_U64_VRING_IDX_MSK;
+ USFSTL_ASSERT(virtq <= dev->ext.server->max_queues);
+ if (dev->virtqs[virtq].call_fd != -1)
+ close(dev->virtqs[virtq].call_fd);
+ if (msg.payload.u64 & VHOST_USER_U64_NO_FD)
+ dev->virtqs[virtq].call_fd = -1;
+ else
+ usfstl_vhost_user_get_msg_fds(&msghdr,
+ &dev->virtqs[virtq].call_fd,
+ 1);
+ break;
+ case VHOST_USER_GET_PROTOCOL_FEATURES:
+ USFSTL_ASSERT_EQ(len, (ssize_t)0, "%zd");
+ reply_len = sizeof(uint64_t);
+ msg.payload.u64 = dev->ext.server->protocol_features;
+ if (dev->ext.server->config && dev->ext.server->config_len)
+ msg.payload.u64 |= 1ULL << VHOST_USER_PROTOCOL_F_CONFIG;
+ msg.payload.u64 |= 1ULL << VHOST_USER_PROTOCOL_F_SLAVE_REQ;
+ msg.payload.u64 |= 1ULL << VHOST_USER_PROTOCOL_F_SLAVE_SEND_FD;
+ msg.payload.u64 |= 1ULL << VHOST_USER_PROTOCOL_F_REPLY_ACK;
+ break;
+ case VHOST_USER_SET_VRING_ENABLE:
+ USFSTL_ASSERT(len == (int)sizeof(msg.payload.vring_state));
+ USFSTL_ASSERT(msg.payload.vring_state.idx <
+ dev->ext.server->max_queues);
+ dev->virtqs[msg.payload.vring_state.idx].enabled =
+ msg.payload.vring_state.num;
+ break;
+ case VHOST_USER_SET_PROTOCOL_FEATURES:
+ USFSTL_ASSERT(len == (int)sizeof(msg.payload.u64));
+ dev->ext.protocol_features = msg.payload.u64;
+ break;
+ case VHOST_USER_SET_SLAVE_REQ_FD:
+ USFSTL_ASSERT_EQ(len, (ssize_t)0, "%zd");
+ if (dev->req_fd != -1)
+ close(dev->req_fd);
+ usfstl_vhost_user_get_msg_fds(&msghdr, &dev->req_fd, 1);
+ USFSTL_ASSERT(dev->req_fd != -1);
+ break;
+ case VHOST_USER_GET_CONFIG:
+ USFSTL_ASSERT(len == (int)(sizeof(msg.payload.cfg_space) +
+ msg.payload.cfg_space.size));
+ USFSTL_ASSERT(dev->ext.server->config && dev->ext.server->config_len);
+ USFSTL_ASSERT(msg.payload.cfg_space.offset == 0);
+ USFSTL_ASSERT(msg.payload.cfg_space.size <= dev->ext.server->config_len);
+ msg.payload.cfg_space.flags = 0;
+ msg_iov[1].iov_len = sizeof(msg.payload.cfg_space);
+ msg_iov[2].iov_base = (void *)dev->ext.server->config;
+ reply_len = len;
+ break;
+ case VHOST_USER_VRING_KICK:
+ USFSTL_ASSERT(len == (int)sizeof(msg.payload.vring_state));
+ USFSTL_ASSERT(msg.payload.vring_state.idx <
+ dev->ext.server->max_queues);
+ USFSTL_ASSERT(msg.payload.vring_state.num == 0);
+ usfstl_vhost_user_virtq_kick(dev, msg.payload.vring_state.idx);
+ break;
+ default:
+ USFSTL_ASSERT(0, "Unsupported message: %d\n", msg.hdr.request);
+ }
+
+ if (reply_len || (msg.hdr.flags & VHOST_USER_MSG_FLAGS_NEED_REPLY)) {
+ size_t i, tmp;
+
+ if (!reply_len) {
+ msg.payload.u64 = 0;
+ reply_len = sizeof(uint64_t);
+ }
+
+ msg.hdr.size = reply_len;
+ msg.hdr.flags &= ~VHOST_USER_MSG_FLAGS_NEED_REPLY;
+ msg.hdr.flags |= VHOST_USER_MSG_FLAGS_REPLY;
+
+ msghdr.msg_control = NULL;
+ msghdr.msg_controllen = 0;
+
+ reply_len += sizeof(msg.hdr);
+
+ tmp = reply_len;
+ for (i = 0; tmp && i < msghdr.msg_iovlen; i++) {
+ if (tmp <= msg_iov[i].iov_len)
+ msg_iov[i].iov_len = tmp;
+ tmp -= msg_iov[i].iov_len;
+ }
+ msghdr.msg_iovlen = i;
+
+ while (reply_len) {
+ len = sendmsg(entry->fd, &msghdr, 0);
+ if (len < 0) {
+ usfstl_vhost_user_dev_free(dev);
+ return;
+ }
+ USFSTL_ASSERT(len != 0);
+ reply_len -= len;
+
+ for (i = 0; len && i < msghdr.msg_iovlen; i++) {
+ unsigned int rm = len;
+
+ if (msg_iov[i].iov_len <= (size_t)len)
+ rm = msg_iov[i].iov_len;
+ len -= rm;
+ msg_iov[i].iov_len -= rm;
+ msg_iov[i].iov_base += rm;
+ }
+ }
+ }
+}
+
+static void usfstl_vhost_user_connected(int fd, void *data)
+{
+ struct usfstl_vhost_user_server *server = data;
+ struct usfstl_vhost_user_dev_int *dev;
+ unsigned int i;
+
+ dev = calloc(1, sizeof(*dev) +
+ sizeof(dev->virtqs[0]) * server->max_queues);
+
+ USFSTL_ASSERT(dev);
+
+ for (i = 0; i < server->max_queues; i++) {
+ dev->virtqs[i].call_fd = -1;
+ dev->virtqs[i].entry.fd = -1;
+ dev->virtqs[i].entry.data = dev;
+ dev->virtqs[i].entry.handler = usfstl_vhost_user_virtq_fdkick;
+ }
+
+ for (i = 0; i < MAX_REGIONS; i++)
+ dev->region_fds[i] = -1;
+ dev->req_fd = -1;
+
+ dev->ext.server = server;
+ dev->irq_job.data = dev;
+ dev->irq_job.name = "vhost-user-irq";
+ dev->irq_job.priority = 0x10000000;
+ dev->irq_job.callback = usfstl_vhost_user_job_callback;
+ usfstl_list_init(&dev->fds);
+
+ if (server->ops->connected)
+ server->ops->connected(&dev->ext);
+
+ dev->entry.fd = fd;
+ dev->entry.handler = usfstl_vhost_user_handle_msg;
+
+ usfstl_loop_register(&dev->entry);
+}
+
+void usfstl_vhost_user_server_start(struct usfstl_vhost_user_server *server)
+{
+ USFSTL_ASSERT(server->ops);
+ USFSTL_ASSERT(server->socket);
+
+ usfstl_uds_create(server->socket, usfstl_vhost_user_connected, server);
+}
+
+void usfstl_vhost_user_server_stop(struct usfstl_vhost_user_server *server)
+{
+ usfstl_uds_remove(server->socket);
+}
+
+void usfstl_vhost_user_dev_notify(struct usfstl_vhost_user_dev *extdev,
+ unsigned int virtq_idx,
+ const uint8_t *data, size_t datalen)
+{
+ struct usfstl_vhost_user_dev_int *dev;
+ /* preallocate on the stack for most cases */
+ struct iovec in_sg[SG_STACK_PREALLOC] = { };
+ struct iovec out_sg[SG_STACK_PREALLOC] = { };
+ struct usfstl_vhost_user_buf _buf = {
+ .in_sg = in_sg,
+ .n_in_sg = SG_STACK_PREALLOC,
+ .out_sg = out_sg,
+ .n_out_sg = SG_STACK_PREALLOC,
+ };
+ struct usfstl_vhost_user_buf *buf;
+
+ dev = container_of(extdev, struct usfstl_vhost_user_dev_int, ext);
+
+ USFSTL_ASSERT(virtq_idx <= dev->ext.server->max_queues);
+
+ if (!dev->virtqs[virtq_idx].enabled)
+ return;
+
+ buf = usfstl_vhost_user_get_virtq_buf(dev, virtq_idx, &_buf);
+ if (!buf)
+ return;
+
+ USFSTL_ASSERT(buf->n_in_sg && !buf->n_out_sg);
+ iov_fill(buf->in_sg, buf->n_in_sg, data, datalen);
+ buf->written = datalen;
+
+ usfstl_vhost_user_send_virtq_buf(dev, buf, virtq_idx);
+ usfstl_vhost_user_free_buf(buf);
+}
+
+void usfstl_vhost_user_config_changed(struct usfstl_vhost_user_dev *dev)
+{
+ struct usfstl_vhost_user_dev_int *idev;
+ struct vhost_user_msg msg = {
+ .hdr.request = VHOST_USER_SLAVE_CONFIG_CHANGE_MSG,
+ .hdr.flags = VHOST_USER_VERSION,
+ };
+
+ idev = container_of(dev, struct usfstl_vhost_user_dev_int, ext);
+
+ if (!(idev->ext.protocol_features &
+ (1ULL << VHOST_USER_PROTOCOL_F_CONFIG)))
+ return;
+
+ usfstl_vhost_user_send_msg(idev, &msg);
+}
+
+void *usfstl_vhost_user_to_va(struct usfstl_vhost_user_dev *extdev, uint64_t addr)
+{
+ struct usfstl_vhost_user_dev_int *dev;
+ unsigned int region;
+
+ dev = container_of(extdev, struct usfstl_vhost_user_dev_int, ext);
+
+ for (region = 0; region < dev->n_regions; region++) {
+ if (addr >= dev->regions[region].user_addr &&
+ addr < dev->regions[region].user_addr +
+ dev->regions[region].size)
+ return (uint8_t *)dev->region_vaddr[region] +
+ (addr -
+ dev->regions[region].user_addr +
+ dev->regions[region].mmap_offset);
+ }
+
+ USFSTL_ASSERT(0, "cannot translate address %"PRIx64"\n", addr);
+ return NULL;
+}
+
+size_t iov_len(struct iovec *sg, unsigned int nsg)
+{
+ size_t len = 0;
+ unsigned int i;
+
+ for (i = 0; i < nsg; i++)
+ len += sg[i].iov_len;
+
+ return len;
+}
+
+size_t iov_fill(struct iovec *sg, unsigned int nsg,
+ const void *_buf, size_t buflen)
+{
+ const char *buf = _buf;
+ unsigned int i;
+ size_t copied = 0;
+
+#define min(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; })
+ for (i = 0; buflen && i < nsg; i++) {
+ size_t cpy = min(buflen, sg[i].iov_len);
+
+ memcpy(sg[i].iov_base, buf, cpy);
+ buflen -= cpy;
+ copied += cpy;
+ buf += cpy;
+ }
+
+ return copied;
+}
+
+size_t iov_read(void *_buf, size_t buflen,
+ struct iovec *sg, unsigned int nsg)
+{
+ char *buf = _buf;
+ unsigned int i;
+ size_t copied = 0;
+
+#define min(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; })
+ for (i = 0; buflen && i < nsg; i++) {
+ size_t cpy = min(buflen, sg[i].iov_len);
+
+ memcpy(buf, sg[i].iov_base, cpy);
+ buflen -= cpy;
+ copied += cpy;
+ buf += cpy;
+ }
+
+ return copied;
+}
diff --git a/wmediumd/lib/wallclock.c b/wmediumd/lib/wallclock.c
new file mode 100644
index 0000000..3fb09ac
--- /dev/null
+++ b/wmediumd/lib/wallclock.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+#include <sys/timerfd.h>
+#include <usfstl/sched.h>
+#include <usfstl/loop.h>
+
+void usfstl_sched_wallclock_handle_fd(struct usfstl_loop_entry *entry)
+{
+ struct usfstl_scheduler *sched;
+ uint64_t v;
+
+ sched = container_of(entry, struct usfstl_scheduler, wallclock.entry);
+
+ USFSTL_ASSERT_EQ((int)read(entry->fd, &v, sizeof(v)), (int)sizeof(v), "%d");
+ sched->wallclock.timer_triggered = 1;
+}
+
+static void usfstl_sched_wallclock_initialize(struct usfstl_scheduler *sched)
+{
+ struct timespec now = {};
+
+ USFSTL_ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &now), 0, "%d");
+
+ sched->wallclock.start = (uint64_t)now.tv_sec * 1000000000 + now.tv_nsec;
+ sched->wallclock.initialized = 1;
+}
+
+void usfstl_sched_wallclock_request(struct usfstl_scheduler *sched, uint64_t time)
+{
+ struct itimerspec itimer = {};
+ unsigned int nsec_per_tick = sched->wallclock.nsec_per_tick;
+ uint64_t waketime;
+
+ USFSTL_ASSERT(nsec_per_tick != 0);
+
+ if (!sched->wallclock.initialized)
+ usfstl_sched_wallclock_initialize(sched);
+
+ waketime = sched->wallclock.start + nsec_per_tick * time;
+
+ itimer.it_value.tv_sec = waketime / 1000000000;
+ itimer.it_value.tv_nsec = waketime % 1000000000;
+
+ USFSTL_ASSERT_EQ(timerfd_settime(sched->wallclock.entry.fd,
+ TFD_TIMER_ABSTIME, &itimer, NULL),
+ 0, "%d");
+}
+
+void usfstl_sched_wallclock_wait(struct usfstl_scheduler *sched)
+{
+ sched->wallclock.timer_triggered = 0;
+
+ usfstl_loop_register(&sched->wallclock.entry);
+
+ while (!sched->wallclock.timer_triggered)
+ usfstl_loop_wait_and_handle();
+
+ usfstl_loop_unregister(&sched->wallclock.entry);
+
+ usfstl_sched_set_time(sched, sched->prev_external_sync);
+}
+
+void usfstl_sched_wallclock_init(struct usfstl_scheduler *sched,
+ unsigned int ns_per_tick)
+{
+ USFSTL_ASSERT(!sched->external_request && !sched->external_wait);
+
+ sched->external_request = usfstl_sched_wallclock_request;
+ sched->external_wait = usfstl_sched_wallclock_wait;
+
+ sched->wallclock.entry.fd = timerfd_create(CLOCK_MONOTONIC, 0);
+ USFSTL_ASSERT(sched->wallclock.entry.fd >= 0);
+
+ sched->wallclock.entry.handler = usfstl_sched_wallclock_handle_fd;
+
+ sched->wallclock.nsec_per_tick = ns_per_tick;
+}
+
+void usfstl_sched_wallclock_exit(struct usfstl_scheduler *sched)
+{
+ USFSTL_ASSERT(sched->external_request == usfstl_sched_wallclock_request &&
+ sched->external_wait == usfstl_sched_wallclock_wait);
+
+ sched->external_request = NULL;
+ sched->external_wait = NULL;
+ close(sched->wallclock.entry.fd);
+}
+
+static void _usfstl_sched_wallclock_sync_real(struct usfstl_scheduler *sched)
+{
+ struct timespec now = {};
+ uint64_t nowns;
+
+ USFSTL_ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &now), 0, "%d");
+
+ nowns = (uint64_t)now.tv_sec * 1000000000 + now.tv_nsec;
+ nowns -= sched->wallclock.start;
+ usfstl_sched_set_time(sched, nowns / sched->wallclock.nsec_per_tick);
+}
+
+static void usfstl_sched_wallclock_sync_real(void *data)
+{
+ _usfstl_sched_wallclock_sync_real(data);
+}
+
+void usfstl_sched_wallclock_wait_and_handle(struct usfstl_scheduler *sched)
+{
+ void (*old_fn)(void *);
+ void *old_data;
+
+ if (usfstl_sched_next_pending(sched, NULL))
+ return;
+
+ if (!sched->wallclock.initialized) {
+ /*
+ * At least once at the beginning we should schedule
+ * and initialize from an initial request, not here.
+ */
+ usfstl_loop_wait_and_handle();
+ return;
+ }
+
+ old_fn = g_usfstl_loop_pre_handler_fn;
+ old_data = g_usfstl_loop_pre_handler_fn_data;
+ g_usfstl_loop_pre_handler_fn = usfstl_sched_wallclock_sync_real;
+ g_usfstl_loop_pre_handler_fn_data = sched;
+
+ usfstl_loop_wait_and_handle();
+
+ g_usfstl_loop_pre_handler_fn = old_fn;
+ g_usfstl_loop_pre_handler_fn_data = old_data;
+}
diff --git a/wmediumd/list.h b/wmediumd/list.h
new file mode 100644
index 0000000..a6212af
--- /dev/null
+++ b/wmediumd/list.h
@@ -0,0 +1,774 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#include <linux/types.h>
+#include <stddef.h>
+
+/* Stripped down from Linux ~5.5 */
+
+#define POISON_POINTER_DELTA 0
+#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
+#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
+#define WRITE_ONCE(p, v) do { p = v; } while (0)
+#define READ_ONCE(p) (p)
+#ifndef container_of
+#define container_of(ptr, type, member) ((type *)(void*)( (char*)ptr - offsetof(type, member)))
+#endif
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+/**
+ * INIT_LIST_HEAD - Initialize a list_head structure
+ * @list: list_head structure to be initialized.
+ *
+ * Initializes the list_head to point to itself. If it is a list header,
+ * the result is an empty list.
+ */
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+ WRITE_ONCE(list->next, list);
+ list->prev = list;
+}
+
+static inline bool __list_add_valid(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ return true;
+}
+static inline bool __list_del_entry_valid(struct list_head *entry)
+{
+ return true;
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ if (!__list_add_valid(new, prev, next))
+ return;
+
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ WRITE_ONCE(prev->next, new);
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ WRITE_ONCE(prev->next, next);
+}
+
+/*
+ * Delete a list entry and clear the 'prev' pointer.
+ *
+ * This is a special-purpose list clearing method used in the networking code
+ * for lists allocated as per-cpu, where we don't want to incur the extra
+ * WRITE_ONCE() overhead of a regular list_del_init(). The code that uses this
+ * needs to check the node 'prev' pointer instead of calling list_empty().
+ */
+static inline void __list_del_clearprev(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->prev = NULL;
+}
+
+static inline void __list_del_entry(struct list_head *entry)
+{
+ if (!__list_del_entry_valid(entry))
+ return;
+
+ __list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty() on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del_entry(entry);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_replace - replace old entry by new one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace(struct list_head *old,
+ struct list_head *new)
+{
+ new->next = old->next;
+ new->next->prev = new;
+ new->prev = old->prev;
+ new->prev->next = new;
+}
+
+/**
+ * list_replace_init - replace old entry by new one and initialize the old one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace_init(struct list_head *old,
+ struct list_head *new)
+{
+ list_replace(old, new);
+ INIT_LIST_HEAD(old);
+}
+
+/**
+ * list_swap - replace entry1 with entry2 and re-add entry1 at entry2's position
+ * @entry1: the location to place entry2
+ * @entry2: the location to place entry1
+ */
+static inline void list_swap(struct list_head *entry1,
+ struct list_head *entry2)
+{
+ struct list_head *pos = entry2->prev;
+
+ list_del(entry2);
+ list_replace(entry1, entry2);
+ if (pos == entry1)
+ pos = entry2;
+ list_add(entry1, pos);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+ __list_del_entry(entry);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del_entry(list);
+ list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del_entry(list);
+ list_add_tail(list, head);
+}
+
+/**
+ * list_bulk_move_tail - move a subsection of a list to its tail
+ * @head: the head that will follow our entry
+ * @first: first entry to move
+ * @last: last entry to move, can be the same as first
+ *
+ * Move all entries between @first and including @last before @head.
+ * All three entries must belong to the same linked list.
+ */
+static inline void list_bulk_move_tail(struct list_head *head,
+ struct list_head *first,
+ struct list_head *last)
+{
+ first->prev->next = last->next;
+ last->next->prev = first->prev;
+
+ head->prev->next = first;
+ first->prev = head->prev;
+
+ last->next = head;
+ head->prev = last;
+}
+
+/**
+ * list_is_first -- tests whether @list is the first entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_first(const struct list_head *list,
+ const struct list_head *head)
+{
+ return list->prev == head;
+}
+
+/**
+ * list_is_last - tests whether @list is the last entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_last(const struct list_head *list,
+ const struct list_head *head)
+{
+ return list->next == head;
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return READ_ONCE(head->next) == head;
+}
+
+/**
+ * list_empty_careful - tests whether a list is empty and not being modified
+ * @head: the list to test
+ *
+ * Description:
+ * tests whether a list is empty _and_ checks that no other CPU might be
+ * in the process of modifying either member (next or prev)
+ *
+ * NOTE: using list_empty_careful() without synchronization
+ * can only be safe if the only activity that can happen
+ * to the list entry is list_del_init(). Eg. it cannot be used
+ * if another CPU could re-list_add() it.
+ */
+static inline int list_empty_careful(const struct list_head *head)
+{
+ struct list_head *next = head->next;
+ return (next == head) && (next == head->prev);
+}
+
+/**
+ * list_rotate_left - rotate the list to the left
+ * @head: the head of the list
+ */
+static inline void list_rotate_left(struct list_head *head)
+{
+ struct list_head *first;
+
+ if (!list_empty(head)) {
+ first = head->next;
+ list_move_tail(first, head);
+ }
+}
+
+/**
+ * list_rotate_to_front() - Rotate list to specific item.
+ * @list: The desired new front of the list.
+ * @head: The head of the list.
+ *
+ * Rotates list so that @list becomes the new front of the list.
+ */
+static inline void list_rotate_to_front(struct list_head *list,
+ struct list_head *head)
+{
+ /*
+ * Deletes the list head from the list denoted by @head and
+ * places it as the tail of @list, this effectively rotates the
+ * list so that @list is at the front.
+ */
+ list_move_tail(head, list);
+}
+
+/**
+ * list_is_singular - tests whether a list has just one entry.
+ * @head: the list to test.
+ */
+static inline int list_is_singular(const struct list_head *head)
+{
+ return !list_empty(head) && (head->next == head->prev);
+}
+
+static inline void __list_cut_position(struct list_head *list,
+ struct list_head *head, struct list_head *entry)
+{
+ struct list_head *new_first = entry->next;
+ list->next = head->next;
+ list->next->prev = list;
+ list->prev = entry;
+ entry->next = list;
+ head->next = new_first;
+ new_first->prev = head;
+}
+
+/**
+ * list_cut_position - cut a list into two
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ * and if so we won't cut the list
+ *
+ * This helper moves the initial part of @head, up to and
+ * including @entry, from @head to @list. You should
+ * pass on @entry an element you know is on @head. @list
+ * should be an empty list or a list you do not care about
+ * losing its data.
+ *
+ */
+static inline void list_cut_position(struct list_head *list,
+ struct list_head *head, struct list_head *entry)
+{
+ if (list_empty(head))
+ return;
+ if (list_is_singular(head) &&
+ (head->next != entry && head != entry))
+ return;
+ if (entry == head)
+ INIT_LIST_HEAD(list);
+ else
+ __list_cut_position(list, head, entry);
+}
+
+/**
+ * list_cut_before - cut a list into two, before given entry
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ *
+ * This helper moves the initial part of @head, up to but
+ * excluding @entry, from @head to @list. You should pass
+ * in @entry an element you know is on @head. @list should
+ * be an empty list or a list you do not care about losing
+ * its data.
+ * If @entry == @head, all entries on @head are moved to
+ * @list.
+ */
+static inline void list_cut_before(struct list_head *list,
+ struct list_head *head,
+ struct list_head *entry)
+{
+ if (head->next == entry) {
+ INIT_LIST_HEAD(list);
+ return;
+ }
+ list->next = head->next;
+ list->next->prev = list;
+ list->prev = entry->prev;
+ list->prev->next = list;
+ head->next = entry;
+ entry->prev = head;
+}
+
+static inline void __list_splice(const struct list_head *list,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ struct list_head *first = list->next;
+ struct list_head *last = list->prev;
+
+ first->prev = prev;
+ prev->next = first;
+
+ last->next = next;
+ next->prev = last;
+}
+
+/**
+ * list_splice - join two lists, this is designed for stacks
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(const struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head, head->next);
+}
+
+/**
+ * list_splice_tail - join two lists, each list being a queue
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice_tail(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head->prev, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head, head->next);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_splice_tail_init - join two lists and reinitialise the emptied list
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * Each of the lists is a queue.
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_tail_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head->prev, head);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_first_entry - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+/**
+ * list_last_entry - get the last element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_last_entry(ptr, type, member) \
+ list_entry((ptr)->prev, type, member)
+
+/**
+ * list_first_entry_or_null - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define list_first_entry_or_null(ptr, type, member) ({ \
+ struct list_head *head__ = (ptr); \
+ struct list_head *pos__ = READ_ONCE(head__->next); \
+ pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
+})
+
+/**
+ * list_last_entry_or_null - get the last element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define list_last_entry_or_null(ptr, type, member) ({ \
+ struct list_head *head__ = (ptr); \
+ struct list_head *pos__ = READ_ONCE(head__->prev); \
+ pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
+})
+
+/**
+ * list_next_entry - get the next element in list
+ * @pos: the type * to cursor
+ * @member: the name of the list_head within the struct.
+ */
+#define list_next_entry(pos, member) \
+ list_entry((pos)->member.next, typeof(*(pos)), member)
+
+/**
+ * list_prev_entry - get the prev element in list
+ * @pos: the type * to cursor
+ * @member: the name of the list_head within the struct.
+ */
+#define list_prev_entry(pos, member) \
+ list_entry((pos)->member.prev, typeof(*(pos)), member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_continue - continue iteration over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ *
+ * Continue to iterate over a list, continuing after the current position.
+ */
+#define list_for_each_continue(pos, head) \
+ for (pos = pos->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_prev_safe(pos, n, head) \
+ for (pos = (head)->prev, n = pos->prev; \
+ pos != (head); \
+ pos = n, n = pos->prev)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_last_entry(head, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_prev_entry(pos, member))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
+ * @pos: the type * to use as a start point
+ * @head: the head of the list
+ * @member: the name of the list_head within the struct.
+ *
+ * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
+ */
+#define list_prepare_entry(pos, head, member) \
+ ((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - continue iteration over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Continue to iterate over list of given type, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue(pos, head, member) \
+ for (pos = list_next_entry(pos, member); \
+ &pos->member != (head); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_continue_reverse - iterate backwards from the given point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Start to iterate over list of given type backwards, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue_reverse(pos, head, member) \
+ for (pos = list_prev_entry(pos, member); \
+ &pos->member != (head); \
+ pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_from - iterate over list of given type from the current point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from(pos, head, member) \
+ for (; &pos->member != (head); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_from_reverse - iterate backwards over list of given type
+ * from the current point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from_reverse(pos, head, member) \
+ for (; &pos->member != (head); \
+ pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member), \
+ n = list_next_entry(pos, member); \
+ &pos->member != (head); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_continue - continue list iteration safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing after current point,
+ * safe against removal of list entry.
+ */
+#define list_for_each_entry_safe_continue(pos, n, head, member) \
+ for (pos = list_next_entry(pos, member), \
+ n = list_next_entry(pos, member); \
+ &pos->member != (head); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_from - iterate over list from current point safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type from current point, safe against
+ * removal of list entry.
+ */
+#define list_for_each_entry_safe_from(pos, n, head, member) \
+ for (n = list_next_entry(pos, member); \
+ &pos->member != (head); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, safe against removal
+ * of list entry.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member) \
+ for (pos = list_last_entry(head, typeof(*pos), member), \
+ n = list_prev_entry(pos, member); \
+ &pos->member != (head); \
+ pos = n, n = list_prev_entry(n, member))
+
+/**
+ * list_safe_reset_next - reset a stale list_for_each_entry_safe loop
+ * @pos: the loop cursor used in the list_for_each_entry_safe loop
+ * @n: temporary storage used in list_for_each_entry_safe
+ * @member: the name of the list_head within the struct.
+ *
+ * list_safe_reset_next is not safe to use in general if the list may be
+ * modified concurrently (eg. the lock is dropped in the loop body). An
+ * exception to this is if the cursor element (pos) is pinned in the list,
+ * and list_safe_reset_next is called after re-taking the lock and before
+ * completing the current iteration of the loop body.
+ */
+#define list_safe_reset_next(pos, n, member) \
+ n = list_next_entry(pos, member)
+
+#endif
diff --git a/wmediumd/per.c b/wmediumd/per.c
new file mode 100644
index 0000000..1f89a77
--- /dev/null
+++ b/wmediumd/per.c
@@ -0,0 +1,283 @@
+/*
+ * Generate packet error rates for OFDM rates given signal level and
+ * packet length.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "wmediumd.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+/* Code rates for convolutional codes */
+enum fec_rate {
+ FEC_RATE_1_2,
+ FEC_RATE_2_3,
+ FEC_RATE_3_4,
+};
+
+struct rate {
+ int mbps;
+ int mqam;
+ enum fec_rate fec;
+};
+
+/*
+ * rate sets are defined in drivers/net/wireless/mac80211_hwsim.c#hwsim_rates.
+ */
+static struct rate rateset[] = {
+ /*
+ * XXX:
+ * For rate = 1, 2, 5.5, 11 Mbps, we will use mqam and fec of closest
+ * rate. Because these rates are not OFDM rate.
+ */
+ { .mbps = 10, .mqam = 2, .fec = FEC_RATE_1_2 },
+ { .mbps = 20, .mqam = 2, .fec = FEC_RATE_1_2 },
+ { .mbps = 55, .mqam = 2, .fec = FEC_RATE_1_2 },
+ { .mbps = 110, .mqam = 4, .fec = FEC_RATE_1_2 },
+ { .mbps = 60, .mqam = 2, .fec = FEC_RATE_1_2 },
+ { .mbps = 90, .mqam = 2, .fec = FEC_RATE_3_4 },
+ { .mbps = 120, .mqam = 4, .fec = FEC_RATE_1_2 },
+ { .mbps = 180, .mqam = 4, .fec = FEC_RATE_3_4 },
+ { .mbps = 240, .mqam = 16, .fec = FEC_RATE_1_2 },
+ { .mbps = 360, .mqam = 16, .fec = FEC_RATE_3_4 },
+ { .mbps = 480, .mqam = 64, .fec = FEC_RATE_2_3 },
+ { .mbps = 540, .mqam = 64, .fec = FEC_RATE_3_4 },
+};
+static size_t rate_len = ARRAY_SIZE(rateset);
+
+static double n_choose_k(double n, double k)
+{
+ int i;
+ double c = 1;
+
+ if (n < k || !k)
+ return 0;
+
+ if (k > n - k)
+ k = n - k;
+
+ for (i = 1; i <= k; i++)
+ c *= (n - (k - i)) / i;
+
+ return c;
+}
+
+static double dot(double *v1, double *v2, int len)
+{
+ int i;
+ double val = 0;
+
+ for (i = 0; i < len; i++)
+ val += v1[i] * v2[i];
+
+ return val;
+}
+
+/*
+ * Compute bit error rate for BPSK at a given SNR.
+ * See http://en.wikipedia.org/wiki/Phase-shift_keying
+ */
+static double bpsk_ber(double snr_db)
+{
+ double snr = pow(10, (snr_db / 10.));
+
+ return .5 * erfc(sqrt(snr));
+}
+
+/*
+ * Compute bit error rate for M-QAM at a given SNR.
+ * See http://www.dsplog.com/2012/01/01/symbol-error-rate-16qam-64qam-256qam/
+ */
+static double mqam_ber(int m, double snr_db)
+{
+ double k = sqrt(1. / ((2./3) * (m - 1)));
+ double snr = pow(10, (snr_db / 10.));
+ double e = erfc(k * sqrt(snr));
+ double sqrtm = sqrt(m);
+
+ double b = 2 * (1 - 1./sqrtm) * e;
+ double c = (1 - 2./sqrtm + 1./m) * pow(e, 2);
+ double ser = b - c;
+
+ return ser / log2(m);
+}
+
+/*
+ * Compute packet (frame) error rate given a length
+ */
+static double per(double ber, enum fec_rate rate, int frame_len)
+{
+ /* free distances for each fec_rate */
+ int d_free[] = { 10, 6, 5 };
+
+ /* initial rate code coefficients */
+ double a_d[3][10] = {
+ /* FEC_RATE_1_2 */
+ { 11, 0, 38, 0, 193, 0, 1331, 0, 7275, 0 },
+ /* FEC_RATE_2_3 */
+ { 1, 16, 48, 158, 642, 2435, 9174, 34701, 131533, 499312 },
+ /* FEC_RATE_3_4 */
+ { 8, 31, 160, 892, 4512, 23297, 120976, 624304, 3229885, 16721329 }
+ };
+
+ double p_d[ARRAY_SIZE(a_d[0])] = {};
+ double rho = ber;
+ double prob_uncorrected;
+ int k;
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(p_d); i++) {
+ double sum_prob = 0;
+ int d = d_free[rate] + i;
+
+ if (d & 1) {
+ for (k = (d + 1)/2; k <= d; k++)
+ sum_prob += n_choose_k(d, k) * pow(rho, k) *
+ pow(1 - rho, d - k);
+ } else {
+ for (k = d/2 + 1; k <= d; k++)
+ sum_prob += n_choose_k(d, k) * pow(rho, k) *
+ pow(1 - rho, d - k);
+
+ sum_prob += .5 * n_choose_k(d, d/2) * pow(rho, d/2) *
+ pow(1 - rho, d/2);
+ }
+
+ p_d[i] = sum_prob;
+ }
+
+ prob_uncorrected = dot(p_d, a_d[rate], ARRAY_SIZE(a_d[rate]));
+ if (prob_uncorrected > 1)
+ prob_uncorrected = 1;
+
+ return 1.0 - pow(1 - prob_uncorrected, 8 * frame_len);
+}
+
+double get_error_prob_from_snr(double snr, unsigned int rate_idx, u32 freq,
+ int frame_len)
+{
+ int m;
+ enum fec_rate fec;
+ double ber;
+
+ if (snr <= 0.0)
+ return 1.0;
+
+ if (freq > 5000)
+ rate_idx += 4;
+
+ if (rate_idx >= rate_len)
+ return 1.0;
+
+ m = rateset[rate_idx].mqam;
+ fec = rateset[rate_idx].fec;
+
+ if (m == 2)
+ ber = bpsk_ber(snr);
+ else
+ ber = mqam_ber(m, snr);
+
+ return per(ber, fec, frame_len);
+}
+
+static double get_error_prob_from_per_matrix(struct wmediumd *ctx, double snr,
+ unsigned int rate_idx, u32 freq,
+ int frame_len, struct station *src,
+ struct station *dst)
+{
+ int signal_idx;
+
+ signal_idx = snr + NOISE_LEVEL - ctx->per_matrix_signal_min;
+
+ if (signal_idx < 0)
+ return 1.0;
+
+ if (signal_idx >= ctx->per_matrix_row_num)
+ return 0.0;
+
+ if (freq > 5000)
+ rate_idx += 4;
+
+ if (rate_idx >= rate_len)
+ return 1.0;
+
+ return ctx->per_matrix[signal_idx * rate_len + rate_idx];
+}
+
+int read_per_file(struct wmediumd *ctx, const char *file_name)
+{
+ FILE *fp;
+ char line[256];
+ int signal;
+ size_t i;
+ float *temp;
+
+ fp = fopen(file_name, "r");
+ if (fp == NULL) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "fopen failed %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ ctx->per_matrix_signal_min = 1000;
+ while (fscanf(fp, "%s", line) != EOF){
+ if (line[0] == '#') {
+ if (fgets(line, sizeof(line), fp) == NULL) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Failed to read comment line\n");
+ return EXIT_FAILURE;
+ }
+ continue;
+ }
+
+ signal = atoi(line);
+ if (ctx->per_matrix_signal_min > signal)
+ ctx->per_matrix_signal_min = signal;
+
+ if (signal - ctx->per_matrix_signal_min < 0) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "%s: invalid signal=%d\n", __func__, signal);
+ return EXIT_FAILURE;
+ }
+
+ temp = realloc(ctx->per_matrix, sizeof(float) * rate_len *
+ ++ctx->per_matrix_row_num);
+ if (temp == NULL) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Out of memory(PER file)\n");
+ return EXIT_FAILURE;
+ }
+ ctx->per_matrix = temp;
+
+ for (i = 0; i < rate_len; i++) {
+ if (fscanf(fp, "%f", &ctx->per_matrix[
+ (signal - ctx->per_matrix_signal_min) *
+ rate_len + i]) == EOF) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Not enough rate found\n");
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ ctx->get_error_prob = get_error_prob_from_per_matrix;
+
+ return EXIT_SUCCESS;
+}
+
+int index_to_rate(size_t index, u32 freq)
+{
+ if (freq > 5000)
+ index += 4;
+
+ if (index >= rate_len)
+ index = rate_len - 1;
+
+ return rateset[index].mbps;
+}
diff --git a/wmediumd/wmediumd.c b/wmediumd/wmediumd.c
new file mode 100644
index 0000000..3b4aed3
--- /dev/null
+++ b/wmediumd/wmediumd.c
@@ -0,0 +1,1438 @@
+/*
+ * wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ * Copyright (c) 2011 cozybit Inc.
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * Author: Javier Lopez <jlopex@cozybit.com>
+ * Javier Cardona <javier@cozybit.com>
+ *
+ * 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.
+ */
+
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
+#include <assert.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <signal.h>
+#include <math.h>
+#include <sys/timerfd.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <endian.h>
+#include <usfstl/loop.h>
+#include <usfstl/sched.h>
+#include <usfstl/schedctrl.h>
+#include <usfstl/vhost.h>
+#include <usfstl/uds.h>
+
+#include "wmediumd.h"
+#include "ieee80211.h"
+#include "config.h"
+#include "api.h"
+
+USFSTL_SCHEDULER(scheduler);
+
+static void wmediumd_deliver_frame(struct usfstl_job *job);
+
+enum {
+ HWSIM_VQ_TX,
+ HWSIM_VQ_RX,
+ HWSIM_NUM_VQS,
+};
+
+static inline int div_round(int a, int b)
+{
+ return (a + b - 1) / b;
+}
+
+static inline int pkt_duration(int len, int rate)
+{
+ /* preamble + signal + t_sym * n_sym, rate in 100 kbps */
+ return 16 + 4 + 4 * div_round((16 + 8 * len + 6) * 10, 4 * rate);
+}
+
+int w_logf(struct wmediumd *ctx, u8 level, const char *format, ...)
+{
+ va_list(args);
+ va_start(args, format);
+ if (ctx->log_lvl >= level) {
+ return vprintf(format, args);
+ }
+ return -1;
+}
+
+int w_flogf(struct wmediumd *ctx, u8 level, FILE *stream, const char *format, ...)
+{
+ va_list(args);
+ va_start(args, format);
+ if (ctx->log_lvl >= level) {
+ return vfprintf(stream, format, args);
+ }
+ return -1;
+}
+
+static void wqueue_init(struct wqueue *wqueue, int cw_min, int cw_max)
+{
+ INIT_LIST_HEAD(&wqueue->frames);
+ wqueue->cw_min = cw_min;
+ wqueue->cw_max = cw_max;
+}
+
+void station_init_queues(struct station *station)
+{
+ wqueue_init(&station->queues[IEEE80211_AC_BK], 15, 1023);
+ wqueue_init(&station->queues[IEEE80211_AC_BE], 15, 1023);
+ wqueue_init(&station->queues[IEEE80211_AC_VI], 7, 15);
+ wqueue_init(&station->queues[IEEE80211_AC_VO], 3, 7);
+}
+
+static inline bool frame_has_a4(struct frame *frame)
+{
+ struct ieee80211_hdr *hdr = (void *)frame->data;
+
+ return (hdr->frame_control[1] & (FCTL_TODS | FCTL_FROMDS)) ==
+ (FCTL_TODS | FCTL_FROMDS);
+}
+
+static inline bool frame_is_mgmt(struct frame *frame)
+{
+ struct ieee80211_hdr *hdr = (void *)frame->data;
+
+ return (hdr->frame_control[0] & FCTL_FTYPE) == FTYPE_MGMT;
+}
+
+static inline bool frame_is_data(struct frame *frame)
+{
+ struct ieee80211_hdr *hdr = (void *)frame->data;
+
+ return (hdr->frame_control[0] & FCTL_FTYPE) == FTYPE_DATA;
+}
+
+static inline bool frame_is_data_qos(struct frame *frame)
+{
+ struct ieee80211_hdr *hdr = (void *)frame->data;
+
+ return (hdr->frame_control[0] & (FCTL_FTYPE | STYPE_QOS_DATA)) ==
+ (FTYPE_DATA | STYPE_QOS_DATA);
+}
+
+static inline u8 *frame_get_qos_ctl(struct frame *frame)
+{
+ struct ieee80211_hdr *hdr = (void *)frame->data;
+
+ if (frame_has_a4(frame))
+ return (u8 *)hdr + 30;
+ else
+ return (u8 *)hdr + 24;
+}
+
+static enum ieee80211_ac_number frame_select_queue_80211(struct frame *frame)
+{
+ u8 *p;
+ int priority;
+
+ if (!frame_is_data(frame))
+ return IEEE80211_AC_VO;
+
+ if (!frame_is_data_qos(frame))
+ return IEEE80211_AC_BE;
+
+ p = frame_get_qos_ctl(frame);
+ priority = *p & QOS_CTL_TAG1D_MASK;
+
+ return ieee802_1d_to_ac[priority];
+}
+
+static double dBm_to_milliwatt(int decibel_intf)
+{
+#define INTF_LIMIT (31)
+ int intf_diff = NOISE_LEVEL - decibel_intf;
+
+ if (intf_diff >= INTF_LIMIT)
+ return 0.001;
+
+ if (intf_diff <= -INTF_LIMIT)
+ return 1000.0;
+
+ return pow(10.0, -intf_diff / 10.0);
+}
+
+static double milliwatt_to_dBm(double value)
+{
+ return 10.0 * log10(value);
+}
+
+static int set_interference_duration(struct wmediumd *ctx, int src_idx,
+ int duration, int signal)
+{
+ int i;
+
+ if (!ctx->intf)
+ return 0;
+
+ if (signal >= CCA_THRESHOLD)
+ return 0;
+
+ for (i = 0; i < ctx->num_stas; i++) {
+ ctx->intf[ctx->num_stas * src_idx + i].duration += duration;
+ // use only latest value
+ ctx->intf[ctx->num_stas * src_idx + i].signal = signal;
+ }
+
+ return 1;
+}
+
+static int get_signal_offset_by_interference(struct wmediumd *ctx, int src_idx,
+ int dst_idx)
+{
+ int i;
+ double intf_power;
+
+ if (!ctx->intf)
+ return 0;
+
+ intf_power = 0.0;
+ for (i = 0; i < ctx->num_stas; i++) {
+ if (i == src_idx || i == dst_idx)
+ continue;
+ if (drand48() < ctx->intf[i * ctx->num_stas + dst_idx].prob_col)
+ intf_power += dBm_to_milliwatt(
+ ctx->intf[i * ctx->num_stas + dst_idx].signal);
+ }
+
+ if (intf_power <= 1.0)
+ return 0;
+
+ return (int)(milliwatt_to_dBm(intf_power) + 0.5);
+}
+
+static bool is_multicast_ether_addr(const u8 *addr)
+{
+ return 0x01 & addr[0];
+}
+
+static struct station *get_station_by_addr(struct wmediumd *ctx, u8 *addr)
+{
+ struct station *station;
+
+ list_for_each_entry(station, &ctx->stations, list) {
+ if (memcmp(station->addr, addr, ETH_ALEN) == 0)
+ return station;
+ }
+ return NULL;
+}
+
+static bool station_has_addr(struct station *station, const u8 *addr)
+{
+ unsigned int i;
+
+ if (memcmp(station->addr, addr, ETH_ALEN) == 0)
+ return true;
+
+ for (i = 0; i < station->n_addrs; i++) {
+ if (memcmp(station->addrs[i].addr, addr, ETH_ALEN) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+static struct station *get_station_by_used_addr(struct wmediumd *ctx, u8 *addr)
+{
+ struct station *station;
+
+ list_for_each_entry(station, &ctx->stations, list) {
+ if (station_has_addr(station, addr))
+ return station;
+ }
+ return NULL;
+}
+
+static void wmediumd_wait_for_client_ack(struct wmediumd *ctx,
+ struct client *client)
+{
+ client->wait_for_ack = true;
+
+ while (client->wait_for_ack)
+ usfstl_loop_wait_and_handle();
+}
+
+static void wmediumd_notify_frame_start(struct usfstl_job *job)
+{
+ struct frame *frame = container_of(job, struct frame, start_job);
+ struct wmediumd *ctx = job->data;
+ struct client *client, *tmp;
+ struct {
+ struct wmediumd_message_header hdr;
+ struct wmediumd_tx_start start;
+ } __attribute__((packed)) msg = {
+ .hdr.type = WMEDIUMD_MSG_TX_START,
+ .hdr.data_len = sizeof(msg.start),
+ .start.freq = frame->freq,
+ };
+
+ if (ctx->ctrl)
+ usfstl_sched_ctrl_sync_to(ctx->ctrl);
+
+ list_for_each_entry_safe(client, tmp, &ctx->clients, list) {
+ if (!(client->flags & WMEDIUMD_CTL_NOTIFY_TX_START))
+ continue;
+
+ if (client == frame->src)
+ msg.start.cookie = frame->cookie;
+ else
+ msg.start.cookie = 0;
+
+ /* must be API socket since flags cannot otherwise be set */
+ assert(client->type == CLIENT_API_SOCK);
+
+ write(client->loop.fd, &msg, sizeof(msg));
+
+ wmediumd_wait_for_client_ack(ctx, client);
+ }
+}
+
+static void log2pcap(struct wmediumd *ctx, struct frame *frame, uint64_t ts)
+{
+ struct {
+ uint8_t it_version;
+ uint8_t it_pad;
+ uint16_t it_len;
+ uint32_t it_present;
+ struct {
+ uint16_t freq, flags;
+ } channel;
+ uint8_t signal;
+ } __attribute__((packed)) radiotap_hdr = {
+ .it_len = htole16(sizeof(radiotap_hdr)),
+ .it_present = htole32(1 << 3 /* channel */ |
+ 1 << 5 /* signal dBm */),
+ .channel.freq = htole16(frame->freq),
+ .signal = frame->signal,
+ };
+ struct {
+ uint32_t type, blocklen, ifidx, ts_hi, ts_lo, caplen, pktlen;
+ } __attribute__((packed)) blockhdr = {
+ .type = 6,
+ .ts_hi = ts / (1ULL << 32),
+ .ts_lo = ts,
+ .caplen = frame->data_len + sizeof(radiotap_hdr),
+ .pktlen = frame->data_len + sizeof(radiotap_hdr),
+ };
+ static const uint8_t pad[3];
+ uint32_t sz, align;
+
+ sz = blockhdr.caplen + sizeof(blockhdr) + sizeof(uint32_t);
+ blockhdr.blocklen = (sz + 3) & ~3;
+ align = blockhdr.blocklen - sz;
+
+ fwrite(&blockhdr, sizeof(blockhdr), 1, ctx->pcap_file);
+ fwrite(&radiotap_hdr, sizeof(radiotap_hdr), 1, ctx->pcap_file);
+ fwrite(frame->data, frame->data_len, 1, ctx->pcap_file);
+ fwrite(pad, align, 1, ctx->pcap_file);
+ fwrite(&blockhdr.blocklen, sizeof(blockhdr.blocklen), 1, ctx->pcap_file);
+ fflush(ctx->pcap_file);
+}
+
+static void queue_frame(struct wmediumd *ctx, struct station *station,
+ struct frame *frame)
+{
+ struct ieee80211_hdr *hdr = (void *)frame->data;
+ u8 *dest = hdr->addr1;
+ uint64_t target;
+ struct wqueue *queue;
+ struct frame *tail;
+ struct station *tmpsta, *deststa;
+ int send_time;
+ int cw;
+ double error_prob;
+ bool is_acked = false;
+ bool noack = false;
+ int i, j;
+ int rate_idx;
+ int ac;
+
+ /* TODO configure phy parameters */
+ int slot_time = 9;
+ int sifs = 16;
+ int difs = 2 * slot_time + sifs;
+
+ int retries = 0;
+
+ int ack_time_usec = pkt_duration(14, index_to_rate(0, frame->freq)) +
+ sifs;
+
+ /*
+ * To determine a frame's expiration time, we compute the
+ * number of retries we might have to make due to radio conditions
+ * or contention, and add backoff time accordingly. To that, we
+ * add the expiration time of the previous frame in the queue.
+ */
+
+ ac = frame_select_queue_80211(frame);
+ queue = &station->queues[ac];
+
+ /* try to "send" this frame at each of the rates in the rateset */
+ send_time = 0;
+ cw = queue->cw_min;
+
+ int snr = SNR_DEFAULT;
+
+ if (is_multicast_ether_addr(dest)) {
+ deststa = NULL;
+ } else {
+ deststa = get_station_by_used_addr(ctx, dest);
+ if (deststa) {
+ snr = ctx->get_link_snr(ctx, station, deststa) -
+ get_signal_offset_by_interference(ctx,
+ station->index, deststa->index);
+ snr += ctx->get_fading_signal(ctx);
+ }
+ }
+ frame->signal = snr + NOISE_LEVEL;
+
+ noack = is_multicast_ether_addr(dest);
+
+ double choice = drand48();
+
+ for (i = 0; i < frame->tx_rates_count && !is_acked; i++) {
+
+ rate_idx = frame->tx_rates[i].idx;
+
+ /* no more rates in MRR */
+ if (rate_idx < 0)
+ break;
+
+ error_prob = ctx->get_error_prob(ctx, snr, rate_idx,
+ frame->freq, frame->data_len,
+ station, deststa);
+ for (j = 0; j < frame->tx_rates[i].count; j++) {
+ send_time += difs + pkt_duration(frame->data_len,
+ index_to_rate(rate_idx, frame->freq));
+
+ retries++;
+
+ /* skip ack/backoff/retries for noack frames */
+ if (noack) {
+ is_acked = true;
+ break;
+ }
+
+ /* TODO TXOPs */
+
+ /* backoff */
+ if (j > 0) {
+ send_time += (cw * slot_time) / 2;
+ cw = (cw << 1) + 1;
+ if (cw > queue->cw_max)
+ cw = queue->cw_max;
+ }
+
+ send_time += ack_time_usec;
+
+ if (choice > error_prob) {
+ is_acked = true;
+ break;
+ }
+
+ if (!use_fixed_random_value(ctx))
+ choice = drand48();
+ }
+ }
+
+ if (is_acked) {
+ frame->tx_rates[i-1].count = j + 1;
+ for (; i < frame->tx_rates_count; i++) {
+ frame->tx_rates[i].idx = -1;
+ frame->tx_rates[i].count = -1;
+ }
+ frame->flags |= HWSIM_TX_STAT_ACK;
+ }
+
+ /*
+ * delivery time starts after any equal or higher prio frame
+ * (or now, if none).
+ */
+ target = scheduler.current_time;
+ for (i = 0; i <= ac; i++) {
+ list_for_each_entry(tmpsta, &ctx->stations, list) {
+ tail = list_last_entry_or_null(&tmpsta->queues[i].frames,
+ struct frame, list);
+ if (tail && target < tail->job.start)
+ target = tail->job.start;
+ }
+ }
+
+ if (ctx->pcap_file) {
+ log2pcap(ctx, frame, target);
+
+ if (is_acked && !noack) {
+ struct {
+ struct frame frame;
+ uint16_t fc;
+ uint16_t dur;
+ uint8_t ra[6];
+ } __attribute__((packed, aligned(8))) ack = {
+ .fc = htole16(0xd4),
+ .dur = htole16(ack_time_usec),
+ };
+
+ memcpy(&ack.frame, frame, sizeof(ack.frame));
+ ack.frame.data_len = 10;
+ memcpy(ack.ra, frame->data + 10, 6);
+
+ log2pcap(ctx, &ack.frame,
+ target + send_time - ack_time_usec);
+ }
+ }
+
+ target += send_time;
+
+ frame->duration = send_time;
+ frame->src = station->client;
+
+ if (ctx->need_start_notify) {
+ frame->start_job.start = target - send_time;
+ frame->start_job.callback = wmediumd_notify_frame_start;
+ frame->start_job.data = ctx;
+ frame->start_job.name = "frame-start";
+ usfstl_sched_add_job(&scheduler, &frame->start_job);
+ }
+
+ frame->job.start = target;
+ frame->job.callback = wmediumd_deliver_frame;
+ frame->job.data = ctx;
+ frame->job.name = "frame";
+ usfstl_sched_add_job(&scheduler, &frame->job);
+ list_add_tail(&frame->list, &queue->frames);
+}
+
+static void wmediumd_send_to_client(struct wmediumd *ctx,
+ struct client *client,
+ struct nl_msg *msg)
+{
+ struct wmediumd_message_header hdr;
+ size_t len;
+ int ret;
+
+ switch (client->type) {
+ case CLIENT_NETLINK:
+ ret = nl_send_auto_complete(ctx->sock, msg);
+ if (ret < 0)
+ w_logf(ctx, LOG_ERR, "%s: nl_send_auto failed\n", __func__);
+ break;
+ case CLIENT_VHOST_USER:
+ len = nlmsg_total_size(nlmsg_datalen(nlmsg_hdr(msg)));
+ usfstl_vhost_user_dev_notify(client->dev, HWSIM_VQ_RX,
+ (void *)nlmsg_hdr(msg), len);
+ break;
+ case CLIENT_API_SOCK:
+ len = nlmsg_total_size(nlmsg_datalen(nlmsg_hdr(msg)));
+ hdr.type = WMEDIUMD_MSG_NETLINK;
+ hdr.data_len = len;
+ write(client->loop.fd, &hdr, sizeof(hdr));
+ write(client->loop.fd, (void *)nlmsg_hdr(msg), len);
+ wmediumd_wait_for_client_ack(ctx, client);
+ break;
+ }
+}
+
+static void wmediumd_remove_client(struct wmediumd *ctx, struct client *client)
+{
+ struct frame *frame, *tmp;
+ struct wqueue *queue;
+ struct station *station;
+ int ac;
+
+ list_for_each_entry(station, &ctx->stations, list) {
+ if (station->client == client)
+ station->client = NULL;
+ }
+
+ list_for_each_entry(station, &ctx->stations, list) {
+ for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+ queue = &station->queues[ac];
+ list_for_each_entry_safe(frame, tmp, &queue->frames,
+ list) {
+ if (frame->src == client) {
+ list_del(&frame->list);
+ usfstl_sched_del_job(&frame->job);
+ free(frame);
+ }
+ }
+ }
+ }
+
+ if (!list_empty(&client->list))
+ list_del(&client->list);
+ list_add(&client->list, &ctx->clients_to_free);
+
+ if (client->flags & WMEDIUMD_CTL_NOTIFY_TX_START)
+ ctx->need_start_notify--;
+
+ client->wait_for_ack = false;
+}
+
+/*
+ * Report transmit status to the kernel.
+ */
+static void send_tx_info_frame_nl(struct wmediumd *ctx, struct frame *frame)
+{
+ struct nl_msg *msg;
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n");
+ return;
+ }
+
+ if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id,
+ 0, NLM_F_REQUEST, HWSIM_CMD_TX_INFO_FRAME,
+ VERSION_NR) == NULL) {
+ w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__);
+ goto out;
+ }
+
+ if (nla_put(msg, HWSIM_ATTR_ADDR_TRANSMITTER, ETH_ALEN,
+ frame->sender->hwaddr) ||
+ nla_put_u32(msg, HWSIM_ATTR_FLAGS, frame->flags) ||
+ nla_put_u32(msg, HWSIM_ATTR_SIGNAL, frame->signal) ||
+ nla_put(msg, HWSIM_ATTR_TX_INFO,
+ frame->tx_rates_count * sizeof(struct hwsim_tx_rate),
+ frame->tx_rates) ||
+ nla_put_u64(msg, HWSIM_ATTR_COOKIE, frame->cookie)) {
+ w_logf(ctx, LOG_ERR, "%s: Failed to fill a payload\n", __func__);
+ goto out;
+ }
+
+ if (ctx->ctrl)
+ usfstl_sched_ctrl_sync_to(ctx->ctrl);
+ wmediumd_send_to_client(ctx, frame->src, msg);
+
+out:
+ nlmsg_free(msg);
+}
+
+/*
+ * Send a data frame to the kernel for reception at a specific radio.
+ */
+static void send_cloned_frame_msg(struct wmediumd *ctx, struct client *src,
+ struct station *dst, u8 *data, int data_len,
+ int rate_idx, int signal, int freq,
+ uint64_t cookie)
+{
+ struct client *client, *tmp;
+ struct nl_msg *msg, *cmsg = NULL;
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n");
+ return;
+ }
+
+ if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id,
+ 0, NLM_F_REQUEST, HWSIM_CMD_FRAME,
+ VERSION_NR) == NULL) {
+ w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__);
+ goto out;
+ }
+
+ if (nla_put(msg, HWSIM_ATTR_ADDR_RECEIVER, ETH_ALEN,
+ dst->hwaddr) ||
+ nla_put(msg, HWSIM_ATTR_FRAME, data_len, data) ||
+ nla_put_u32(msg, HWSIM_ATTR_RX_RATE, 1) ||
+ nla_put_u32(msg, HWSIM_ATTR_FREQ, freq) ||
+ nla_put_u32(msg, HWSIM_ATTR_SIGNAL, signal)) {
+ w_logf(ctx, LOG_ERR, "%s: Failed to fill a payload\n", __func__);
+ goto out;
+ }
+
+ w_logf(ctx, LOG_DEBUG, "cloned msg dest " MAC_FMT " (radio: " MAC_FMT ") len %d\n",
+ MAC_ARGS(dst->addr), MAC_ARGS(dst->hwaddr), data_len);
+
+ if (ctx->ctrl)
+ usfstl_sched_ctrl_sync_to(ctx->ctrl);
+
+ list_for_each_entry_safe(client, tmp, &ctx->clients, list) {
+ if (client->flags & WMEDIUMD_CTL_RX_ALL_FRAMES) {
+ if (src == client && !cmsg) {
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+
+ cmsg = nlmsg_inherit(nlh);
+ nlmsg_append(cmsg, nlmsg_data(nlh), nlmsg_datalen(nlh), 0);
+ assert(nla_put_u64(cmsg, HWSIM_ATTR_COOKIE, cookie) == 0);
+ }
+ wmediumd_send_to_client(ctx, client,
+ src == client ? cmsg : msg);
+ } else if (!dst->client || dst->client == client) {
+ wmediumd_send_to_client(ctx, client, msg);
+ }
+ }
+
+out:
+ nlmsg_free(msg);
+ if (cmsg)
+ nlmsg_free(cmsg);
+}
+
+static void wmediumd_deliver_frame(struct usfstl_job *job)
+{
+ struct wmediumd *ctx = job->data;
+ struct frame *frame = container_of(job, struct frame, job);
+ struct ieee80211_hdr *hdr = (void *) frame->data;
+ struct station *station;
+ u8 *dest = hdr->addr1;
+ u8 *src = frame->sender->addr;
+
+ list_del(&frame->list);
+
+ if (frame->flags & HWSIM_TX_STAT_ACK) {
+ /* rx the frame on the dest interface */
+ list_for_each_entry(station, &ctx->stations, list) {
+ if (memcmp(src, station->addr, ETH_ALEN) == 0)
+ continue;
+
+ if (is_multicast_ether_addr(dest)) {
+ int snr, rate_idx, signal;
+ double error_prob;
+
+ /*
+ * we may or may not receive this based on
+ * reverse link from sender -- check for
+ * each receiver.
+ */
+ snr = ctx->get_link_snr(ctx, frame->sender,
+ station);
+ snr += ctx->get_fading_signal(ctx);
+ signal = snr + NOISE_LEVEL;
+ if (signal < CCA_THRESHOLD)
+ continue;
+
+ if (set_interference_duration(ctx,
+ frame->sender->index, frame->duration,
+ signal))
+ continue;
+
+ snr -= get_signal_offset_by_interference(ctx,
+ frame->sender->index, station->index);
+ rate_idx = frame->tx_rates[0].idx;
+ error_prob = ctx->get_error_prob(ctx,
+ (double)snr, rate_idx, frame->freq,
+ frame->data_len, frame->sender,
+ station);
+
+ if (drand48() <= error_prob) {
+ w_logf(ctx, LOG_INFO, "Dropped mcast from "
+ MAC_FMT " to " MAC_FMT " at receiver\n",
+ MAC_ARGS(src), MAC_ARGS(station->addr));
+ continue;
+ }
+
+ send_cloned_frame_msg(ctx, frame->sender->client,
+ station,
+ frame->data,
+ frame->data_len,
+ 1, signal,
+ frame->freq,
+ frame->cookie);
+
+ } else if (station_has_addr(station, dest)) {
+ if (set_interference_duration(ctx,
+ frame->sender->index, frame->duration,
+ frame->signal))
+ continue;
+
+ send_cloned_frame_msg(ctx, frame->sender->client,
+ station,
+ frame->data,
+ frame->data_len,
+ 1, frame->signal,
+ frame->freq,
+ frame->cookie);
+ }
+ }
+ } else
+ set_interference_duration(ctx, frame->sender->index,
+ frame->duration, frame->signal);
+
+ send_tx_info_frame_nl(ctx, frame);
+
+ free(frame);
+}
+
+static void wmediumd_intf_update(struct usfstl_job *job)
+{
+ struct wmediumd *ctx = job->data;
+ int i, j;
+
+ for (i = 0; i < ctx->num_stas; i++)
+ for (j = 0; j < ctx->num_stas; j++) {
+ if (i == j)
+ continue;
+ // probability is used for next calc
+ ctx->intf[i * ctx->num_stas + j].prob_col =
+ ctx->intf[i * ctx->num_stas + j].duration /
+ (double)10000;
+ ctx->intf[i * ctx->num_stas + j].duration = 0;
+ }
+
+ job->start += 10000;
+ usfstl_sched_add_job(&scheduler, job);
+}
+
+static
+int nl_err_cb(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg)
+{
+ struct genlmsghdr *gnlh = nlmsg_data(&nlerr->msg);
+ struct wmediumd *ctx = arg;
+
+ w_flogf(ctx, LOG_ERR, stderr, "nl: cmd %d, seq %d: %s\n", gnlh->cmd,
+ nlerr->msg.nlmsg_seq, strerror(abs(nlerr->error)));
+
+ return NL_SKIP;
+}
+
+/*
+ * Handle events from the kernel. Process CMD_FRAME events and queue them
+ * for later delivery with the scheduler.
+ */
+static void _process_messages(struct nl_msg *msg,
+ struct wmediumd *ctx,
+ struct client *client)
+{
+ struct nlattr *attrs[HWSIM_ATTR_MAX+1];
+ /* netlink header */
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+ /* generic netlink header*/
+ struct genlmsghdr *gnlh = nlmsg_data(nlh);
+
+ struct station *sender;
+ struct frame *frame;
+ struct ieee80211_hdr *hdr;
+ u8 *src, *hwaddr, *addr;
+ void *new;
+ unsigned int i;
+
+ genlmsg_parse(nlh, 0, attrs, HWSIM_ATTR_MAX, NULL);
+
+ switch (gnlh->cmd) {
+ case HWSIM_CMD_FRAME:
+ if (attrs[HWSIM_ATTR_ADDR_TRANSMITTER]) {
+ hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]);
+
+ unsigned int data_len =
+ nla_len(attrs[HWSIM_ATTR_FRAME]);
+ char *data = (char *)nla_data(attrs[HWSIM_ATTR_FRAME]);
+ unsigned int flags =
+ nla_get_u32(attrs[HWSIM_ATTR_FLAGS]);
+ unsigned int tx_rates_len =
+ nla_len(attrs[HWSIM_ATTR_TX_INFO]);
+ struct hwsim_tx_rate *tx_rates =
+ (struct hwsim_tx_rate *)
+ nla_data(attrs[HWSIM_ATTR_TX_INFO]);
+ u64 cookie = nla_get_u64(attrs[HWSIM_ATTR_COOKIE]);
+ u32 freq;
+
+ freq = attrs[HWSIM_ATTR_FREQ] ?
+ nla_get_u32(attrs[HWSIM_ATTR_FREQ]) : 2412;
+
+ hdr = (struct ieee80211_hdr *)data;
+ src = hdr->addr2;
+
+ if (data_len < 6 + 6 + 4)
+ return;
+
+ sender = get_station_by_addr(ctx, hwaddr);
+ if (!sender) {
+ sender = get_station_by_used_addr(ctx, src);
+ if (!sender) {
+ w_flogf(ctx, LOG_ERR, stderr,
+ "Unable to find sender station by src=" MAC_FMT " nor hwaddr=" MAC_FMT "\n",
+ MAC_ARGS(src), MAC_ARGS(hwaddr));
+ return;
+ }
+ memcpy(sender->hwaddr, hwaddr, ETH_ALEN);
+ }
+
+ if (!sender->client)
+ sender->client = client;
+
+ frame = calloc(1, sizeof(*frame) + data_len);
+ if (!frame)
+ return;
+
+ memcpy(frame->data, data, data_len);
+ frame->data_len = data_len;
+ frame->flags = flags;
+ frame->cookie = cookie;
+ frame->freq = freq;
+ frame->sender = sender;
+ frame->tx_rates_count =
+ tx_rates_len / sizeof(struct hwsim_tx_rate);
+ memcpy(frame->tx_rates, tx_rates,
+ min(tx_rates_len, sizeof(frame->tx_rates)));
+ queue_frame(ctx, sender, frame);
+ }
+ break;
+ case HWSIM_CMD_ADD_MAC_ADDR:
+ if (!attrs[HWSIM_ATTR_ADDR_TRANSMITTER] ||
+ !attrs[HWSIM_ATTR_ADDR_RECEIVER])
+ break;
+ hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]);
+ addr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_RECEIVER]);
+ sender = get_station_by_addr(ctx, hwaddr);
+ if (!sender)
+ break;
+ for (i = 0; i < sender->n_addrs; i++) {
+ if (memcmp(sender->addrs[i].addr, addr, ETH_ALEN) == 0)
+ return;
+ }
+ new = realloc(sender->addrs, ETH_ALEN * (sender->n_addrs + 1));
+ if (!new)
+ break;
+ sender->addrs = new;
+ memcpy(sender->addrs[sender->n_addrs].addr, addr, ETH_ALEN);
+ sender->n_addrs += 1;
+ break;
+ case HWSIM_CMD_DEL_MAC_ADDR:
+ if (!attrs[HWSIM_ATTR_ADDR_TRANSMITTER] ||
+ !attrs[HWSIM_ATTR_ADDR_RECEIVER])
+ break;
+ hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]);
+ addr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_RECEIVER]);
+ sender = get_station_by_addr(ctx, hwaddr);
+ if (!sender)
+ break;
+ for (i = 0; i < sender->n_addrs; i++) {
+ if (memcmp(sender->addrs[i].addr, addr, ETH_ALEN))
+ continue;
+ sender->n_addrs -= 1;
+ memmove(sender->addrs[i].addr,
+ sender->addrs[sender->n_addrs].addr,
+ ETH_ALEN);
+ break;
+ }
+ break;
+ }
+}
+
+static int process_messages_cb(struct nl_msg *msg, void *arg)
+{
+ struct wmediumd *ctx = arg;
+
+ _process_messages(msg, ctx, &ctx->nl_client);
+ return 0;
+}
+
+static void wmediumd_vu_connected(struct usfstl_vhost_user_dev *dev)
+{
+ struct wmediumd *ctx = dev->server->data;
+ struct client *client;
+
+ client = calloc(1, sizeof(*client));
+ dev->data = client;
+ client->type = CLIENT_VHOST_USER;
+ client->dev = dev;
+ list_add(&client->list, &ctx->clients);
+}
+
+static void wmediumd_vu_handle(struct usfstl_vhost_user_dev *dev,
+ struct usfstl_vhost_user_buf *buf,
+ unsigned int vring)
+{
+ struct nl_msg *nlmsg;
+ char data[4096];
+ size_t len;
+
+ len = iov_read(data, sizeof(data), buf->out_sg, buf->n_out_sg);
+
+ if (!nlmsg_ok((const struct nlmsghdr *)data, len))
+ return;
+ nlmsg = nlmsg_convert((struct nlmsghdr *)data);
+ if (!nlmsg)
+ return;
+
+ _process_messages(nlmsg, dev->server->data, dev->data);
+
+ nlmsg_free(nlmsg);
+}
+
+static void wmediumd_vu_disconnected(struct usfstl_vhost_user_dev *dev)
+{
+ struct client *client = dev->data;
+
+ dev->data = NULL;
+ wmediumd_remove_client(dev->server->data, client);
+}
+
+static const struct usfstl_vhost_user_ops wmediumd_vu_ops = {
+ .connected = wmediumd_vu_connected,
+ .handle = wmediumd_vu_handle,
+ .disconnected = wmediumd_vu_disconnected,
+};
+
+static void wmediumd_api_handler(struct usfstl_loop_entry *entry)
+{
+ struct client *client = container_of(entry, struct client, loop);
+ struct wmediumd *ctx = entry->data;
+ struct wmediumd_message_header hdr;
+ enum wmediumd_message response = WMEDIUMD_MSG_ACK;
+ struct wmediumd_message_control control = {};
+ struct nl_msg *nlmsg;
+ unsigned char *data;
+ ssize_t len;
+
+ len = read(entry->fd, &hdr, sizeof(hdr));
+ if (len != sizeof(hdr))
+ goto disconnect;
+
+ /* safety valve */
+ if (hdr.data_len > 1024 * 1024)
+ goto disconnect;
+
+ data = malloc(hdr.data_len);
+ if (!data)
+ goto disconnect;
+
+ len = read(entry->fd, data, hdr.data_len);
+ if (len != hdr.data_len)
+ goto disconnect;
+
+ if (client->wait_for_ack) {
+ assert(hdr.type == WMEDIUMD_MSG_ACK);
+ assert(hdr.data_len == 0);
+ client->wait_for_ack = false;
+ /* don't send a response to a response, of course */
+ return;
+ }
+
+ switch (hdr.type) {
+ case WMEDIUMD_MSG_REGISTER:
+ if (!list_empty(&client->list)) {
+ response = WMEDIUMD_MSG_INVALID;
+ break;
+ }
+ list_add(&client->list, &ctx->clients);
+ break;
+ case WMEDIUMD_MSG_UNREGISTER:
+ if (list_empty(&client->list)) {
+ response = WMEDIUMD_MSG_INVALID;
+ break;
+ }
+ list_del_init(&client->list);
+ break;
+ case WMEDIUMD_MSG_NETLINK:
+ if (ctx->ctrl)
+ usfstl_sched_ctrl_sync_from(ctx->ctrl);
+
+ if (!nlmsg_ok((const struct nlmsghdr *)data, len)) {
+ response = WMEDIUMD_MSG_INVALID;
+ break;
+ }
+
+ nlmsg = nlmsg_convert((struct nlmsghdr *)data);
+ if (!nlmsg)
+ break;
+
+ _process_messages(nlmsg, ctx, client);
+
+ nlmsg_free(nlmsg);
+ break;
+ case WMEDIUMD_MSG_SET_CONTROL:
+ /* copy what we get and understand, leave the rest zeroed */
+ memcpy(&control, data,
+ min(sizeof(control), hdr.data_len));
+
+ if (client->flags & WMEDIUMD_CTL_NOTIFY_TX_START)
+ ctx->need_start_notify--;
+ if (control.flags & WMEDIUMD_CTL_NOTIFY_TX_START)
+ ctx->need_start_notify++;
+
+ client->flags = control.flags;
+ break;
+ case WMEDIUMD_MSG_ACK:
+ abort();
+ default:
+ response = WMEDIUMD_MSG_INVALID;
+ break;
+ }
+
+ /* return a response */
+ hdr.type = response;
+ hdr.data_len = 0;
+ len = write(entry->fd, &hdr, sizeof(hdr));
+ if (len != sizeof(hdr))
+ goto disconnect;
+
+ return;
+disconnect:
+ usfstl_loop_unregister(&client->loop);
+ wmediumd_remove_client(ctx, client);
+}
+
+static void wmediumd_api_connected(int fd, void *data)
+{
+ struct wmediumd *ctx = data;
+ struct client *client;
+
+ client = calloc(1, sizeof(*client));
+ client->type = CLIENT_API_SOCK;
+ client->loop.fd = fd;
+ client->loop.data = ctx;
+ client->loop.handler = wmediumd_api_handler;
+ usfstl_loop_register(&client->loop);
+ INIT_LIST_HEAD(&client->list);
+}
+
+/*
+ * Register with the kernel to start receiving new frames.
+ */
+static int send_register_msg(struct wmediumd *ctx)
+{
+ struct nl_sock *sock = ctx->sock;
+ struct nl_msg *msg;
+ int ret;
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n");
+ return -1;
+ }
+
+ if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id,
+ 0, NLM_F_REQUEST, HWSIM_CMD_REGISTER,
+ VERSION_NR) == NULL) {
+ w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__);
+ ret = -1;
+ goto out;
+ }
+
+ ret = nl_send_auto_complete(sock, msg);
+ if (ret < 0) {
+ w_logf(ctx, LOG_ERR, "%s: nl_send_auto failed\n", __func__);
+ ret = -1;
+ goto out;
+ }
+ ret = 0;
+
+out:
+ nlmsg_free(msg);
+ return ret;
+}
+
+static void sock_event_cb(struct usfstl_loop_entry *entry)
+{
+ struct wmediumd *ctx = entry->data;
+
+ nl_recvmsgs_default(ctx->sock);
+}
+
+/*
+ * Setup netlink socket and callbacks.
+ */
+static int init_netlink(struct wmediumd *ctx)
+{
+ struct nl_sock *sock;
+ int ret;
+
+ ctx->cb = nl_cb_alloc(NL_CB_CUSTOM);
+ if (!ctx->cb) {
+ w_logf(ctx, LOG_ERR, "Error allocating netlink callbacks\n");
+ return -1;
+ }
+
+ sock = nl_socket_alloc_cb(ctx->cb);
+ if (!sock) {
+ w_logf(ctx, LOG_ERR, "Error allocating netlink socket\n");
+ return -1;
+ }
+
+ ctx->sock = sock;
+
+ ret = genl_connect(sock);
+ if (ret < 0) {
+ w_logf(ctx, LOG_ERR, "Error connecting netlink socket ret=%d\n", ret);
+ return -1;
+ }
+
+ ctx->family_id = genl_ctrl_resolve(sock, "MAC80211_HWSIM");
+ if (ctx->family_id < 0) {
+ w_logf(ctx, LOG_ERR, "Family MAC80211_HWSIM not registered\n");
+ return -1;
+ }
+
+ nl_cb_set(ctx->cb, NL_CB_MSG_IN, NL_CB_CUSTOM, process_messages_cb, ctx);
+ nl_cb_err(ctx->cb, NL_CB_CUSTOM, nl_err_cb, ctx);
+
+ return 0;
+}
+
+/*
+ * Print the CLI help
+ */
+static void print_help(int exval)
+{
+ printf("wmediumd v%s - a wireless medium simulator\n", VERSION_STR);
+ printf("wmediumd [-h] [-V] [-l LOG_LVL] [-x FILE] -c FILE \n\n");
+
+ printf(" -h print this help and exit\n");
+ printf(" -V print version and exit\n\n");
+
+ printf(" -l LOG_LVL set the logging level\n");
+ printf(" LOG_LVL: RFC 5424 severity, values 0 - 7\n");
+ printf(" >= 3: errors are logged\n");
+ printf(" >= 5: startup msgs are logged\n");
+ printf(" >= 6: dropped packets are logged (default)\n");
+ printf(" == 7: all packets will be logged\n");
+ printf(" -c FILE set input config file\n");
+ printf(" -x FILE set input PER file\n");
+ printf(" -t socket set the time control socket\n");
+ printf(" -u socket expose vhost-user socket, don't use netlink\n");
+ printf(" -a socket expose wmediumd API socket\n");
+ printf(" -n force netlink use even with vhost-user\n");
+ printf(" -p FILE log packets to pcapng file FILE\n");
+
+ exit(exval);
+}
+
+static void init_pcapng(struct wmediumd *ctx, const char *filename)
+{
+ struct {
+ uint32_t type, blocklen, byte_order;
+ uint16_t ver_maj, ver_min;
+ uint64_t seclen;
+ uint32_t blocklen2;
+ } __attribute__((packed)) blockhdr = {
+ .type = 0x0A0D0D0A,
+ .blocklen = sizeof(blockhdr),
+ .byte_order = 0x1A2B3C4D,
+ .ver_maj = 1,
+ .ver_min = 0,
+ .seclen = -1,
+ .blocklen2 = sizeof(blockhdr),
+ };
+ struct {
+ uint32_t type, blocklen;
+ uint16_t linktype, reserved;
+ uint32_t snaplen;
+ struct {
+ uint16_t code, len;
+ uint8_t val, pad[3];
+ } opt_if_tsresol;
+ struct {
+ uint16_t code, len;
+ } opt_endofopt;
+ uint32_t blocklen2;
+ } __attribute__((packed)) idb = {
+ .type = 1,
+ .blocklen = sizeof(idb),
+ .linktype = 127, // radiotap
+ .snaplen = -1,
+ .opt_if_tsresol.code = 9,
+ .opt_if_tsresol.len = 1,
+ .opt_if_tsresol.val = 6, // usec
+ .blocklen2 = sizeof(idb),
+ };
+
+ if (!filename)
+ return;
+
+ ctx->pcap_file = fopen(filename, "w+");
+ fwrite(&blockhdr, sizeof(blockhdr), 1, ctx->pcap_file);
+ fwrite(&idb, sizeof(idb), 1, ctx->pcap_file);
+}
+
+int main(int argc, char *argv[])
+{
+ int opt;
+ struct wmediumd ctx = {};
+ char *config_file = NULL;
+ char *per_file = NULL;
+ const char *time_socket = NULL, *api_socket = NULL;
+ struct usfstl_sched_ctrl ctrl = {};
+ struct usfstl_vhost_user_server vusrv = {
+ .ops = &wmediumd_vu_ops,
+ .max_queues = HWSIM_NUM_VQS,
+ .input_queues = 1 << HWSIM_VQ_TX,
+ .protocol_features =
+ 1ULL << VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS,
+ .data = &ctx,
+ };
+ bool use_netlink, force_netlink = false;
+
+ setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
+
+ if (argc == 1) {
+ fprintf(stderr, "This program needs arguments....\n\n");
+ print_help(EXIT_FAILURE);
+ }
+
+ ctx.log_lvl = 6;
+ unsigned long int parse_log_lvl;
+ char* parse_end_token;
+
+ while ((opt = getopt(argc, argv, "hVc:l:x:t:u:a:np:")) != -1) {
+ switch (opt) {
+ case 'h':
+ print_help(EXIT_SUCCESS);
+ break;
+ case 'V':
+ printf("wmediumd v%s - a wireless medium simulator "
+ "for mac80211_hwsim\n", VERSION_STR);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'x':
+ printf("Input packet error rate file: %s\n", optarg);
+ per_file = optarg;
+ break;
+ case ':':
+ printf("wmediumd: Error - Option `%c' "
+ "needs a value\n\n", optopt);
+ print_help(EXIT_FAILURE);
+ break;
+ case 'l':
+ parse_log_lvl = strtoul(optarg, &parse_end_token, 10);
+ if ((parse_log_lvl == ULONG_MAX && errno == ERANGE) ||
+ optarg == parse_end_token || parse_log_lvl > 7) {
+ printf("wmediumd: Error - Invalid RFC 5424 severity level: "
+ "%s\n\n", optarg);
+ print_help(EXIT_FAILURE);
+ }
+ ctx.log_lvl = parse_log_lvl;
+ break;
+ case 't':
+ time_socket = optarg;
+ break;
+ case 'u':
+ vusrv.socket = optarg;
+ break;
+ case 'a':
+ api_socket = optarg;
+ break;
+ case 'n':
+ force_netlink = true;
+ break;
+ case 'p':
+ init_pcapng(&ctx, optarg);
+ break;
+ case '?':
+ printf("wmediumd: Error - No such option: "
+ "`%c'\n\n", optopt);
+ print_help(EXIT_FAILURE);
+ break;
+ }
+
+ }
+
+ if (optind < argc)
+ print_help(EXIT_FAILURE);
+
+ if (!config_file) {
+ printf("%s: config file must be supplied\n", argv[0]);
+ print_help(EXIT_FAILURE);
+ }
+
+ w_logf(&ctx, LOG_NOTICE, "Input configuration file: %s\n", config_file);
+
+ INIT_LIST_HEAD(&ctx.stations);
+ INIT_LIST_HEAD(&ctx.clients);
+ INIT_LIST_HEAD(&ctx.clients_to_free);
+
+ if (load_config(&ctx, config_file, per_file))
+ return EXIT_FAILURE;
+
+ use_netlink = force_netlink || !vusrv.socket;
+
+ /* init netlink */
+ if (use_netlink && init_netlink(&ctx) < 0)
+ return EXIT_FAILURE;
+
+ if (ctx.intf) {
+ ctx.intf_job.start = 10000; // usec
+ ctx.intf_job.name = "interference update";
+ ctx.intf_job.data = &ctx;
+ ctx.intf_job.callback = wmediumd_intf_update;
+ usfstl_sched_add_job(&scheduler, &ctx.intf_job);
+ }
+
+ if (vusrv.socket)
+ usfstl_vhost_user_server_start(&vusrv);
+
+ if (use_netlink) {
+ ctx.nl_client.type = CLIENT_NETLINK;
+ list_add(&ctx.nl_client.list, &ctx.clients);
+
+ ctx.nl_loop.handler = sock_event_cb;
+ ctx.nl_loop.data = &ctx;
+ ctx.nl_loop.fd = nl_socket_get_fd(ctx.sock);
+ usfstl_loop_register(&ctx.nl_loop);
+
+ /* register for new frames */
+ if (send_register_msg(&ctx) == 0)
+ w_logf(&ctx, LOG_NOTICE, "REGISTER SENT!\n");
+ }
+
+ if (api_socket)
+ usfstl_uds_create(api_socket, wmediumd_api_connected, &ctx);
+
+ if (time_socket) {
+ usfstl_sched_ctrl_start(&ctrl, time_socket,
+ 1000 /* nsec per usec */,
+ (uint64_t)-1 /* no ID */,
+ &scheduler);
+ vusrv.scheduler = &scheduler;
+ vusrv.ctrl = &ctrl;
+ ctx.ctrl = &ctrl;
+ } else {
+ usfstl_sched_wallclock_init(&scheduler, 1000);
+ }
+
+ while (1) {
+ if (time_socket) {
+ usfstl_sched_next(&scheduler);
+ } else {
+ usfstl_sched_wallclock_wait_and_handle(&scheduler);
+
+ if (usfstl_sched_next_pending(&scheduler, NULL))
+ usfstl_sched_next(&scheduler);
+ }
+
+ while (!list_empty(&ctx.clients_to_free)) {
+ struct client *client;
+
+ client = list_first_entry(&ctx.clients_to_free,
+ struct client, list);
+
+ list_del(&client->list);
+ free(client);
+ }
+ }
+
+ free(ctx.sock);
+ free(ctx.cb);
+ free(ctx.intf);
+ free(ctx.per_matrix);
+
+ return EXIT_SUCCESS;
+}
diff --git a/wmediumd/wmediumd.h b/wmediumd/wmediumd.h
new file mode 100644
index 0000000..4b7ca66
--- /dev/null
+++ b/wmediumd/wmediumd.h
@@ -0,0 +1,282 @@
+/*
+ * wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ * Copyright (c) 2011 cozybit Inc.
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * Author: Javier Lopez <jlopex@cozybit.com>
+ * Javier Cardona <javier@cozybit.com>
+ *
+ * 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.
+ */
+
+#ifndef WMEDIUMD_H_
+#define WMEDIUMD_H_
+
+#define HWSIM_TX_CTL_REQ_TX_STATUS 1
+#define HWSIM_TX_CTL_NO_ACK (1 << 1)
+#define HWSIM_TX_STAT_ACK (1 << 2)
+
+enum {
+ HWSIM_CMD_UNSPEC,
+ HWSIM_CMD_REGISTER,
+ HWSIM_CMD_FRAME,
+ HWSIM_CMD_TX_INFO_FRAME,
+ HWSIM_CMD_NEW_RADIO,
+ HWSIM_CMD_DEL_RADIO,
+ HWSIM_CMD_GET_RADIO,
+ HWSIM_CMD_ADD_MAC_ADDR,
+ HWSIM_CMD_DEL_MAC_ADDR,
+ __HWSIM_CMD_MAX,
+};
+#define HWSIM_CMD_MAX (_HWSIM_CMD_MAX - 1)
+
+/**
+ * enum hwsim_attrs - hwsim netlink attributes
+ *
+ * @HWSIM_ATTR_UNSPEC: unspecified attribute to catch errors
+ *
+ * @HWSIM_ATTR_ADDR_RECEIVER: MAC address of the radio device that
+ * the frame is broadcasted to
+ * @HWSIM_ATTR_ADDR_TRANSMITTER: MAC address of the radio device that
+ * the frame was broadcasted from
+ * @HWSIM_ATTR_FRAME: Data array
+ * @HWSIM_ATTR_FLAGS: mac80211 transmission flags, used to process
+ properly the frame at user space
+ * @HWSIM_ATTR_RX_RATE: estimated rx rate index for this frame at user
+ space
+ * @HWSIM_ATTR_SIGNAL: estimated RX signal for this frame at user
+ space
+ * @HWSIM_ATTR_TX_INFO: ieee80211_tx_rate array
+ * @HWSIM_ATTR_COOKIE: sk_buff cookie to identify the frame
+ * @HWSIM_ATTR_CHANNELS: u32 attribute used with the %HWSIM_CMD_CREATE_RADIO
+ * command giving the number of channels supported by the new radio
+ * @HWSIM_ATTR_RADIO_ID: u32 attribute used with %HWSIM_CMD_DESTROY_RADIO
+ * only to destroy a radio
+ * @HWSIM_ATTR_REG_HINT_ALPHA2: alpha2 for regulatoro driver hint
+ * (nla string, length 2)
+ * @HWSIM_ATTR_REG_CUSTOM_REG: custom regulatory domain index (u32 attribute)
+ * @HWSIM_ATTR_REG_STRICT_REG: request REGULATORY_STRICT_REG (flag attribute)
+ * @HWSIM_ATTR_SUPPORT_P2P_DEVICE: support P2P Device virtual interface (flag)
+ * @HWSIM_ATTR_USE_CHANCTX: used with the %HWSIM_CMD_CREATE_RADIO
+ * command to force use of channel contexts even when only a
+ * single channel is supported
+ * @HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE: used with the %HWSIM_CMD_CREATE_RADIO
+ * command to force radio removal when process that created the radio dies
+ * @HWSIM_ATTR_RADIO_NAME: Name of radio, e.g. phy666
+ * @HWSIM_ATTR_NO_VIF: Do not create vif (wlanX) when creating radio.
+ * @HWSIM_ATTR_FREQ: Frequency at which packet is transmitted or received.
+ * @__HWSIM_ATTR_MAX: enum limit
+ */
+
+
+enum {
+ HWSIM_ATTR_UNSPEC,
+ HWSIM_ATTR_ADDR_RECEIVER,
+ HWSIM_ATTR_ADDR_TRANSMITTER,
+ HWSIM_ATTR_FRAME,
+ HWSIM_ATTR_FLAGS,
+ HWSIM_ATTR_RX_RATE,
+ HWSIM_ATTR_SIGNAL,
+ HWSIM_ATTR_TX_INFO,
+ HWSIM_ATTR_COOKIE,
+ HWSIM_ATTR_CHANNELS,
+ HWSIM_ATTR_RADIO_ID,
+ HWSIM_ATTR_REG_HINT_ALPHA2,
+ HWSIM_ATTR_REG_CUSTOM_REG,
+ HWSIM_ATTR_REG_STRICT_REG,
+ HWSIM_ATTR_SUPPORT_P2P_DEVICE,
+ HWSIM_ATTR_USE_CHANCTX,
+ HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE,
+ HWSIM_ATTR_RADIO_NAME,
+ HWSIM_ATTR_NO_VIF,
+ HWSIM_ATTR_FREQ,
+ HWSIM_ATTR_PAD,
+ __HWSIM_ATTR_MAX,
+};
+#define HWSIM_ATTR_MAX (__HWSIM_ATTR_MAX - 1)
+
+#define VERSION_NR 1
+
+#define SNR_DEFAULT 30
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <syslog.h>
+#include <usfstl/sched.h>
+
+#include "list.h"
+#include "ieee80211.h"
+
+typedef uint8_t u8;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+#define TIME_FMT "%lld.%06lld"
+#define TIME_ARGS(a) ((unsigned long long)(a)->tv_sec), ((unsigned long long)(a)->tv_nsec/1000)
+
+#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
+#define MAC_ARGS(a) a[0],a[1],a[2],a[3],a[4],a[5]
+
+#ifndef min
+#define min(x,y) ((x) < (y) ? (x) : (y))
+#endif
+
+#define NOISE_LEVEL (-91)
+#define CCA_THRESHOLD (-90)
+
+extern struct usfstl_scheduler scheduler;
+
+struct wqueue {
+ struct list_head frames;
+ int cw_min;
+ int cw_max;
+};
+
+struct addr {
+ u8 addr[ETH_ALEN];
+};
+
+struct station {
+ int index;
+ u8 addr[ETH_ALEN]; /* virtual interface mac address */
+ u8 hwaddr[ETH_ALEN]; /* hardware address of hwsim radio */
+ double x, y; /* position of the station [m] */
+ double dir_x, dir_y; /* direction of the station [meter per MOVE_INTERVAL] */
+ int tx_power; /* transmission power [dBm] */
+ struct wqueue queues[IEEE80211_NUM_ACS];
+ struct list_head list;
+ struct client *client;
+ unsigned int n_addrs;
+ struct addr *addrs;
+};
+
+enum client_type {
+ CLIENT_NETLINK,
+ CLIENT_VHOST_USER,
+ CLIENT_API_SOCK,
+};
+
+struct client {
+ struct list_head list;
+ enum client_type type;
+
+ /*
+ * There's no additional data for the netlink client, we
+ * just have it as such for the link from struct station.
+ */
+
+ /* for vhost-user */
+ struct usfstl_vhost_user_dev *dev;
+
+ /* for API socket */
+ struct usfstl_loop_entry loop;
+ bool wait_for_ack;
+
+ u32 flags;
+};
+
+struct wmediumd {
+ int timerfd;
+
+ struct nl_sock *sock;
+ struct usfstl_loop_entry nl_loop;
+
+ struct usfstl_sched_ctrl *ctrl;
+
+ struct list_head clients, clients_to_free;
+ struct client nl_client;
+
+ int num_stas;
+ struct list_head stations;
+ struct station **sta_array;
+ int *snr_matrix;
+ double *error_prob_matrix;
+ struct intf_info *intf;
+ struct usfstl_job intf_job, move_job;
+#define MOVE_INTERVAL (3) /* station movement interval [sec] */
+ void *path_loss_param;
+ float *per_matrix;
+ int per_matrix_row_num;
+ int per_matrix_signal_min;
+ int fading_coefficient;
+
+ struct nl_cb *cb;
+ int family_id;
+
+ int (*get_link_snr)(struct wmediumd *, struct station *,
+ struct station *);
+ double (*get_error_prob)(struct wmediumd *, double, unsigned int, u32,
+ int, struct station *, struct station *);
+ int (*calc_path_loss)(void *, struct station *,
+ struct station *);
+ int (*get_fading_signal)(struct wmediumd *);
+
+ u8 log_lvl;
+
+ u32 need_start_notify;
+
+ FILE *pcap_file;
+};
+
+struct hwsim_tx_rate {
+ signed char idx;
+ unsigned char count;
+};
+
+struct frame {
+ struct list_head list; /* frame queue list */
+ struct usfstl_job job;
+ struct usfstl_job start_job;
+ struct client *src;
+ bool acked;
+ u64 cookie;
+ u32 freq;
+ int flags;
+ int signal;
+ int duration;
+ int tx_rates_count;
+ struct station *sender;
+ struct hwsim_tx_rate tx_rates[IEEE80211_TX_MAX_RATES];
+ size_t data_len;
+ u8 data[0]; /* frame contents */
+};
+
+struct log_distance_model_param {
+ double path_loss_exponent;
+ double Xg;
+};
+
+struct itu_model_param {
+ int nFLOORS;
+ int LF;
+};
+
+struct intf_info {
+ int signal;
+ int duration;
+ double prob_col;
+};
+
+void station_init_queues(struct station *station);
+double get_error_prob_from_snr(double snr, unsigned int rate_idx, u32 freq,
+ int frame_len);
+int set_default_per(struct wmediumd *ctx);
+int read_per_file(struct wmediumd *ctx, const char *file_name);
+int w_logf(struct wmediumd *ctx, u8 level, const char *format, ...);
+int w_flogf(struct wmediumd *ctx, u8 level, FILE *stream, const char *format, ...);
+int index_to_rate(size_t index, u32 freq);
+
+#endif /* WMEDIUMD_H_ */