diff options
| author | Open-IOV <dev@open-iov.org> | 2026-09-23 05:44:08 -0400 |
|---|---|---|
| committer | Open-IOV <dev@open-iov.org> | 2026-09-23 05:46:43 -0400 |
| commit | 2302fc09080bad51c73f293294e19fd4db464eb0 (patch) | |
| tree | c722d186347fdef5a34131e9e143b1549b1daf8e /userspace | |
| download | open-iov-2302fc09080bad51c73f293294e19fd4db464eb0.tar.gz open-iov-2302fc09080bad51c73f293294e19fd4db464eb0.zip | |
Open-IOV: initial public release
Open-IOV virtualizes NVIDIA GPUs so that unmodified guest drivers run with
real GPU functionality - CUDA first, 3D and video to follow - on unmodified
hosts, without a proprietary vGPU license and without patching any driver,
firmware or hypervisor.
Core principle: emulate the interface, pass through the work.
kernel/ open-iov-vfio.ko - host VFIO mdev driver: virtual GPU device
(PCI config space, BAR shadows, MSI-X), GSP boot emulation,
GSP-RM message-queue/RPC emulation, memory model (GPU-VA to
guest-PA discovery) and the semaphore-completion service that
advances guest UVM progress.
userspace/ libiov.so management API and examples.
tools/ guest diagnostics, firmware analysis wrappers, RM probes.
scripts/ stack manager, VM harness, build/load/test automation.
config/ hardware/version profiles (the portability surface).
docs/ documentation tree - start at docs/README.md.
wiki/ long-form firmware and protocol deep dives.
Status: initialization works end to end for a stock 570.144 Linux guest on
Turing (TU104): driver probe, nvidia-smi, the full GSP-RM control battery,
object creation and the early CUDA/UVM start-up path. GPU work forwarding -
the step that makes CUDA kernels execute on real hardware - is the active
work item; see docs/04-implementation/01-known-knowns-and-unknowns.md.
License: BSD-3-Clause for the project; GPL-2.0 for kernel modules that link
GPL-only kernel interfaces. See LICENSE and docs/09-reference/legal-and-licensing.md.
Diffstat (limited to 'userspace')
| -rw-r--r-- | userspace/LICENSE | 339 | ||||
| -rw-r--r-- | userspace/Makefile | 25 | ||||
| -rw-r--r-- | userspace/configs/generic.toml | 45 | ||||
| -rw-r--r-- | userspace/examples/Makefile | 27 | ||||
| -rw-r--r-- | userspace/examples/example_vgpu.c | 173 | ||||
| -rw-r--r-- | userspace/extern/inc/cargs.h | 185 | ||||
| -rw-r--r-- | userspace/extern/inc/toml.h | 175 | ||||
| -rw-r--r-- | userspace/extern/src/cargs.c | 460 | ||||
| -rw-r--r-- | userspace/extern/src/toml.c | 2379 | ||||
| -rw-r--r-- | userspace/include/libvgpu.h | 238 | ||||
| -rw-r--r-- | userspace/lib/Makefile | 89 | ||||
| -rw-r--r-- | userspace/lib/libiov.pc.in | 10 | ||||
| -rw-r--r-- | userspace/lib/libvgpu.c | 894 | ||||
| -rw-r--r-- | userspace/lib/libvgpu.pc.in | 10 | ||||
| -rw-r--r-- | userspace/lib/rpc_emulator.c | 528 | ||||
| -rw-r--r-- | userspace/lib/rpc_emulator.h | 53 | ||||
| -rw-r--r-- | userspace/tests/Makefile | 22 | ||||
| -rw-r--r-- | userspace/tests/test_guest_init.c | 176 | ||||
| -rw-r--r-- | userspace/tests/test_host_init.c | 68 | ||||
| -rw-r--r-- | userspace/tests/test_vgpu_userspace.c | 283 |
20 files changed, 6179 insertions, 0 deletions
diff --git a/userspace/LICENSE b/userspace/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/userspace/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/userspace/Makefile b/userspace/Makefile new file mode 100644 index 0000000..e746fce --- /dev/null +++ b/userspace/Makefile @@ -0,0 +1,25 @@ +# GSP vGPU-IO Userspace Makefile + +SUBDIRS = lib examples + +all: + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir || exit 1; \ + done + +clean: + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir clean; \ + done + +install: + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir install || exit 1; \ + done + +uninstall: + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir uninstall; \ + done + +.PHONY: all clean install uninstall $(SUBDIRS)
\ No newline at end of file diff --git a/userspace/configs/generic.toml b/userspace/configs/generic.toml new file mode 100644 index 0000000..c4ebe03 --- /dev/null +++ b/userspace/configs/generic.toml @@ -0,0 +1,45 @@ +[[config]] + [[config.gpu_config]] + vendor_id = 0x10DE + + [[config.request]] + num = 587 + v_dev_id = 0x223514F9 + p_dev_id = 0x2235 + name = "GVM GPU" + class = "Compute" + fb_len = 46016 + max_instances = 1 + ecc_support = True + multi_mdev = True + fb_res = 3136 + map_video_size = 4 + enc_cap = 100 + bar1_len = 0x800 + [config.request.display] + num_heads = 1 + max_res_x = 4096 + max_res_y = 2160 + frl_config = 60 + frl_enable = 1 + + [[config.request]] + num = 235 + v_dev_id = 0x1ED31F01 + p_dev_id = 0x1ED3 + name = "GRID RTX T10-4" + class = "Compute" + fb_len = 8192 + max_instances = 1 + ecc_support = True + multi_mdev = True + fb_res = 1024 + map_video_size = 4 + enc_cap = 100 + bar1_len = 0x800 + [config.request.display] + num_heads = 1 + max_res_x = 4096 + max_res_y = 2160 + frl_config = 60 + frl_enable = 1 diff --git a/userspace/examples/Makefile b/userspace/examples/Makefile new file mode 100644 index 0000000..065e00f --- /dev/null +++ b/userspace/examples/Makefile @@ -0,0 +1,27 @@ +# GSP vGPU-IO Examples Makefile + +PREFIX ?= /usr/local +LIBDIR ?= $(PREFIX)/lib +INCLUDEDIR ?= $(PREFIX)/include + +CC ?= gcc +CFLAGS ?= -O2 -g -Wall -Wextra -I../include +LDFLAGS ?= -L../lib -liov + +EXAMPLES = example_vgpu + +all: $(EXAMPLES) + +example_vgpu: example_vgpu.c ../lib/libiov.so + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +clean: + rm -f $(EXAMPLES) + +install: all + install -d $(DESTDIR)$(PREFIX)/bin + for ex in $(EXAMPLES); do \ + install -m 755 $$ex $(DESTDIR)$(PREFIX)/bin/; \ + done + +.PHONY: all clean install
\ No newline at end of file diff --git a/userspace/examples/example_vgpu.c b/userspace/examples/example_vgpu.c new file mode 100644 index 0000000..ede6873 --- /dev/null +++ b/userspace/examples/example_vgpu.c @@ -0,0 +1,173 @@ +/* + * GSP vGPU-IO Example Application + * Demonstrates basic VF creation, memory allocation, and BAR configuration + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <libvgpu.h> + +static volatile bool running = true; + +static void signal_handler(int sig) +{ + (void)sig; + running = false; +} + +int main(int argc, char *argv[]) +{ + const char *device_path = "/dev/gsp-vgpu"; + vgpu_device_t device = NULL; + vgpu_vf_t vf = NULL; + vgpu_handle_t handle = 0; + uint64_t va = 0; + uint64_t dma_addr = 0; + int ret; + + if (argc > 1) + device_path = argv[1]; + + printf("GSP vGPU-IO Example Application\n"); + printf("Device: %s\n", device_path); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + /* Open device */ + ret = vgpu_open(device_path, &device); + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to open device: %s\n", vgpu_strerror(ret)); + return 1; + } + printf("Device opened successfully\n"); + + /* Get device info */ + char info[256]; + ret = vgpu_get_info(device, info, sizeof(info)); + if (ret == VGPU_OK) + printf("Device info: %s\n", info); + + /* Create VF */ + vgpu_vf_config_t config = { + .id = 1, + .bar1_offset = 0x10000000, + .bar1_size = 0x40000, + .pt_base = 0, + .pt_size = 0, + .cmd_base = 0, + .cmd_size = 0, + .max_memory_mb = 1024, + .num_engines = 4, + }; + + ret = vgpu_vf_create(device, &config, &vf); + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to create VF: %s\n", vgpu_strerror(ret)); + vgpu_close(device); + return 1; + } + printf("VF %u created\n", config.id); + + /* Configure BAR1 */ + ret = vgpu_bar_config(vf, 0x10000000, 0x40000); + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to configure BAR: %s\n", vgpu_strerror(ret)); + vgpu_vf_destroy(vf); + vgpu_close(device); + return 1; + } + printf("BAR1 configured\n"); + + /* Allocate VRAM */ + const uint64_t alloc_size = 1024 * 1024; /* 1 MB */ + ret = vgpu_mem_alloc(vf, alloc_size, VGPU_MEM_VRAM, 0x1000, 0, + 0, &handle, &va); + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to allocate memory: %s\n", vgpu_strerror(ret)); + vgpu_vf_destroy(vf); + vgpu_close(device); + return 1; + } + printf("Allocated %llu bytes at VA 0x%llx (handle 0x%llx)\n", + (unsigned long long)alloc_size, (unsigned long long)va, + (unsigned long long)handle); + + /* Map memory */ + uint64_t mapped_va = 0; + ret = vgpu_mem_map(vf, handle, va, alloc_size, 0x3, 0, 0, &mapped_va); + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to map memory: %s\n", vgpu_strerror(ret)); + vgpu_mem_free(vf, handle); + vgpu_vf_destroy(vf); + vgpu_close(device); + return 1; + } + printf("Mapped at 0x%llx\n", (unsigned long long)mapped_va); + + /* Pin for DMA */ + ret = vgpu_mem_pin(vf, handle, 0, alloc_size, VGPU_PIN_READ | VGPU_PIN_WRITE, &dma_addr); + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to pin memory: %s\n", vgpu_strerror(ret)); + } else { + printf("Pinned for DMA at 0x%llx\n", (unsigned long long)dma_addr); + } + + /* Protect with WPR (write) */ + ret = vgpu_wpr_protect_write(vf, va, alloc_size); + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to protect with WPR: %s\n", vgpu_strerror(ret)); + } else { + printf("WPR write protection enabled\n"); + } + + /* Set WPR timeout */ + ret = vgpu_wpr_set_timeout(vf, 1000000000ULL); /* 1 second */ + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to set WPR timeout: %s\n", vgpu_strerror(ret)); + } + + /* Schedule work */ + ret = vgpu_schedule(vf, handle, 0, 0, 0, 0); + if (ret != VGPU_OK) { + fprintf(stderr, "Failed to schedule: %s\n", vgpu_strerror(ret)); + } else { + printf("Work scheduled\n"); + } + + /* Run loop */ + printf("\nRunning... Press Ctrl+C to exit\n"); + while (running) { + ret = vgpu_process_events(device, 1000); + if (ret != VGPU_OK && ret != VGPU_ERR_TIMEOUT) { + fprintf(stderr, "Event processing error: %s\n", vgpu_strerror(ret)); + } + } + + printf("\nShutting down...\n"); + + /* Unprotect WPR */ + vgpu_wpr_unprotect(vf, va); + + /* Unpin */ + if (dma_addr) + vgpu_mem_unpin(vf, handle, 0, alloc_size); + + /* Unmap */ + vgpu_mem_unmap(vf, handle, va, alloc_size); + + /* Free memory */ + vgpu_mem_free(vf, handle); + + /* Destroy VF */ + vgpu_vf_destroy(vf); + + /* Close device */ + vgpu_close(device); + + printf("Done\n"); + return 0; +}
\ No newline at end of file diff --git a/userspace/extern/inc/cargs.h b/userspace/extern/inc/cargs.h new file mode 100644 index 0000000..7d9dacd --- /dev/null +++ b/userspace/extern/inc/cargs.h @@ -0,0 +1,185 @@ +/* +MIT License + +Copyright (c) 2022 Leonard Iklé + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +#pragma once + +/** + * This is a simple alternative cross-platform implementation of getopt, which + * is used to parse argument strings submitted to the executable (argc and argv + * which are received in the main function). + */ + +#ifndef CAG_LIBRARY_H +#define CAG_LIBRARY_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> + +#if defined(_WIN32) || defined(__CYGWIN__) +#define CAG_EXPORT __declspec(dllexport) +#define CAG_IMPORT __declspec(dllimport) +#elif __GNUC__ >= 4 +#define CAG_EXPORT __attribute__((visibility("default"))) +#define CAG_IMPORT __attribute__((visibility("default"))) +#else +#define CAG_EXPORT +#define CAG_IMPORT +#endif + +#if defined(CAG_SHARED) +#if defined(CAG_EXPORTS) +#define CAG_PUBLIC CAG_EXPORT +#else +#define CAG_PUBLIC CAG_IMPORT +#endif +#else +#define CAG_PUBLIC +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * An option is used to describe a flag/argument option submitted when the + * program is run. + */ +typedef struct cag_option +{ + const char identifier; + const char *access_letters; + const char *access_name; + const char *value_name; + const char *description; +} cag_option; + +/** + * A context is used to iterate over all options provided. It stores the parsing + * state. + */ +typedef struct cag_option_context +{ + const struct cag_option *options; + size_t option_count; + int argc; + char **argv; + int index; + int inner_index; + bool forced_end; + char identifier; + char *value; +} cag_option_context; + +/** + * This is just a small macro which calculates the size of an array. + */ +#define CAG_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/** + * @brief Prints all options to the terminal. + * + * This function prints all options to the terminal. This can be used to + * generate the output for a "--help" option. + * + * @param options The options which will be printed. + * @param option_count The option count which will be printed. + * @param destination The destination where the output will be printed. + */ +CAG_PUBLIC void cag_option_print(const cag_option *options, size_t option_count, + FILE *destination); + +/** + * @brief Prepare argument options context for parsing. + * + * This function prepares the context for iteration and initializes the context + * with the supplied options and arguments. After the context has been prepared, + * it can be used to fetch arguments from it. + * + * @param context The context which will be initialized. + * @param options The registered options which are available for the program. + * @param option_count The amount of options which are available for the + * program. + * @param argc The amount of arguments the user supplied in the main function. + * @param argv A pointer to the arguments of the main function. + */ +CAG_PUBLIC void cag_option_prepare(cag_option_context *context, + const cag_option *options, size_t option_count, int argc, char **argv); + +/** + * @brief Fetches an option from the argument list. + * + * This function fetches a single option from the argument list. The context + * will be moved to that item. Information can be extracted from the context + * after the item has been fetched. + * The arguments will be re-ordered, which means that non-option arguments will + * be moved to the end of the argument list. After all options have been + * fetched, all non-option arguments will be positioned after the index of + * the context. + * + * @param context The context from which we will fetch the option. + * @return Returns true if there was another option or false if the end is + * reached. + */ +CAG_PUBLIC bool cag_option_fetch(cag_option_context *context); + +/** + * @brief Gets the identifier of the option. + * + * This function gets the identifier of the option, which should be unique to + * this option and can be used to determine what kind of option this is. + * + * @param context The context from which the option was fetched. + * @return Returns the identifier of the option. + */ +CAG_PUBLIC char cag_option_get(const cag_option_context *context); + +/** + * @brief Gets the value from the option. + * + * This function gets the value from the option, if any. If the option does not + * contain a value, this function will return NULL. + * + * @param context The context from which the option was fetched. + * @return Returns a pointer to the value or NULL if there is no value. + */ +CAG_PUBLIC const char *cag_option_get_value(const cag_option_context *context); + +/** + * @brief Gets the current index of the context. + * + * This function gets the index within the argv arguments of the context. The + * context always points to the next item which it will inspect. This is + * particularly useful to inspect the original argument array, or to get + * non-option arguments after option fetching has finished. + * + * @param context The context from which the option was fetched. + * @return Returns the current index of the context. + */ +CAG_PUBLIC int cag_option_get_index(const cag_option_context *context); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/userspace/extern/inc/toml.h b/userspace/extern/inc/toml.h new file mode 100644 index 0000000..19dc3d2 --- /dev/null +++ b/userspace/extern/inc/toml.h @@ -0,0 +1,175 @@ +/* + MIT License + + Copyright (c) CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef TOML_H +#define TOML_H + +#ifdef _MSC_VER +#pragma warning(disable: 4996) +#endif + +#include <stdint.h> +#include <stdio.h> + +#ifdef __cplusplus +#define TOML_EXTERN extern "C" +#else +#define TOML_EXTERN extern +#endif + +typedef struct toml_timestamp_t toml_timestamp_t; +typedef struct toml_table_t toml_table_t; +typedef struct toml_array_t toml_array_t; +typedef struct toml_datum_t toml_datum_t; + +/* Parse a file. Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz); + +/* Parse a string containing the full config. + * Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t *toml_parse(char *conf, /* NUL terminated, please. */ + char *errbuf, int errbufsz); + +/* Free the table returned by toml_parse() or toml_parse_file(). Once + * this function is called, any handles accessed through this tab + * directly or indirectly are no longer valid. + */ +TOML_EXTERN void toml_free(toml_table_t *tab); + +/* Timestamp types. The year, month, day, hour, minute, second, z + * fields may be NULL if they are not relevant. e.g. In a DATE + * type, the hour, minute, second and z fields will be NULLs. + */ +struct toml_timestamp_t { + struct { /* internal. do not use. */ + int year, month, day; + int hour, minute, second, millisec; + char z[10]; + } __buffer; + int *year, *month, *day; + int *hour, *minute, *second, *millisec; + char *z; +}; + +/*----------------------------------------------------------------- + * Enhanced access methods + */ +struct toml_datum_t { + int ok; + union { + toml_timestamp_t *ts; /* ts must be freed after use */ + char *s; /* string value. s must be freed after use */ + int b; /* bool value */ + int64_t i; /* int value */ + double d; /* double value */ + } u; +}; + +/* on arrays: */ +/* ... retrieve size of array. */ +TOML_EXTERN int toml_array_nelem(const toml_array_t *arr); +/* ... retrieve values using index. */ +TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx); +/* ... retrieve array or table using index. */ +TOML_EXTERN toml_array_t *toml_array_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_table_t *toml_table_at(const toml_array_t *arr, int idx); + +/* on tables: */ +/* ... retrieve the key in table at keyidx. Return 0 if out of range. */ +TOML_EXTERN const char *toml_key_in(const toml_table_t *tab, int keyidx); +/* ... returns 1 if key exists in tab, 0 otherwise */ +TOML_EXTERN int toml_key_exists(const toml_table_t *tab, const char *key); +/* ... retrieve values using key. */ +TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t *arr, + const char *key); +TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key); +TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t *arr, const char *key); +TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t *arr, + const char *key); +TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t *arr, + const char *key); +/* .. retrieve array or table using key. */ +TOML_EXTERN toml_array_t *toml_array_in(const toml_table_t *tab, + const char *key); +TOML_EXTERN toml_table_t *toml_table_in(const toml_table_t *tab, + const char *key); + +/*----------------------------------------------------------------- + * lesser used + */ +/* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */ +TOML_EXTERN char toml_array_kind(const toml_array_t *arr); + +/* For array kind 'v'alue, return the type of values + i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed + 0 if unknown +*/ +TOML_EXTERN char toml_array_type(const toml_array_t *arr); + +/* Return the key of an array */ +TOML_EXTERN const char *toml_array_key(const toml_array_t *arr); + +/* Return the number of key-values in a table */ +TOML_EXTERN int toml_table_nkval(const toml_table_t *tab); + +/* Return the number of arrays in a table */ +TOML_EXTERN int toml_table_narr(const toml_table_t *tab); + +/* Return the number of sub-tables in a table */ +TOML_EXTERN int toml_table_ntab(const toml_table_t *tab); + +/* Return the key of a table*/ +TOML_EXTERN const char *toml_table_key(const toml_table_t *tab); + +/*-------------------------------------------------------------- + * misc + */ +TOML_EXTERN int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret); +TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); +TOML_EXTERN void toml_set_memutil(void *(*xxmalloc)(size_t), + void (*xxfree)(void *)); + +/*-------------------------------------------------------------- + * deprecated + */ +/* A raw value, must be processed by toml_rto* before using. */ +typedef const char *toml_raw_t; +TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key); +TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t *arr, int idx); +TOML_EXTERN int toml_rtos(toml_raw_t s, char **ret); +TOML_EXTERN int toml_rtob(toml_raw_t s, int *ret); +TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t *ret); +TOML_EXTERN int toml_rtod(toml_raw_t s, double *ret); +TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double *ret, char *buf, int buflen); +TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t *ret); + +#endif /* TOML_H */ diff --git a/userspace/extern/src/cargs.c b/userspace/extern/src/cargs.c new file mode 100644 index 0000000..4f95e18 --- /dev/null +++ b/userspace/extern/src/cargs.c @@ -0,0 +1,460 @@ +/* +MIT License + +Copyright (c) 2022 Leonard Iklé + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +#include <assert.h> +#include <cargs.h> +#include <memory.h> +#include <stdio.h> +#include <string.h> + +#define CAG_OPTION_PRINT_DISTANCE 4 +#define CAG_OPTION_PRINT_MIN_INDENTION 20 + +static void cag_option_print_value(const cag_option *option, + size_t *accessor_length, FILE *destination) +{ + if (option->value_name != NULL) { + *accessor_length += fprintf(destination, "=%s", option->value_name); + } +} + +static void cag_option_print_letters(const cag_option *option, bool *first, + size_t *accessor_length, FILE *destination) +{ + const char *access_letter; + access_letter = option->access_letters; + if (access_letter != NULL) { + while (*access_letter) { + if (*first) { + *accessor_length += fprintf(destination, "-%c", *access_letter); + *first = false; + } else { + *accessor_length += fprintf(destination, ", -%c", *access_letter); + } + ++access_letter; + } + } +} + +static void cag_option_print_name(const cag_option *option, bool *first, + size_t *accessor_length, FILE *destination) +{ + if (option->access_name != NULL) { + if (*first) { + *accessor_length += fprintf(destination, "--%s", option->access_name); + } else { + *accessor_length += fprintf(destination, ", --%s", option->access_name); + } + } +} + +static size_t cag_option_get_print_indention(const cag_option *options, + size_t option_count) +{ + size_t option_index, indention, result; + const cag_option *option; + + result = CAG_OPTION_PRINT_MIN_INDENTION; + + for (option_index = 0; option_index < option_count; ++option_index) { + indention = CAG_OPTION_PRINT_DISTANCE; + option = &options[option_index]; + if (option->access_letters != NULL && *option->access_letters) { + indention += strlen(option->access_letters) * 4 - 2; + if (option->access_name != NULL) { + indention += strlen(option->access_name) + 4; + } + } else if (option->access_name != NULL) { + indention += strlen(option->access_name) + 2; + } + + if (option->value_name != NULL) { + indention += strlen(option->value_name) + 1; + } + + if (indention > result) { + result = indention; + } + } + + return result; +} + +void cag_option_print(const cag_option *options, size_t option_count, + FILE *destination) +{ + size_t option_index, indention, i, accessor_length; + const cag_option *option; + bool first; + + indention = cag_option_get_print_indention(options, option_count); + + for (option_index = 0; option_index < option_count; ++option_index) { + option = &options[option_index]; + accessor_length = 0; + first = true; + + fputs(" ", destination); + + cag_option_print_letters(option, &first, &accessor_length, destination); + cag_option_print_name(option, &first, &accessor_length, destination); + cag_option_print_value(option, &accessor_length, destination); + + for (i = accessor_length; i < indention; ++i) { + fputs(" ", destination); + } + + fputs(" ", destination); + fputs(option->description, destination); + + fprintf(destination, "\n"); + } +} + +void cag_option_prepare(cag_option_context *context, const cag_option *options, + size_t option_count, int argc, char **argv) +{ + // This just initialized the values to the beginning of all the arguments. + context->options = options; + context->option_count = option_count; + context->argc = argc; + context->argv = argv; + context->index = 1; + context->inner_index = 0; + context->forced_end = false; +} + +static const cag_option *cag_option_find_by_name(cag_option_context *context, + char *name, size_t name_size) +{ + const cag_option *option; + size_t i; + + // We loop over all the available options and stop as soon as we have found + // one. We don't use any hash map table, since there won't be that many + // arguments anyway. + for (i = 0; i < context->option_count; ++i) { + option = &context->options[i]; + + // The option might not have an item name, we can just skip those. + if (option->access_name == NULL) { + continue; + } + + // Try to compare the name of the access name. We can use the name_size or + // this comparison, since we are guaranteed to have null-terminated access + // names. + if (strncmp(option->access_name, name, name_size) == 0) { + return option; + } + } + + return NULL; +} + +static const cag_option *cag_option_find_by_letter(cag_option_context *context, + char letter) +{ + const cag_option *option; + size_t i; + + // We loop over all the available options and stop as soon as we have found + // one. We don't use any look up table, since there won't be that many + // arguments anyway. + for (i = 0; i < context->option_count; ++i) { + option = &context->options[i]; + + // If this option doesn't have any access letters we will skip them. + if (option->access_letters == NULL) { + continue; + } + + // Verify whether this option has the access letter in it's access letter + // string. If it does, then this is our option. + if (strchr(option->access_letters, letter) != NULL) { + return option; + } + } + + return NULL; +} + +static void cag_option_parse_value(cag_option_context *context, + const cag_option *option, char **c) +{ + // And now let's check whether this option is supposed to have a value, which + // is the case if there is a value name set. The value can be either submitted + // with a '=' sign or a space, which means we would have to jump over to the + // next argv index. This is somewhat ugly, but we do it to behave the same as + // the other option parsers. + if (option->value_name != NULL) { + if (**c == '=') { + context->value = ++(*c); + } else { + // If the next index is larger or equal to the argument count, then the + // parameter for this option is missing. The user will know about this, + // since the value pointer of the context will be NULL because we don't + // set it here in that case. + if (context->argc > context->index + 1) { + // We consider this argv to be the value, no matter what the contents + // are. + ++context->index; + *c = context->argv[context->index]; + context->value = *c; + } + } + + // Move c to the end of the value, to not confuse the caller about our + // position. + while (**c) { + ++(*c); + } + } +} + +static void cag_option_parse_access_name(cag_option_context *context, char **c) +{ + const cag_option *option; + char *n; + + // Now we need to extract the access name, which is any symbol up to a '=' or + // a '\0'. + n = *c; + while (**c && **c != '=') { + ++*c; + } + + // Now this will obviously always be true, but we are paranoid. Sometimes. It + // doesn't hurt to check. + assert(*c >= n); + + // Figure out which option this name belongs to. This might return NULL if the + // name is not registered, which means the user supplied an unknown option. In + // that case we return true to indicate that we finished with this option. We + // have to skip the value parsing since we don't know whether the user thinks + // this option has one or not. Since we don't set any identifier specifically, + // it will remain '?' within the context. + option = cag_option_find_by_name(context, n, (size_t)(*c - n)); + if (option == NULL) { + // Since this option is invalid, we will move on to the next index. There is + // nothing we can do about this. + ++context->index; + return; + } + + // We found an option and now we can specify the identifier within the + // context. + context->identifier = option->identifier; + + // And now we try to parse the value. This function will also check whether + // this option is actually supposed to have a value. + cag_option_parse_value(context, option, c); + + // And finally we move on to the next index. + ++context->index; +} + +static void cag_option_parse_access_letter(cag_option_context *context, + char **c) +{ + const cag_option *option; + char *n = *c; + char *v; + + // Figure out which option this letter belongs to. This might return NULL if + // the letter is not registered, which means the user supplied an unknown + // option. In that case we return true to indicate that we finished with this + // option. We have to skip the value parsing since we don't know whether the + // user thinks this option has one or not. Since we don't set any identifier + // specifically, it will remain '?' within the context. + option = cag_option_find_by_letter(context, n[context->inner_index]); + if (option == NULL) { + ++context->index; + context->inner_index = 0; + return; + } + + // We found an option and now we can specify the identifier within the + // context. + context->identifier = option->identifier; + + // And now we try to parse the value. This function will also check whether + // this option is actually supposed to have a value. + v = &n[++context->inner_index]; + cag_option_parse_value(context, option, &v); + + // Check whether we reached the end of this option argument. + if (*v == '\0') { + ++context->index; + context->inner_index = 0; + } +} + +static void cag_option_shift(cag_option_context *context, int start, int option, + int end) +{ + char *tmp; + int a_index, shift_index, shift_count, left_index, right_index; + + shift_count = option - start; + + // There is no shift is required if the start and the option have the same + // index. + if (shift_count == 0) { + return; + } + + // Lets loop through the option strings first, which we will move towards the + // beginning. + for (a_index = option; a_index < end; ++a_index) { + // First remember the current option value, because we will have to save + // that later at the beginning. + tmp = context->argv[a_index]; + + // Let's loop over all option values and shift them one towards the end. + // This will override the option value we just stored temporarily. + for (shift_index = 0; shift_index < shift_count; ++shift_index) { + left_index = a_index - shift_index; + right_index = a_index - shift_index - 1; + context->argv[left_index] = context->argv[right_index]; + } + + // Now restore the saved option value at the beginning. + context->argv[a_index - shift_count] = tmp; + } + + // The new index will be before all non-option values, in such a way that they + // all will be moved again in the next fetch call. + context->index = end - shift_count; +} + +static bool cag_option_is_argument_string(const char *c) +{ + return *c == '-' && *(c + 1) != '\0'; +} + +static int cag_option_find_next(cag_option_context *context) +{ + int next_index, next_option_index; + char *c; + + // Prepare to search the next option at the next index. + next_index = context->index; + next_option_index = next_index; + + // Grab a pointer to the string and verify that it is not the end. If it is + // the end, we have to return false to indicate that we finished. + c = context->argv[next_option_index]; + if (context->forced_end || c == NULL) { + return -1; + } + + // Check whether it is a '-'. We need to find the next option - and an option + // always starts with a '-'. If there is a string "-\0", we don't consider it + // as an option neither. + while (!cag_option_is_argument_string(c)) { + c = context->argv[++next_option_index]; + if (c == NULL) { + // We reached the end and did not find any argument anymore. Let's tell + // our caller that we reached the end. + return -1; + } + } + + // Indicate that we found an option which can be processed. The index of the + // next option will be returned. + return next_option_index; +} + +bool cag_option_fetch(cag_option_context *context) +{ + char *c; + int old_index, new_index; + + // Reset our identifier to a question mark, which indicates an "unknown" + // option. The value is set to NULL, to make sure we are not carrying the + // parameter from the previous option to this one. + context->identifier = '?'; + context->value = NULL; + + // Check whether there are any options left to parse and remember the old + // index as well as the new index. In the end we will move the option junk to + // the beginning, so that non option arguments can be read. + old_index = context->index; + new_index = cag_option_find_next(context); + if (new_index >= 0) { + context->index = new_index; + } else { + return false; + } + + // Grab a pointer to the beginning of the option. At this point, the next + // character must be a '-', since if it was not the prepare function would + // have returned false. We will skip that symbol and proceed. + c = context->argv[context->index]; + assert(*c == '-'); + ++c; + + // Check whether this is a long option, starting with a double "--". + if (*c == '-') { + ++c; + + // This might be a double "--" which indicates the end of options. If this + // is the case, we will not move to the next index. That ensures that + // another call to the fetch function will not skip the "--". + if (*c == '\0') { + context->forced_end = true; + } else { + // We parse now the access name. All information about it will be written + // to the context. + cag_option_parse_access_name(context, &c); + } + } else { + // This is no long option, so we can just parse an access letter. + cag_option_parse_access_letter(context, &c); + } + + // Move the items so that the options come first followed by non-option + // arguments. + cag_option_shift(context, old_index, new_index, context->index); + + return context->forced_end == false; +} + +char cag_option_get(const cag_option_context *context) +{ + // We just return the identifier here. + return context->identifier; +} + +const char *cag_option_get_value(const cag_option_context *context) +{ + // We just return the internal value pointer of the context. + return context->value; +} + +int cag_option_get_index(const cag_option_context *context) +{ + // Either we point to a value item, + return context->index; +} diff --git a/userspace/extern/src/toml.c b/userspace/extern/src/toml.c new file mode 100644 index 0000000..2f4dc03 --- /dev/null +++ b/userspace/extern/src/toml.c @@ -0,0 +1,2379 @@ +/* + + MIT License + + Copyright (c) CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ +#define _POSIX_C_SOURCE 200809L +#include "toml.h" +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static void *(*ppmalloc)(size_t) = malloc; +static void (*ppfree)(void *) = free; + +void toml_set_memutil(void *(*xxmalloc)(size_t), void (*xxfree)(void *)) { + if (xxmalloc) + ppmalloc = xxmalloc; + if (xxfree) + ppfree = xxfree; +} + +#define MALLOC(a) ppmalloc(a) +#define FREE(a) ppfree(a) + +#define malloc(x) error - forbidden - use MALLOC instead +#define free(x) error - forbidden - use FREE instead +#define calloc(x, y) error - forbidden - use CALLOC instead + +static void *CALLOC(size_t nmemb, size_t sz) { + int nb = sz * nmemb; + void *p = MALLOC(nb); + if (p) { + memset(p, 0, nb); + } + return p; +} + +// some old platforms define strdup macro -- drop it. +#undef strdup +#define strdup(x) error - forbidden - use STRDUP instead + +static char *STRDUP(const char *s) { + int len = strlen(s); + char *p = MALLOC(len + 1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +// some old platforms define strndup macro -- drop it. +#undef strndup +#define strndup(x) error - forbiden - use STRNDUP instead + +static char *STRNDUP(const char *s, size_t n) { + size_t len = strnlen(s, n); + char *p = MALLOC(len + 1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +/** + * Convert a char in utf8 into UCS, and store it in *ret. + * Return #bytes consumed or -1 on failure. + */ +int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret) { + const unsigned char *buf = (const unsigned char *)orig; + unsigned i = *buf++; + int64_t v; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (0 == (i >> 7)) { + if (len < 1) + return -1; + v = i; + return *ret = v, 1; + } + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (0x6 == (i >> 5)) { + if (len < 2) + return -1; + v = i & 0x1f; + for (int j = 0; j < 1; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (0xE == (i >> 4)) { + if (len < 3) + return -1; + v = i & 0x0F; + for (int j = 0; j < 2; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x1E == (i >> 3)) { + if (len < 4) + return -1; + v = i & 0x07; + for (int j = 0; j < 3; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x3E == (i >> 2)) { + if (len < 5) + return -1; + v = i & 0x03; + for (int j = 0; j < 4; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x7e == (i >> 1)) { + if (len < 6) + return -1; + v = i & 0x01; + for (int j = 0; j < 5; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + return -1; +} + +/** + * Convert a UCS char to utf8 code, and return it in buf. + * Return #bytes used in buf to encode the char, or + * -1 on error. + */ +int toml_ucs_to_utf8(int64_t code, char buf[6]) { + /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 + */ + /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well + * as 0xfffe and 0xffff (UCS noncharacters) should not appear in + * conforming UTF-8 streams. + */ + if (0xd800 <= code && code <= 0xdfff) + return -1; + if (0xfffe <= code && code <= 0xffff) + return -1; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (code < 0) + return -1; + if (code <= 0x7F) { + buf[0] = (unsigned char)code; + return 1; + } + + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (code <= 0x000007FF) { + buf[0] = (unsigned char) (0xc0 | (code >> 6)); + buf[1] = (unsigned char) (0x80 | (code & 0x3f)); + return 2; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x0000FFFF) { + buf[0] = (unsigned char) (0xe0 | (code >> 12)); + buf[1] = (unsigned char) (0x80 | ((code >> 6) & 0x3f)); + buf[2] = (unsigned char) (0x80 | (code & 0x3f)); + return 3; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x001FFFFF) { + buf[0] = (unsigned char) (0xf0 | (code >> 18)); + buf[1] = (unsigned char) (0x80 | ((code >> 12) & 0x3f)); + buf[2] = (unsigned char) (0x80 | ((code >> 6) & 0x3f)); + buf[3] = (unsigned char) (0x80 | (code & 0x3f)); + return 4; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x03FFFFFF) { + buf[0] = (unsigned char) (0xf8 | (code >> 24)); + buf[1] = (unsigned char) (0x80 | ((code >> 18) & 0x3f)); + buf[2] = (unsigned char) (0x80 | ((code >> 12) & 0x3f)); + buf[3] = (unsigned char) (0x80 | ((code >> 6) & 0x3f)); + buf[4] = (unsigned char) (0x80 | (code & 0x3f)); + return 5; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x7FFFFFFF) { + buf[0] = (unsigned char) (0xfc | (code >> 30)); + buf[1] = (unsigned char) (0x80 | ((code >> 24) & 0x3f)); + buf[2] = (unsigned char) (0x80 | ((code >> 18) & 0x3f)); + buf[3] = (unsigned char) (0x80 | ((code >> 12) & 0x3f)); + buf[4] = (unsigned char) (0x80 | ((code >> 6) & 0x3f)); + buf[5] = (unsigned char) (0x80 | (code & 0x3f)); + return 6; + } + + return -1; +} + +/* + * TOML has 3 data structures: value, array, table. + * Each of them can have identification key. + */ +typedef struct toml_keyval_t toml_keyval_t; +struct toml_keyval_t { + const char *key; /* key to this value */ + const char *val; /* the raw value */ +}; + +typedef struct toml_arritem_t toml_arritem_t; +struct toml_arritem_t { + int valtype; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, + 'D'ate, 'T'imestamp */ + char *val; + toml_array_t *arr; + toml_table_t *tab; +}; + +struct toml_array_t { + const char *key; /* key to this array */ + int kind; /* element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed */ + int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, + 'D'ate, 'T'imestamp, 'm'ixed */ + + int nitem; /* number of elements */ + toml_arritem_t *item; +}; + +struct toml_table_t { + const char *key; /* key to this table */ + bool implicit; /* table was created implicitly */ + bool readonly; /* no more modification allowed */ + + /* key-values in the table */ + int nkval; + toml_keyval_t **kval; + + /* arrays in the table */ + int narr; + toml_array_t **arr; + + /* tables in the table */ + int ntab; + toml_table_t **tab; +}; + +static inline void xfree(const void *x) { + if (x) + FREE((void *)(intptr_t)x); +} + +enum tokentype_t { + INVALID, + DOT, + COMMA, + EQUAL, + LBRACE, + RBRACE, + NEWLINE, + LBRACKET, + RBRACKET, + STRING, +}; +typedef enum tokentype_t tokentype_t; + +typedef struct token_t token_t; +struct token_t { + tokentype_t tok; + int lineno; + char *ptr; /* points into context->start */ + int len; + int eof; +}; + +typedef struct context_t context_t; +struct context_t { + char *start; + char *stop; + char *errbuf; + int errbufsz; + + token_t tok; + toml_table_t *root; + toml_table_t *curtab; + + struct { + int top; + char *key[10]; + token_t tok[10]; + } tpath; +}; + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define FLINE __FILE__ ":" TOSTRING(__LINE__) + +static int next_token(context_t *ctx, int dotisspecial); + +/* + Error reporting. Call when an error is detected. Always return -1. +*/ +static int e_outofmemory(context_t *ctx, const char *fline) { + snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline); + return -1; +} + +static int e_internal(context_t *ctx, const char *fline) { + snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline); + return -1; +} + +static int e_syntax(context_t *ctx, int lineno, const char *msg) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static int e_badkey(context_t *ctx, int lineno) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno); + return -1; +} + +static int e_keyexists(context_t *ctx, int lineno) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno); + return -1; +} + +static int e_forbid(context_t *ctx, int lineno, const char *msg) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static void *expand(void *p, int sz, int newsz) { + void *s = MALLOC(newsz); + if (!s) + return 0; + + memcpy(s, p, sz); + FREE(p); + return s; +} + +static void **expand_ptrarr(void **p, int n) { + void **s = MALLOC((n + 1) * sizeof(void *)); + if (!s) + return 0; + + s[n] = 0; + memcpy(s, p, n * sizeof(void *)); + FREE(p); + return s; +} + +static toml_arritem_t *expand_arritem(toml_arritem_t *p, int n) { + toml_arritem_t *pp = expand(p, n * sizeof(*p), (n + 1) * sizeof(*p)); + if (!pp) + return 0; + + memset(&pp[n], 0, sizeof(pp[n])); + return pp; +} + +static char *norm_lit_str(const char *src, int srclen, int multiline, + char *errbuf, int errbufsz) { + char *dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char *sp = src; + const char *sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char *x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) + break; + + ch = *sp++; + /* control characters other than tab is not allowed */ + if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || (ch == 0x7f)) { + if (!(multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + } + + dst[off++] = 0; + return dst; +} + +/* + * Convert src to raw unescaped utf-8 string. + * Returns NULL if error with errmsg in errbuf. + */ +static char *norm_basic_str(const char *src, int srclen, int multiline, + char *errbuf, int errbufsz) { + char *dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char *sp = src; + const char *sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char *x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) + break; + + ch = *sp++; + if (ch != '\\') { + /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F + */ + if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || + (ch == 0x7f)) { + if (!(multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + continue; + } + + /* ch was backslash. we expect the escape char. */ + if (sp >= sq) { + snprintf(errbuf, errbufsz, "last backslash is invalid"); + xfree(dst); + return 0; + } + + /* for multi-line, we want to kill line-ending-backslash ... */ + if (multiline) { + + // if there is only whitespace after the backslash ... + if (sp[strspn(sp, " \t\r")] == '\n') { + /* skip all the following whitespaces */ + sp += strspn(sp, " \t\r\n"); + continue; + } + } + + /* get the escaped char */ + ch = *sp++; + switch (ch) { + case 'u': + case 'U': { + int64_t ucs = 0; + int nhex = (ch == 'u' ? 4 : 8); + for (int i = 0; i < nhex; i++) { + if (sp >= sq) { + snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex); + xfree(dst); + return 0; + } + ch = *sp++; + int v = ('0' <= ch && ch <= '9') + ? ch - '0' + : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1); + if (-1 == v) { + snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U"); + xfree(dst); + return 0; + } + ucs = ucs * 16 + v; + } + int n = toml_ucs_to_utf8(ucs, &dst[off]); + if (-1 == n) { + snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U"); + xfree(dst); + return 0; + } + off += n; + } + continue; + + case 'b': + ch = '\b'; + break; + case 't': + ch = '\t'; + break; + case 'n': + ch = '\n'; + break; + case 'f': + ch = '\f'; + break; + case 'r': + ch = '\r'; + break; + case '"': + ch = '"'; + break; + case '\\': + ch = '\\'; + break; + default: + snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch); + xfree(dst); + return 0; + } + + dst[off++] = ch; + } + + // Cap with NUL and return it. + dst[off++] = 0; + return dst; +} + +/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */ +static char *normalize_key(context_t *ctx, token_t strtok) { + const char *sp = strtok.ptr; + const char *sq = strtok.ptr + strtok.len; + int lineno = strtok.lineno; + char *ret; + int ch = *sp; + char ebuf[80]; + + /* handle quoted string */ + if (ch == '\'' || ch == '\"') { + /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */ + int multiline = 0; + if (sp[1] == ch && sp[2] == ch) { + sp += 3, sq -= 3; + multiline = 1; + } else + sp++, sq--; + + if (ch == '\'') { + /* for single quote, take it verbatim. */ + if (!(ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + } else { + /* for double quote, we need to normalize */ + ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf)); + if (!ret) { + e_syntax(ctx, lineno, ebuf); + return 0; + } + } + + /* newlines are not allowed in keys */ + if (strchr(ret, '\n')) { + xfree(ret); + e_badkey(ctx, lineno); + return 0; + } + return ret; + } + + /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */ + const char *xp; + for (xp = sp; xp != sq; xp++) { + int k = *xp; + if (isalnum(k)) + continue; + if (k == '_' || k == '-') + continue; + e_badkey(ctx, lineno); + return 0; + } + + /* dup and return it */ + if (!(ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + return ret; +} + +/* + * Look up key in tab. Return 0 if not found, or + * 'v'alue, 'a'rray or 't'able depending on the element. + */ +static int check_key(toml_table_t *tab, const char *key, + toml_keyval_t **ret_val, toml_array_t **ret_arr, + toml_table_t **ret_tab) { + int i; + void *dummy; + + if (!ret_tab) + ret_tab = (toml_table_t **)&dummy; + if (!ret_arr) + ret_arr = (toml_array_t **)&dummy; + if (!ret_val) + ret_val = (toml_keyval_t **)&dummy; + + *ret_tab = 0; + *ret_arr = 0; + *ret_val = 0; + + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) { + *ret_val = tab->kval[i]; + return 'v'; + } + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) { + *ret_arr = tab->arr[i]; + return 'a'; + } + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) { + *ret_tab = tab->tab[i]; + return 't'; + } + } + return 0; +} + +static int key_kind(toml_table_t *tab, const char *key) { + return check_key(tab, key, 0, 0, 0); +} + +/* Create a keyval in the table. + */ +static toml_keyval_t *create_keyval_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out. */ + toml_keyval_t *dest = 0; + if (key_kind(tab, newkey)) { + xfree(newkey); + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new entry */ + int n = tab->nkval; + toml_keyval_t **base; + if (0 == (base = (toml_keyval_t **)expand_ptrarr((void **)tab->kval, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->kval = base; + + if (0 == (base[n] = (toml_keyval_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->kval[tab->nkval++]; + + /* save the key in the new value struct */ + dest->key = newkey; + return dest; +} + +/* Create a table in the table. + */ +static toml_table_t *create_keytable_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out */ + toml_table_t *dest = 0; + if (check_key(tab, newkey, 0, 0, &dest)) { + xfree(newkey); /* don't need this anymore */ + + /* special case: if table exists, but was created implicitly ... */ + if (dest && dest->implicit) { + /* we make it explicit now, and simply return it. */ + dest->implicit = false; + return dest; + } + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* create a new table entry */ + int n = tab->ntab; + toml_table_t **base; + if (0 == (base = (toml_table_t **)expand_ptrarr((void **)tab->tab, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->tab = base; + + if (0 == (base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->tab[tab->ntab++]; + + /* save the key in the new table struct */ + dest->key = newkey; + return dest; +} + +/* Create an array in the table. + */ +static toml_array_t *create_keyarray_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok, char kind) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out */ + if (key_kind(tab, newkey)) { + xfree(newkey); /* don't need this anymore */ + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new array entry */ + int n = tab->narr; + toml_array_t **base; + if (0 == (base = (toml_array_t **)expand_ptrarr((void **)tab->arr, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->arr = base; + + if (0 == (base[n] = (toml_array_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t *dest = tab->arr[tab->narr++]; + + /* save the key in the new array struct */ + dest->key = newkey; + dest->kind = kind; + return dest; +} + +static toml_arritem_t *create_value_in_array(context_t *ctx, + toml_array_t *parent) { + const int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + parent->item = base; + parent->nitem++; + return &parent->item[n]; +} + +/* Create an array in an array + */ +static toml_array_t *create_array_in_array(context_t *ctx, + toml_array_t *parent) { + const int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t *ret = (toml_array_t *)CALLOC(1, sizeof(toml_array_t)); + if (!ret) { + e_outofmemory(ctx, FLINE); + return 0; + } + base[n].arr = ret; + parent->item = base; + parent->nitem++; + return ret; +} + +/* Create a table in an array + */ +static toml_table_t *create_table_in_array(context_t *ctx, + toml_array_t *parent) { + int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + toml_table_t *ret = (toml_table_t *)CALLOC(1, sizeof(toml_table_t)); + if (!ret) { + e_outofmemory(ctx, FLINE); + return 0; + } + base[n].tab = ret; + parent->item = base; + parent->nitem++; + return ret; +} + +static int skip_newlines(context_t *ctx, int isdotspecial) { + while (ctx->tok.tok == NEWLINE) { + if (next_token(ctx, isdotspecial)) + return -1; + if (ctx->tok.eof) + break; + } + return 0; +} + +static int parse_keyval(context_t *ctx, toml_table_t *tab); + +static inline int eat_token(context_t *ctx, tokentype_t typ, int isdotspecial, + const char *fline) { + if (ctx->tok.tok != typ) + return e_internal(ctx, fline); + + if (next_token(ctx, isdotspecial)) + return -1; + + return 0; +} + +/* We are at '{ ... }'. + * Parse the table. + */ +static int parse_inline_table(context_t *ctx, toml_table_t *tab) { + if (eat_token(ctx, LBRACE, 1, FLINE)) + return -1; + + for (;;) { + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, + "newline not allowed in inline table"); + + /* until } */ + if (ctx->tok.tok == RBRACE) + break; + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, ctx->tok.lineno, "expect a string"); + + if (parse_keyval(ctx, tab)) + return -1; + + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, + "newline not allowed in inline table"); + + /* on comma, continue to scan for next keyval */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 1, FLINE)) + return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACE, 1, FLINE)) + return -1; + + tab->readonly = 1; + + return 0; +} + +static int valtype(const char *val) { + toml_timestamp_t ts; + if (*val == '\'' || *val == '"') + return 's'; + if (0 == toml_rtob(val, 0)) + return 'b'; + if (0 == toml_rtoi(val, 0)) + return 'i'; + if (0 == toml_rtod(val, 0)) + return 'd'; + if (0 == toml_rtots(val, &ts)) { + if (ts.year && ts.hour) + return 'T'; /* timestamp */ + if (ts.year) + return 'D'; /* date */ + return 't'; /* time */ + } + return 'u'; /* unknown */ +} + +/* We are at '[...]' */ +static int parse_array(context_t *ctx, toml_array_t *arr) { + if (eat_token(ctx, LBRACKET, 0, FLINE)) + return -1; + + for (;;) { + if (skip_newlines(ctx, 0)) + return -1; + + /* until ] */ + if (ctx->tok.tok == RBRACKET) + break; + + switch (ctx->tok.tok) { + case STRING: { + /* set array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 'v'; + else if (arr->kind != 'v') + arr->kind = 'm'; + + char *val = ctx->tok.ptr; + int vlen = ctx->tok.len; + + /* make a new value in array */ + toml_arritem_t *newval = create_value_in_array(ctx, arr); + if (!newval) + return e_outofmemory(ctx, FLINE); + + if (!(newval->val = STRNDUP(val, vlen))) + return e_outofmemory(ctx, FLINE); + + newval->valtype = valtype(newval->val); + + /* set array type if this is the first entry */ + if (arr->nitem == 1) + arr->type = newval->valtype; + else if (arr->type != newval->valtype) + arr->type = 'm'; /* mixed */ + + if (eat_token(ctx, STRING, 0, FLINE)) + return -1; + break; + } + + case LBRACKET: { /* [ [array], [array] ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 'a'; + else if (arr->kind != 'a') + arr->kind = 'm'; + + toml_array_t *subarr = create_array_in_array(ctx, arr); + if (!subarr) + return -1; + if (parse_array(ctx, subarr)) + return -1; + break; + } + + case LBRACE: { /* [ {table}, {table} ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 't'; + else if (arr->kind != 't') + arr->kind = 'm'; + + toml_table_t *subtab = create_table_in_array(ctx, arr); + if (!subtab) + return -1; + if (parse_inline_table(ctx, subtab)) + return -1; + break; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + + if (skip_newlines(ctx, 0)) + return -1; + + /* on comma, continue to scan for next element */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 0, FLINE)) + return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + return 0; +} + +/* handle lines like these: + key = "value" + key = [ array ] + key = { table } +*/ +static int parse_keyval(context_t *ctx, toml_table_t *tab) { + if (tab->readonly) { + return e_forbid(ctx, ctx->tok.lineno, + "cannot insert new entry into existing table"); + } + + token_t key = ctx->tok; + if (eat_token(ctx, STRING, 1, FLINE)) + return -1; + + if (ctx->tok.tok == DOT) { + /* handle inline dotted key. + e.g. + physical.color = "orange" + physical.shape = "round" + */ + toml_table_t *subtab = 0; + { + char *subtabstr = normalize_key(ctx, key); + if (!subtabstr) + return -1; + + subtab = toml_table_in(tab, subtabstr); + xfree(subtabstr); + } + if (!subtab) { + subtab = create_keytable_in_table(ctx, tab, key); + if (!subtab) + return -1; + } + if (next_token(ctx, 1)) + return -1; + if (parse_keyval(ctx, subtab)) + return -1; + return 0; + } + + if (ctx->tok.tok != EQUAL) { + return e_syntax(ctx, ctx->tok.lineno, "missing ="); + } + + if (next_token(ctx, 0)) + return -1; + + switch (ctx->tok.tok) { + case STRING: { /* key = "value" */ + toml_keyval_t *keyval = create_keyval_in_table(ctx, tab, key); + if (!keyval) + return -1; + token_t val = ctx->tok; + + assert(keyval->val == 0); + if (!(keyval->val = STRNDUP(val.ptr, val.len))) + return e_outofmemory(ctx, FLINE); + + if (next_token(ctx, 1)) + return -1; + + return 0; + } + + case LBRACKET: { /* key = [ array ] */ + toml_array_t *arr = create_keyarray_in_table(ctx, tab, key, 0); + if (!arr) + return -1; + if (parse_array(ctx, arr)) + return -1; + return 0; + } + + case LBRACE: { /* key = { table } */ + toml_table_t *nxttab = create_keytable_in_table(ctx, tab, key); + if (!nxttab) + return -1; + if (parse_inline_table(ctx, nxttab)) + return -1; + return 0; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + return 0; +} + +typedef struct tabpath_t tabpath_t; +struct tabpath_t { + int cnt; + token_t key[10]; +}; + +/* at [x.y.z] or [[x.y.z]] + * Scan forward and fill tabpath until it enters ] or ]] + * There will be at least one entry on return. + */ +static int fill_tabpath(context_t *ctx) { + int lineno = ctx->tok.lineno; + int i; + + /* clear tpath */ + for (i = 0; i < ctx->tpath.top; i++) { + char **p = &ctx->tpath.key[i]; + xfree(*p); + *p = 0; + } + ctx->tpath.top = 0; + + for (;;) { + if (ctx->tpath.top >= 10) + return e_syntax(ctx, lineno, + "table path is too deep; max allowed is 10."); + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, lineno, "invalid or missing key"); + + char *key = normalize_key(ctx, ctx->tok); + if (!key) + return -1; + ctx->tpath.tok[ctx->tpath.top] = ctx->tok; + ctx->tpath.key[ctx->tpath.top] = key; + ctx->tpath.top++; + + if (next_token(ctx, 1)) + return -1; + + if (ctx->tok.tok == RBRACKET) + break; + + if (ctx->tok.tok != DOT) + return e_syntax(ctx, lineno, "invalid key"); + + if (next_token(ctx, 1)) + return -1; + } + + if (ctx->tpath.top <= 0) + return e_syntax(ctx, lineno, "empty table selector"); + + return 0; +} + +/* Walk tabpath from the root, and create new tables on the way. + * Sets ctx->curtab to the final table. + */ +static int walk_tabpath(context_t *ctx) { + /* start from root */ + toml_table_t *curtab = ctx->root; + + for (int i = 0; i < ctx->tpath.top; i++) { + const char *key = ctx->tpath.key[i]; + + toml_keyval_t *nextval = 0; + toml_array_t *nextarr = 0; + toml_table_t *nexttab = 0; + switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) { + case 't': + /* found a table. nexttab is where we will go next. */ + break; + + case 'a': + /* found an array. nexttab is the last table in the array. */ + if (nextarr->kind != 't') + return e_internal(ctx, FLINE); + + if (nextarr->nitem == 0) + return e_internal(ctx, FLINE); + + nexttab = nextarr->item[nextarr->nitem - 1].tab; + break; + + case 'v': + return e_keyexists(ctx, ctx->tpath.tok[i].lineno); + + default: { /* Not found. Let's create an implicit table. */ + int n = curtab->ntab; + toml_table_t **base = + (toml_table_t **)expand_ptrarr((void **)curtab->tab, n); + if (0 == base) + return e_outofmemory(ctx, FLINE); + + curtab->tab = base; + + if (0 == (base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n])))) + return e_outofmemory(ctx, FLINE); + + if (0 == (base[n]->key = STRDUP(key))) + return e_outofmemory(ctx, FLINE); + + nexttab = curtab->tab[curtab->ntab++]; + + /* tabs created by walk_tabpath are considered implicit */ + nexttab->implicit = true; + } break; + } + + /* switch to next tab */ + curtab = nexttab; + } + + /* save it */ + ctx->curtab = curtab; + + return 0; +} + +/* handle lines like [x.y.z] or [[x.y.z]] */ +static int parse_select(context_t *ctx) { + assert(ctx->tok.tok == LBRACKET); + + /* true if [[ */ + int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '['); + /* need to detect '[[' on our own because next_token() will skip whitespace, + and '[ [' would be taken as '[[', which is wrong. */ + + /* eat [ or [[ */ + if (eat_token(ctx, LBRACKET, 1, FLINE)) + return -1; + if (llb) { + assert(ctx->tok.tok == LBRACKET); + if (eat_token(ctx, LBRACKET, 1, FLINE)) + return -1; + } + + if (fill_tabpath(ctx)) + return -1; + + /* For [x.y.z] or [[x.y.z]], remove z from tpath. + */ + token_t z = ctx->tpath.tok[ctx->tpath.top - 1]; + xfree(ctx->tpath.key[ctx->tpath.top - 1]); + ctx->tpath.top--; + + /* set up ctx->curtab */ + if (walk_tabpath(ctx)) + return -1; + + if (!llb) { + /* [x.y.z] -> create z = {} in x.y */ + toml_table_t *curtab = create_keytable_in_table(ctx, ctx->curtab, z); + if (!curtab) + return -1; + ctx->curtab = curtab; + } else { + /* [[x.y.z]] -> create z = [] in x.y */ + toml_array_t *arr = 0; + { + char *zstr = normalize_key(ctx, z); + if (!zstr) + return -1; + arr = toml_array_in(ctx->curtab, zstr); + xfree(zstr); + } + if (!arr) { + arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't'); + if (!arr) + return -1; + } + if (arr->kind != 't') + return e_syntax(ctx, z.lineno, "array mismatch"); + + /* add to z[] */ + toml_table_t *dest; + { + toml_table_t *t = create_table_in_array(ctx, arr); + if (!t) + return -1; + + if (0 == (t->key = STRDUP("__anon__"))) + return e_outofmemory(ctx, FLINE); + + dest = t; + } + + ctx->curtab = dest; + } + + if (ctx->tok.tok != RBRACKET) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]"); + } + if (llb) { + if (!(ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]]"); + } + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + + if (ctx->tok.tok != NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]"); + + return 0; +} + +toml_table_t *toml_parse(char *conf, char *errbuf, int errbufsz) { + context_t ctx; + + // clear errbuf + if (errbufsz <= 0) + errbufsz = 0; + if (errbufsz > 0) + errbuf[0] = 0; + + // init context + memset(&ctx, 0, sizeof(ctx)); + ctx.start = conf; + ctx.stop = ctx.start + strlen(conf); + ctx.errbuf = errbuf; + ctx.errbufsz = errbufsz; + + // start with an artificial newline of length 0 + ctx.tok.tok = NEWLINE; + ctx.tok.lineno = 1; + ctx.tok.ptr = conf; + ctx.tok.len = 0; + + // make a root table + if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) { + e_outofmemory(&ctx, FLINE); + // Do not goto fail, root table not set up yet + return 0; + } + + // set root as default table + ctx.curtab = ctx.root; + + /* Scan forward until EOF */ + for (token_t tok = ctx.tok; !tok.eof; tok = ctx.tok) { + switch (tok.tok) { + + case NEWLINE: + if (next_token(&ctx, 1)) + goto fail; + break; + + case STRING: + if (parse_keyval(&ctx, ctx.curtab)) + goto fail; + + if (ctx.tok.tok != NEWLINE) { + e_syntax(&ctx, ctx.tok.lineno, "extra chars after value"); + goto fail; + } + + if (eat_token(&ctx, NEWLINE, 1, FLINE)) + goto fail; + break; + + case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */ + if (parse_select(&ctx)) + goto fail; + break; + + default: + e_syntax(&ctx, tok.lineno, "syntax error"); + goto fail; + } + } + + /* success */ + for (int i = 0; i < ctx.tpath.top; i++) + xfree(ctx.tpath.key[i]); + return ctx.root; + +fail: + // Something bad has happened. Free resources and return error. + for (int i = 0; i < ctx.tpath.top; i++) + xfree(ctx.tpath.key[i]); + toml_free(ctx.root); + return 0; +} + +toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz) { + int bufsz = 0; + char *buf = 0; + int off = 0; + + /* read from fp into buf */ + while (!feof(fp)) { + + if (off == bufsz) { + int xsz = bufsz + 1000; + char *x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + + errno = 0; + int n = fread(buf + off, 1, bufsz - off, fp); + if (ferror(fp)) { + snprintf(errbuf, errbufsz, "%s", + errno ? strerror(errno) : "Error reading file"); + xfree(buf); + return 0; + } + off += n; + } + + /* tag on a NUL to cap the string */ + if (off == bufsz) { + int xsz = bufsz + 1; + char *x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + buf[off] = 0; + + /* parse it, cleanup and finish */ + toml_table_t *ret = toml_parse(buf, errbuf, errbufsz); + xfree(buf); + return ret; +} + +static void xfree_kval(toml_keyval_t *p) { + if (!p) + return; + xfree(p->key); + xfree(p->val); + xfree(p); +} + +static void xfree_tab(toml_table_t *p); + +static void xfree_arr(toml_array_t *p) { + if (!p) + return; + + xfree(p->key); + const int n = p->nitem; + for (int i = 0; i < n; i++) { + toml_arritem_t *a = &p->item[i]; + if (a->val) + xfree(a->val); + else if (a->arr) + xfree_arr(a->arr); + else if (a->tab) + xfree_tab(a->tab); + } + xfree(p->item); + xfree(p); +} + +static void xfree_tab(toml_table_t *p) { + int i; + + if (!p) + return; + + xfree(p->key); + + for (i = 0; i < p->nkval; i++) + xfree_kval(p->kval[i]); + xfree(p->kval); + + for (i = 0; i < p->narr; i++) + xfree_arr(p->arr[i]); + xfree(p->arr); + + for (i = 0; i < p->ntab; i++) + xfree_tab(p->tab[i]); + xfree(p->tab); + + xfree(p); +} + +void toml_free(toml_table_t *tab) { xfree_tab(tab); } + +static void set_token(context_t *ctx, tokentype_t tok, int lineno, char *ptr, + int len) { + token_t t; + t.tok = tok; + t.lineno = lineno; + t.ptr = ptr; + t.len = len; + t.eof = 0; + ctx->tok = t; +} + +static void set_eof(context_t *ctx, int lineno) { + set_token(ctx, NEWLINE, lineno, ctx->stop, 0); + ctx->tok.eof = 1; +} + +/* Scan p for n digits compositing entirely of [0-9] */ +static int scan_digits(const char *p, int n) { + int ret = 0; + for (; n > 0 && isdigit(*p); n--, p++) { + ret = 10 * ret + (*p - '0'); + } + return n ? -1 : ret; +} + +static int scan_date(const char *p, int *YY, int *MM, int *DD) { + int year, month, day; + year = scan_digits(p, 4); + month = (year >= 0 && p[4] == '-') ? scan_digits(p + 5, 2) : -1; + day = (month >= 0 && p[7] == '-') ? scan_digits(p + 8, 2) : -1; + if (YY) + *YY = year; + if (MM) + *MM = month; + if (DD) + *DD = day; + return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1; +} + +static int scan_time(const char *p, int *hh, int *mm, int *ss) { + int hour, minute, second; + hour = scan_digits(p, 2); + minute = (hour >= 0 && p[2] == ':') ? scan_digits(p + 3, 2) : -1; + second = (minute >= 0 && p[5] == ':') ? scan_digits(p + 6, 2) : -1; + if (hh) + *hh = hour; + if (mm) + *mm = minute; + if (ss) + *ss = second; + return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1; +} + +static int scan_string(context_t *ctx, char *p, int lineno, int dotisspecial) { + char *orig = p; + if (0 == strncmp(p, "'''", 3)) { + char *q = p + 3; + + while (1) { + q = strstr(q, "'''"); + if (0 == q) { + return e_syntax(ctx, lineno, "unterminated triple-s-quote"); + } + while (q[3] == '\'') + q++; + break; + } + + set_token(ctx, STRING, lineno, orig, q + 3 - orig); + return 0; + } + + if (0 == strncmp(p, "\"\"\"", 3)) { + char *q = p + 3; + + while (1) { + q = strstr(q, "\"\"\""); + if (0 == q) { + return e_syntax(ctx, lineno, "unterminated triple-d-quote"); + } + if (q[-1] == '\\') { + q++; + continue; + } + while (q[3] == '\"') + q++; + break; + } + + // the string is [p+3, q-1] + + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p += 3; p < q; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) + continue; + if (*p == 'u') { + hexreq = 4; + continue; + } + if (*p == 'U') { + hexreq = 8; + continue; + } + if (p[strspn(p, " \t\r")] == '\n') + continue; /* allow for line ending backslash */ + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) + continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { + escape = 1; + continue; + } + } + if (escape) + return e_syntax(ctx, lineno, "expect an escape char"); + if (hexreq) + return e_syntax(ctx, lineno, "expected more hex char"); + + set_token(ctx, STRING, lineno, orig, q + 3 - orig); + return 0; + } + + if ('\'' == *p) { + for (p++; *p && *p != '\n' && *p != '\''; p++) + ; + if (*p != '\'') { + return e_syntax(ctx, lineno, "unterminated s-quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + if ('\"' == *p) { + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p++; *p; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) + continue; + if (*p == 'u') { + hexreq = 4; + continue; + } + if (*p == 'U') { + hexreq = 8; + continue; + } + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) + continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { + escape = 1; + continue; + } + if (*p == '\'') { + if (p[1] == '\'' && p[2] == '\'') { + return e_syntax(ctx, lineno, "triple-s-quote inside string lit"); + } + continue; + } + if (*p == '\n') + break; + if (*p == '"') + break; + } + if (*p != '"') { + return e_syntax(ctx, lineno, "unterminated quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + /* check for timestamp without quotes */ + if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) { + // forward thru the timestamp + p += strspn(p, "0123456789.:+-Tt Zz"); + // squeeze out any spaces at end of string + for (; p[-1] == ' '; p--) + ; + // tokenize + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; + } + + /* literals */ + for (; *p && *p != '\n'; p++) { + int ch = *p; + if (ch == '.' && dotisspecial) + break; + if ('A' <= ch && ch <= 'Z') + continue; + if ('a' <= ch && ch <= 'z') + continue; + if (strchr("0123456789+-_.", ch)) + continue; + break; + } + + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; +} + +static int next_token(context_t *ctx, int dotisspecial) { + int lineno = ctx->tok.lineno; + char *p = ctx->tok.ptr; + int i; + + /* eat this tok */ + for (i = 0; i < ctx->tok.len; i++) { + if (*p++ == '\n') + lineno++; + } + + /* make next tok */ + while (p < ctx->stop) { + /* skip comment. stop just before the \n. */ + if (*p == '#') { + for (p++; p < ctx->stop && *p != '\n'; p++) + ; + continue; + } + + if (dotisspecial && *p == '.') { + set_token(ctx, DOT, lineno, p, 1); + return 0; + } + + switch (*p) { + case ',': + set_token(ctx, COMMA, lineno, p, 1); + return 0; + case '=': + set_token(ctx, EQUAL, lineno, p, 1); + return 0; + case '{': + set_token(ctx, LBRACE, lineno, p, 1); + return 0; + case '}': + set_token(ctx, RBRACE, lineno, p, 1); + return 0; + case '[': + set_token(ctx, LBRACKET, lineno, p, 1); + return 0; + case ']': + set_token(ctx, RBRACKET, lineno, p, 1); + return 0; + case '\n': + set_token(ctx, NEWLINE, lineno, p, 1); + return 0; + case '\r': + case ' ': + case '\t': + /* ignore white spaces */ + p++; + continue; + } + + return scan_string(ctx, p, lineno, dotisspecial); + } + + set_eof(ctx, lineno); + return 0; +} + +const char *toml_key_in(const toml_table_t *tab, int keyidx) { + if (keyidx < tab->nkval) + return tab->kval[keyidx]->key; + + keyidx -= tab->nkval; + if (keyidx < tab->narr) + return tab->arr[keyidx]->key; + + keyidx -= tab->narr; + if (keyidx < tab->ntab) + return tab->tab[keyidx]->key; + + return 0; +} + +int toml_key_exists(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return 1; + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return 1; + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return 1; + } + return 0; +} + +toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return tab->kval[i]->val; + } + return 0; +} + +toml_array_t *toml_array_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return tab->arr[i]; + } + return 0; +} + +toml_table_t *toml_table_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return tab->tab[i]; + } + return 0; +} + +toml_raw_t toml_raw_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0; +} + +char toml_array_kind(const toml_array_t *arr) { return arr->kind; } + +char toml_array_type(const toml_array_t *arr) { + if (arr->kind != 'v') + return 0; + + if (arr->nitem == 0) + return 0; + + return arr->type; +} + +int toml_array_nelem(const toml_array_t *arr) { return arr->nitem; } + +const char *toml_array_key(const toml_array_t *arr) { + return arr ? arr->key : (const char *)NULL; +} + +int toml_table_nkval(const toml_table_t *tab) { return tab->nkval; } + +int toml_table_narr(const toml_table_t *tab) { return tab->narr; } + +int toml_table_ntab(const toml_table_t *tab) { return tab->ntab; } + +const char *toml_table_key(const toml_table_t *tab) { + return tab ? tab->key : (const char *)NULL; +} + +toml_array_t *toml_array_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0; +} + +toml_table_t *toml_table_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0; +} + +static int parse_millisec(const char *p, const char **endp); + +int toml_rtots(toml_raw_t src_, toml_timestamp_t *ret) { + if (!src_) + return -1; + + const char *p = src_; + int must_parse_time = 0; + + memset(ret, 0, sizeof(*ret)); + + int *year = &ret->__buffer.year; + int *month = &ret->__buffer.month; + int *day = &ret->__buffer.day; + int *hour = &ret->__buffer.hour; + int *minute = &ret->__buffer.minute; + int *second = &ret->__buffer.second; + int *millisec = &ret->__buffer.millisec; + + /* parse date YYYY-MM-DD */ + if (0 == scan_date(p, year, month, day)) { + ret->year = year; + ret->month = month; + ret->day = day; + + p += 10; + if (*p) { + // parse the T or space separator + if (*p != 'T' && *p != 't' && *p != ' ') + return -1; + must_parse_time = 1; + p++; + } + } + + /* parse time HH:MM:SS */ + if (0 == scan_time(p, hour, minute, second)) { + ret->hour = hour; + ret->minute = minute; + ret->second = second; + + /* optionally, parse millisec */ + p += 8; + if (*p == '.') { + p++; /* skip '.' */ + const char *qq; + *millisec = parse_millisec(p, &qq); + ret->millisec = millisec; + p = qq; + } + + if (*p) { + /* parse and copy Z */ + char *z = ret->__buffer.z; + ret->z = z; + if (*p == 'Z' || *p == 'z') { + *z++ = 'Z'; + p++; + *z = 0; + + } else if (*p == '+' || *p == '-') { + *z++ = *p++; + + if (!(isdigit(p[0]) && isdigit(p[1]))) + return -1; + *z++ = *p++; + *z++ = *p++; + + if (*p == ':') { + *z++ = *p++; + + if (!(isdigit(p[0]) && isdigit(p[1]))) + return -1; + *z++ = *p++; + *z++ = *p++; + } + + *z = 0; + } + } + } + if (*p != 0) + return -1; + + if (must_parse_time && !ret->hour) + return -1; + + return 0; +} + +/* Raw to boolean */ +int toml_rtob(toml_raw_t src, int *ret_) { + if (!src) + return -1; + int dummy; + int *ret = ret_ ? ret_ : &dummy; + + if (0 == strcmp(src, "true")) { + *ret = 1; + return 0; + } + if (0 == strcmp(src, "false")) { + *ret = 0; + return 0; + } + return -1; +} + +/* Raw to integer */ +int toml_rtoi(toml_raw_t src, int64_t *ret_) { + if (!src) + return -1; + + char buf[100]; + char *p = buf; + char *q = p + sizeof(buf); + const char *s = src; + int base = 0; + int64_t dummy; + int64_t *ret = ret_ ? ret_ : &dummy; + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_100 */ + if (s[0] == '_') + return -1; + + /* if 0* ... */ + if ('0' == s[0]) { + switch (s[1]) { + case 'x': + base = 16; + s += 2; + break; + case 'o': + base = 8; + s += 2; + break; + case 'b': + base = 2; + s += 2; + break; + case '\0': + return *ret = 0, 0; + default: + /* ensure no other digits after it */ + if (s[1]) + return -1; + } + } + + /* just strip underscores and pass to strtoll */ + while (*s && p < q) { + int ch = *s++; + if (ch == '_') { + // disallow '__' + if (s[0] == '_') + return -1; + // numbers cannot end with '_' + if (s[0] == '\0') + return -1; + continue; /* skip _ */ + } + *p++ = ch; + } + + // if not at end-of-string or we ran out of buffer ... + if (*s || p == q) + return -1; + + /* cap with NUL */ + *p = 0; + + /* Run strtoll on buf to get the integer */ + char *endp; + errno = 0; + *ret = strtoll(buf, &endp, base); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod_ex(toml_raw_t src, double *ret_, char *buf, int buflen) { + if (!src) + return -1; + + char *p = buf; + char *q = p + buflen; + const char *s = src; + double dummy; + double *ret = ret_ ? ret_ : &dummy; + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_1.00 */ + if (s[0] == '_') + return -1; + + /* decimal point, if used, must be surrounded by at least one digit on each + * side */ + { + char *dot = strchr(s, '.'); + if (dot) { + if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1])) + return -1; + } + } + + /* zero must be followed by . or 'e', or NUL */ + if (s[0] == '0' && s[1] && !strchr("eE.", s[1])) + return -1; + + /* just strip underscores and pass to strtod */ + while (*s && p < q) { + int ch = *s++; + if (ch == '_') { + // disallow '__' + if (s[0] == '_') + return -1; + // disallow last char '_' + if (s[0] == 0) + return -1; + continue; /* skip _ */ + } + *p++ = ch; + } + if (*s || p == q) + return -1; /* reached end of string or buffer is full? */ + + /* cap with NUL */ + *p = 0; + + /* Run strtod on buf to get the value */ + char *endp; + errno = 0; + *ret = strtod(buf, &endp); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod(toml_raw_t src, double *ret_) { + char buf[100]; + return toml_rtod_ex(src, ret_, buf, sizeof(buf)); +} + +int toml_rtos(toml_raw_t src, char **ret) { + int multiline = 0; + const char *sp; + const char *sq; + + *ret = 0; + if (!src) + return -1; + + int qchar = src[0]; + int srclen = strlen(src); + if (!(qchar == '\'' || qchar == '"')) { + return -1; + } + + // triple quotes? + if (qchar == src[1] && qchar == src[2]) { + multiline = 1; + sp = src + 3; + sq = src + srclen - 3; + /* last 3 chars in src must be qchar */ + if (!(sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar)) + return -1; + + /* skip new line immediate after qchar */ + if (sp[0] == '\n') + sp++; + else if (sp[0] == '\r' && sp[1] == '\n') + sp += 2; + + } else { + sp = src + 1; + sq = src + srclen - 1; + /* last char in src must be qchar */ + if (!(sp <= sq && *sq == qchar)) + return -1; + } + + if (qchar == '\'') { + *ret = norm_lit_str(sp, sq - sp, multiline, 0, 0); + } else { + *ret = norm_basic_str(sp, sq - sp, multiline, 0, 0); + } + + return *ret ? 0 : -1; +} + +toml_datum_t toml_string_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtos(toml_raw_at(arr, idx), &ret.u.s)); + return ret; +} + +toml_datum_t toml_bool_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtob(toml_raw_at(arr, idx), &ret.u.b)); + return ret; +} + +toml_datum_t toml_int_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtoi(toml_raw_at(arr, idx), &ret.u.i)); + return ret; +} + +toml_datum_t toml_double_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtod(toml_raw_at(arr, idx), &ret.u.d)); + return ret; +} + +toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx) { + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtots(toml_raw_at(arr, idx), &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + if (ret.u.ts->year) + ret.u.ts->year = &ret.u.ts->__buffer.year; + if (ret.u.ts->month) + ret.u.ts->month = &ret.u.ts->__buffer.month; + if (ret.u.ts->day) + ret.u.ts->day = &ret.u.ts->__buffer.day; + if (ret.u.ts->hour) + ret.u.ts->hour = &ret.u.ts->__buffer.hour; + if (ret.u.ts->minute) + ret.u.ts->minute = &ret.u.ts->__buffer.minute; + if (ret.u.ts->second) + ret.u.ts->second = &ret.u.ts->__buffer.second; + if (ret.u.ts->millisec) + ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; + if (ret.u.ts->z) + ret.u.ts->z = ret.u.ts->__buffer.z; + } + } + return ret; +} + +toml_datum_t toml_string_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtos(raw, &ret.u.s)); + } + return ret; +} + +toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtob(toml_raw_in(arr, key), &ret.u.b)); + return ret; +} + +toml_datum_t toml_int_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtoi(toml_raw_in(arr, key), &ret.u.i)); + return ret; +} + +toml_datum_t toml_double_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtod(toml_raw_in(arr, key), &ret.u.d)); + return ret; +} + +toml_datum_t toml_timestamp_in(const toml_table_t *arr, const char *key) { + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtots(toml_raw_in(arr, key), &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + if (ret.u.ts->year) + ret.u.ts->year = &ret.u.ts->__buffer.year; + if (ret.u.ts->month) + ret.u.ts->month = &ret.u.ts->__buffer.month; + if (ret.u.ts->day) + ret.u.ts->day = &ret.u.ts->__buffer.day; + if (ret.u.ts->hour) + ret.u.ts->hour = &ret.u.ts->__buffer.hour; + if (ret.u.ts->minute) + ret.u.ts->minute = &ret.u.ts->__buffer.minute; + if (ret.u.ts->second) + ret.u.ts->second = &ret.u.ts->__buffer.second; + if (ret.u.ts->millisec) + ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; + if (ret.u.ts->z) + ret.u.ts->z = ret.u.ts->__buffer.z; + } + } + return ret; +} + +static int parse_millisec(const char *p, const char **endp) { + int ret = 0; + int unit = 100; /* unit in millisec */ + for (; '0' <= *p && *p <= '9'; p++, unit /= 10) { + ret += (*p - '0') * unit; + } + *endp = p; + return ret; +} diff --git a/userspace/include/libvgpu.h b/userspace/include/libvgpu.h new file mode 100644 index 0000000..7f6505b --- /dev/null +++ b/userspace/include/libvgpu.h @@ -0,0 +1,238 @@ +/* + * GSP vGPU-IO Userspace Library (libvgpu) + * Simple C API for VF management and GPU operations + */ + +#ifndef _LIBVGPU_H_ +#define _LIBVGPU_H_ + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Error codes */ +typedef enum { + VGPU_OK = 0, + VGPU_ERR_INVALID_ARG = -1, + VGPU_ERR_NO_DEVICE = -2, + VGPU_ERR_NO_MEMORY = -3, + VGPU_ERR_BUSY = -4, + VGPU_ERR_TIMEOUT = -5, + VGPU_ERR_PERMISSION = -6, + VGPU_ERR_NOT_SUPPORTED = -7, + VGPU_ERR_INTERNAL = -8, +} vgpu_error_t; + +/* Memory types */ +typedef enum { + VGPU_MEM_VRAM = 0, + VGPU_MEM_SYSMEM = 1, + VGPU_MEM_UNCACHED = 2, +} vgpu_mem_type_t; + +/* Pin flags */ +#define VGPU_PIN_READ (1 << 0) +#define VGPU_PIN_WRITE (1 << 1) +#define VGPU_PIN_LONGTERM (1 << 2) + +/* WPR flags */ +#define VGPU_WPR_WRITE (1 << 0) +#define VGPU_WPR_READ (1 << 1) +#define VGPU_WPR_EXECUTE (1 << 2) + +/* Migration phases */ +typedef enum { + VGPU_MIG_QUERY = 0, + VGPU_MIG_PREPARE = 1, + VGPU_MIG_TRANSFER = 2, + VGPU_MIG_COMMIT = 3, + VGPU_MIG_CLEANUP = 4, +} vgpu_mig_phase_t; + +/* Migration flags */ +#define VGPU_MIG_LIVE (1 << 0) +#define VGPU_MIG_POSTCOPY (1 << 1) +#define VGPU_MIG_AUTO_COMMIT (1 << 2) +#define VGPU_MIG_VERIFY (1 << 3) + +/* VF Configuration */ +typedef struct { + uint32_t id; + uint64_t bar1_offset; + uint64_t bar1_size; + uint64_t pt_base; + uint64_t pt_size; + uint64_t cmd_base; + uint64_t cmd_size; + uint32_t max_memory_mb; + uint32_t num_engines; +} vgpu_vf_config_t; + +/* VF Handle */ +typedef struct vgpu_vf *vgpu_vf_t; + +/* Device Handle */ +typedef struct vgpu_device *vgpu_device_t; + +/* Allocation Handle */ +typedef uint64_t vgpu_handle_t; + +/* ============================================================ + * Device Management + * ============================================================ */ + +/* Open GSP device */ +vgpu_error_t vgpu_open(const char *device_path, vgpu_device_t *device); + +/* Close GSP device */ +void vgpu_close(vgpu_device_t device); + +/* Get device info */ +vgpu_error_t vgpu_get_info(vgpu_device_t device, char *buf, size_t len); + +/* ============================================================ + * VF Management + * ============================================================ */ + +/* Create VF */ +vgpu_error_t vgpu_vf_create(vgpu_device_t device, const vgpu_vf_config_t *config, vgpu_vf_t *vf); + +/* Destroy VF */ +vgpu_error_t vgpu_vf_destroy(vgpu_vf_t vf); + +/* Get VF info */ +vgpu_error_t vgpu_vf_get_info(vgpu_vf_t vf, char *buf, size_t len); + +/* ============================================================ + * Memory Management + * ============================================================ */ + +/* Allocate GPU memory */ +vgpu_error_t vgpu_mem_alloc(vgpu_vf_t vf, uint64_t size, vgpu_mem_type_t type, + uint64_t alignment, uint64_t va_hint, + uint32_t flags, vgpu_handle_t *handle, uint64_t *va); + +/* Free GPU memory */ +vgpu_error_t vgpu_mem_free(vgpu_vf_t vf, vgpu_handle_t handle); + +/* Map memory to VF address space */ +vgpu_error_t vgpu_mem_map(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t gpu_va, + uint64_t size, uint32_t prot, uint32_t flags, + uint64_t offset, uint64_t *mapped_va); + +/* Unmap memory */ +vgpu_error_t vgpu_mem_unmap(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t gpu_va, uint64_t size); + +/* Pin pages for DMA */ +vgpu_error_t vgpu_mem_pin(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t offset, + uint64_t size, uint32_t flags, uint64_t *dma_addr); + +/* Unpin pages */ +vgpu_error_t vgpu_mem_unpin(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t offset, uint64_t size); + +/* ============================================================ + * BAR Emulation + * ============================================================ */ + +/* Configure BAR1 aperture */ +vgpu_error_t vgpu_bar_config(vgpu_vf_t vf, uint64_t offset, uint64_t size); + +/* Enable/disable ForceBarAccess */ +vgpu_error_t vgpu_bar_force_access(vgpu_vf_t vf, bool enable); + +/* Save BAR state for migration */ +vgpu_error_t vgpu_bar_save(vgpu_vf_t vf, void *buf, size_t *size); + +/* Restore BAR state after migration */ +vgpu_error_t vgpu_bar_restore(vgpu_vf_t vf, const void *buf, size_t size); + +/* ============================================================ + * WPR (Write/Read Protect Region) + * ============================================================ */ + +/* Protect memory region (write) */ +vgpu_error_t vgpu_wpr_protect_write(vgpu_vf_t vf, uint64_t base, uint64_t size); + +/* Protect memory region (read) */ +vgpu_error_t vgpu_wpr_protect_read(vgpu_vf_t vf, uint64_t base, uint64_t size); + +/* Unprotect memory region */ +vgpu_error_t vgpu_wpr_unprotect(vgpu_vf_t vf, uint64_t base); + +/* Set WPR timeout */ +vgpu_error_t vgpu_wpr_set_timeout(vgpu_vf_t vf, uint64_t timeout_ns); + +/* Register WPR callback */ +typedef void (*vgpu_wpr_callback_t)(vgpu_vf_t vf, uint64_t addr, uint32_t type, void *ctx); +vgpu_error_t vgpu_wpr_register_callback(vgpu_vf_t vf, uint32_t event_type, + vgpu_wpr_callback_t cb, void *ctx); + +/* ============================================================ + * Scheduling + * ============================================================ */ + +/* Schedule work on VF */ +vgpu_error_t vgpu_schedule(vgpu_vf_t vf, uint64_t work_handle, uint64_t callback, + uint64_t arg, uint32_t priority, uint32_t flags); + +/* Yield VF timeslice */ +vgpu_error_t vgpu_yield(vgpu_vf_t vf); + +/* ============================================================ + * Migration + * ============================================================ */ + +/* Start live migration */ +vgpu_error_t vgpu_migrate_start(vgpu_vf_t vf, uint64_t dst_vf, uint32_t flags); + +/* Check migration status */ +vgpu_error_t vgpu_migrate_status(vgpu_vf_t vf, vgpu_mig_phase_t *phase, uint64_t *progress); + +/* Cancel migration */ +vgpu_error_t vgpu_migrate_cancel(vgpu_vf_t vf); + +/* ============================================================ + * Error Handling + * ============================================================ */ + +/* Get error string */ +const char *vgpu_strerror(vgpu_error_t err); + +/* ============================================================ + * Event Handling (async) + * ============================================================ */ + +/* Event types */ +typedef enum { + VGPU_EVENT_WPR_WRITE_COMPLETE = 0, + VGPU_EVENT_WPR_READ_COMPLETE = 1, + VGPU_EVENT_WPR_WRITE_TIMEOUT = 2, + VGPU_EVENT_WPR_READ_TIMEOUT = 3, + VGPU_EVENT_MIGRATION_PROGRESS = 4, + VGPU_EVENT_MIGRATION_COMPLETE = 5, + VGPU_EVENT_VF_ERROR = 6, +} vgpu_event_type_t; + +/* Event callback */ +typedef void (*vgpu_event_callback_t)(vgpu_vf_t vf, vgpu_event_type_t type, + void *data, size_t size, void *ctx); + +/* Register event callback */ +vgpu_error_t vgpu_register_event_callback(vgpu_vf_t vf, vgpu_event_type_t type, + vgpu_event_callback_t cb, void *ctx); + +/* Unregister event callback */ +vgpu_error_t vgpu_unregister_event_callback(vgpu_vf_t vf, vgpu_event_type_t type); + +/* Process pending events (call periodically) */ +vgpu_error_t vgpu_process_events(vgpu_device_t device, int timeout_ms); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBVGPU_H_ */
\ No newline at end of file diff --git a/userspace/lib/Makefile b/userspace/lib/Makefile new file mode 100644 index 0000000..05bb05b --- /dev/null +++ b/userspace/lib/Makefile @@ -0,0 +1,89 @@ +# libiov - Userspace library for Open-IOV vGPU +# Makefile + +PREFIX ?= /usr/local +LIBDIR ?= $(PREFIX)/lib +INCLUDEDIR ?= $(PREFIX)/include +PKGCONFIGDIR ?= $(LIBDIR)/pkgconfig + +CC ?= gcc +CFLAGS ?= -O2 -g -Wall -Wextra -fPIC -I../include +LDFLAGS ?= -shared + +LIB_NAME = libiov.so +LIB_MAJOR = 1 +LIB_MINOR = 0 +LIB_PATCH = 0 +LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR).$(LIB_PATCH) +LIB_SONAME = $(LIB_NAME).$(LIB_MAJOR) + +# Compatibility symlinks for libvgpu +COMPAT_NAMES = libvgpu.so libvgpu.so.1 libvgpu.so.1.0.0 + +SRC_DIR = . +OBJ_DIR = obj +SRC = $(SRC_DIR)/libvgpu.c $(SRC_DIR)/rpc_emulator.c +OBJ = $(OBJ_DIR)/libvgpu.o $(OBJ_DIR)/rpc_emulator.o + +HEADERS = ../include/libvgpu.h rpc_emulator.h + +all: $(OBJ_DIR) $(LIB_NAME) compat-links + +$(OBJ_DIR): + mkdir -p $(OBJ_DIR) + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(HEADERS) + $(CC) $(CFLAGS) -c $< -o $@ + +$(LIB_NAME): $(OBJ) + $(CC) $(LDFLAGS) -Wl,-soname,$(LIB_SONAME) -o $@ $^ + ln -sf $(LIB_NAME) $(LIB_SONAME) + ln -sf $(LIB_NAME) $(LIB_NAME).$(LIB_VERSION) + +# Create compatibility symlinks for libvgpu +compat-links: $(LIB_NAME) + @for link in $(COMPAT_NAMES); do \ + ln -sf $(LIB_NAME) $$link; \ + done + +install: all + install -d $(DESTDIR)$(LIBDIR) + install -d $(DESTDIR)$(INCLUDEDIR) + install -d $(DESTDIR)$(PKGCONFIGDIR) + install -m 755 $(LIB_NAME) $(DESTDIR)$(LIBDIR)/$(LIB_NAME).$(LIB_VERSION) + ln -sf $(LIB_NAME).$(LIB_VERSION) $(DESTDIR)$(LIBDIR)/$(LIB_SONAME) + ln -sf $(LIB_NAME).$(LIB_VERSION) $(DESTDIR)$(LIBDIR)/$(LIB_NAME) + # Install compatibility symlinks + @for link in $(COMPAT_NAMES); do \ + ln -sf $(LIB_NAME).$(LIB_VERSION) $(DESTDIR)$(LIBDIR)/$$link; \ + done + install -m 644 $(HEADERS) $(DESTDIR)$(INCLUDEDIR)/ + sed -e "s|@PREFIX@|$(PREFIX)|g" \ + -e "s|@LIBDIR@|$(LIBDIR)|g" \ + -e "s|@INCLUDEDIR@|$(INCLUDEDIR)|g" \ + -e "s|@VERSION@|$(LIB_VERSION)|g" \ + libvgpu.pc.in > $(DESTDIR)$(PKGCONFIGDIR)/libvgpu.pc + # Also create libiov.pc + sed -e "s|@PREFIX@|$(PREFIX)|g" \ + -e "s|@LIBDIR@|$(LIBDIR)|g" \ + -e "s|@INCLUDEDIR@|$(INCLUDEDIR)|g" \ + -e "s|@VERSION@|$(LIB_VERSION)|g" \ + -e "s|libvgpu|libiov|g" \ + libvgpu.pc.in > $(DESTDIR)$(PKGCONFIGDIR)/libiov.pc + +uninstall: + rm -f $(DESTDIR)$(LIBDIR)/$(LIB_NAME) + rm -f $(DESTDIR)$(LIBDIR)/$(LIB_SONAME) + rm -f $(DESTDIR)$(LIBDIR)/$(LIB_NAME).$(LIB_VERSION) + @for link in $(COMPAT_NAMES); do \ + rm -f $(DESTDIR)$(LIBDIR)/$$link; \ + done + rm -f $(DESTDIR)$(INCLUDEDIR)/libvgpu.h + rm -f $(DESTDIR)$(INCLUDEDIR)/rpc_emulator.h + rm -f $(DESTDIR)$(PKGCONFIGDIR)/libvgpu.pc + rm -f $(DESTDIR)$(PKGCONFIGDIR)/libiov.pc + +clean: + rm -rf $(OBJ_DIR) $(LIB_NAME) $(LIB_SONAME) $(LIB_NAME).$(LIB_VERSION) $(COMPAT_NAMES) + +.PHONY: all install uninstall clean compat-links
\ No newline at end of file diff --git a/userspace/lib/libiov.pc.in b/userspace/lib/libiov.pc.in new file mode 100644 index 0000000..ddc1fc3 --- /dev/null +++ b/userspace/lib/libiov.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +exec_prefix=${prefix} +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@ + +Name: libiov +Description: Open-IOV vGPU Userspace Library +Version: @VERSION@ +Libs: -L${libdir} -liov +Cflags: -I${includedir}
\ No newline at end of file diff --git a/userspace/lib/libvgpu.c b/userspace/lib/libvgpu.c new file mode 100644 index 0000000..a25a228 --- /dev/null +++ b/userspace/lib/libvgpu.c @@ -0,0 +1,894 @@ +/* + * GSP vGPU-IO Userspace Library Implementation + */ + +#define _GNU_SOURCE +#include "libvgpu.h" +#include "rpc_emulator.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <errno.h> +#include <pthread.h> +#include <poll.h> + +/* IOCTL definitions (must match kernel driver) */ +#define GSP_IOCTL_MAGIC 'G' +#define GSP_IOCTL_CREATE_VF _IOW(GSP_IOCTL_MAGIC, 0x01, struct gsp_vf_config) +#define GSP_IOCTL_DESTROY_VF _IOW(GSP_IOCTL_MAGIC, 0x02, uint32_t) +#define GSP_IOCTL_ALLOC _IOWR(GSP_IOCTL_MAGIC, 0x10, struct gsp_alloc_args) +#define GSP_IOCTL_FREE _IOW(GSP_IOCTL_MAGIC, 0x11, struct gsp_free_args) +#define GSP_IOCTL_MAP _IOWR(GSP_IOCTL_MAGIC, 0x12, struct gsp_map_args) +#define GSP_IOCTL_UNMAP _IOW(GSP_IOCTL_MAGIC, 0x13, struct gsp_unmap_args) +#define GSP_IOCTL_PIN _IOWR(GSP_IOCTL_MAGIC, 0x14, struct gsp_pin_args) +#define GSP_IOCTL_UNPIN _IOW(GSP_IOCTL_MAGIC, 0x15, struct gsp_unpin_args) +#define GSP_IOCTL_BAR_CONFIG _IOWR(GSP_IOCTL_MAGIC, 0x16, struct gsp_bar_config_args) +#define GSP_IOCTL_BAR_FORCE _IOW(GSP_IOCTL_MAGIC, 0x17, struct gsp_bar_force_args) +#define GSP_IOCTL_BAR_SAVE _IOWR(GSP_IOCTL_MAGIC, 0x18, struct gsp_bar_save_args) +#define GSP_IOCTL_BAR_RESTORE _IOW(GSP_IOCTL_MAGIC, 0x19, struct gsp_bar_restore_args) +#define GSP_IOCTL_WPR_PROTECT _IOWR(GSP_IOCTL_MAGIC, 0x1A, struct gsp_wpr_args) +#define GSP_IOCTL_WPR_UNPROTECT _IOW(GSP_IOCTL_MAGIC, 0x1B, struct gsp_wpr_args) +#define GSP_IOCTL_WPR_TIMEOUT _IOW(GSP_IOCTL_MAGIC, 0x1C, struct gsp_wpr_timeout_args) +#define GSP_IOCTL_SCHEDULE _IOW(GSP_IOCTL_MAGIC, 0x1D, struct gsp_schedule_args) +#define GSP_IOCTL_YIELD _IOW(GSP_IOCTL_MAGIC, 0x1E, uint32_t) +#define GSP_IOCTL_MIGRATE _IOWR(GSP_IOCTL_MAGIC, 0x1F, struct gsp_migrate_args) +#define GSP_IOCTL_GET_INFO _IOR(GSP_IOCTL_MAGIC, 0x20, char[256]) + +/* Kernel argument structures */ +struct gsp_vf_config { + uint32_t id; + uint64_t bar1_offset; + uint64_t bar1_size; + uint64_t pt_base; + uint64_t pt_size; + uint64_t cmd_base; + uint64_t cmd_size; + uint32_t max_memory_mb; + uint32_t num_engines; +}; + +struct gsp_alloc_args { + uint32_t vf_id; + uint64_t size; + uint32_t type; + uint64_t alignment; + uint64_t va_hint; + uint32_t flags; + uint32_t pid; + uint64_t handle; + uint64_t va; + uint64_t actual_size; +}; + +struct gsp_free_args { + uint32_t vf_id; + uint64_t handle; +}; + +struct gsp_map_args { + uint32_t vf_id; + uint64_t handle; + uint64_t gpu_va; + uint64_t size; + uint32_t prot; + uint32_t flags; + uint64_t offset; + uint64_t mapped_va; +}; + +struct gsp_unmap_args { + uint32_t vf_id; + uint64_t handle; + uint64_t gpu_va; + uint64_t size; +}; + +struct gsp_pin_args { + uint32_t vf_id; + uint64_t handle; + uint64_t offset; + uint64_t size; + uint32_t flags; + uint64_t dma_addr; + uint32_t num_pages; +}; + +struct gsp_unpin_args { + uint32_t vf_id; + uint64_t handle; + uint64_t offset; + uint64_t size; +}; + +struct gsp_bar_config_args { + uint32_t vf_id; + uint64_t offset; + uint64_t size; +}; + +struct gsp_bar_force_args { + uint32_t vf_id; + uint32_t enable; +}; + +struct gsp_bar_save_args { + uint32_t vf_id; + void *buf; + size_t *size; +}; + +struct gsp_bar_restore_args { + uint32_t vf_id; + const void *buf; + size_t size; +}; + +struct gsp_wpr_args { + uint32_t vf_id; + uint64_t base; + uint64_t size; + uint32_t flags; +}; + +struct gsp_wpr_timeout_args { + uint32_t vf_id; + uint64_t timeout_ns; +}; + +struct gsp_schedule_args { + uint32_t vf_id; + uint64_t work_handle; + uint64_t callback; + uint64_t arg; + uint32_t priority; + uint32_t flags; +}; + +struct gsp_migrate_args { + uint32_t vf_id; + uint32_t phase; + uint32_t subcmd; + uint64_t src_vf; + uint64_t dst_vf; + uint64_t data_ptr; + uint64_t data_size; + uint32_t flags; + uint64_t token; +}; + +/* Internal structures */ +struct vgpu_device { + int fd; + char path[256]; + pthread_mutex_t lock; + pthread_t event_thread; + bool event_thread_running; + vgpu_event_callback_t event_cb; + void *event_ctx; + bool emulation_enabled; +}; + +struct vgpu_vf { + vgpu_device_t device; + uint32_t id; +}; + +/* Error mapping */ +static vgpu_error_t map_errno(int err) +{ + switch (err) { + case 0: return VGPU_OK; + case EINVAL: return VGPU_ERR_INVALID_ARG; + case ENODEV: return VGPU_ERR_NO_DEVICE; + case ENOMEM: return VGPU_ERR_NO_MEMORY; + case EBUSY: return VGPU_ERR_BUSY; + case ETIMEDOUT: return VGPU_ERR_TIMEOUT; + case EPERM: return VGPU_ERR_PERMISSION; + case ENOTSUP: return VGPU_ERR_NOT_SUPPORTED; + default: return VGPU_ERR_INTERNAL; + } +} + +/* ============================================================ + * Device Management + * ============================================================ */ + +vgpu_error_t vgpu_open(const char *device_path, vgpu_device_t *device) +{ + if (!device_path || !device) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = calloc(1, sizeof(*dev)); + if (!dev) + return VGPU_ERR_NO_MEMORY; + + dev->fd = open(device_path, O_RDWR | O_CLOEXEC); + if (dev->fd < 0) { + free(dev); + return map_errno(errno); + } + + strncpy(dev->path, device_path, sizeof(dev->path) - 1); + pthread_mutex_init(&dev->lock, NULL); + + /* Initialize RPC emulator for fallback when GSP lacks vGPU handlers */ + rpc_emu_init(); + dev->emulation_enabled = true; + rpc_emu_enable(true); + + *device = dev; + return VGPU_OK; +} + +void vgpu_close(vgpu_device_t device) +{ + if (!device) + return; + + struct vgpu_device *dev = device; + + if (dev->event_thread_running) { + dev->event_thread_running = false; + pthread_join(dev->event_thread, NULL); + } + + if (dev->emulation_enabled) { + rpc_emu_fini(); + dev->emulation_enabled = false; + } + + pthread_mutex_destroy(&dev->lock); + close(dev->fd); + free(dev); +} + +vgpu_error_t vgpu_get_info(vgpu_device_t device, char *buf, size_t len) +{ + if (!device || !buf || len == 0) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = device; + int ret = ioctl(dev->fd, GSP_IOCTL_GET_INFO, buf); + return map_errno(ret < 0 ? errno : 0); +} + +/* ============================================================ + * VF Management + * ============================================================ */ + +vgpu_error_t vgpu_vf_create(vgpu_device_t device, const vgpu_vf_config_t *config, vgpu_vf_t *vf) +{ + if (!device || !config || !vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = device; + struct gsp_vf_config kconfig = {0}; + + kconfig.id = config->id; + kconfig.bar1_offset = config->bar1_offset; + kconfig.bar1_size = config->bar1_size; + kconfig.pt_base = config->pt_base; + kconfig.pt_size = config->pt_size; + kconfig.cmd_base = config->cmd_base; + kconfig.cmd_size = config->cmd_size; + kconfig.max_memory_mb = config->max_memory_mb; + kconfig.num_engines = config->num_engines; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_CREATE_VF, &kconfig); + pthread_mutex_unlock(&dev->lock); + + if (ret < 0) + return map_errno(errno); + + struct vgpu_vf *v = calloc(1, sizeof(*v)); + if (!v) + return VGPU_ERR_NO_MEMORY; + + v->device = dev; + v->id = config->id; + *vf = v; + return VGPU_OK; +} + +vgpu_error_t vgpu_vf_destroy(vgpu_vf_t vf) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + uint32_t id = vf->id; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_DESTROY_VF, &id); + pthread_mutex_unlock(&dev->lock); + + free(vf); + return map_errno(ret < 0 ? errno : 0); +} + +vgpu_error_t vgpu_vf_get_info(vgpu_vf_t vf, char *buf, size_t len) +{ + if (!vf || !buf || len == 0) + return VGPU_ERR_INVALID_ARG; + + /* VF-specific info would require a new ioctl */ + snprintf(buf, len, "VF %u", vf->id); + return VGPU_OK; +} + +/* ============================================================ + * Memory Management + * ============================================================ */ + +vgpu_error_t vgpu_mem_alloc(vgpu_vf_t vf, uint64_t size, vgpu_mem_type_t type, + uint64_t alignment, uint64_t va_hint, + uint32_t flags, vgpu_handle_t *handle, uint64_t *va) +{ + if (!vf || !handle || !va) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_alloc_args args = {0}; + + args.vf_id = vf->id; + args.size = size; + args.type = type; + args.alignment = alignment; + args.va_hint = va_hint; + args.flags = flags; + args.pid = getpid(); + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_ALLOC, &args); + pthread_mutex_unlock(&dev->lock); + + if (ret < 0) + return map_errno(errno); + + *handle = args.handle; + *va = args.va; + return VGPU_OK; +} + +vgpu_error_t vgpu_mem_free(vgpu_vf_t vf, vgpu_handle_t handle) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_free_args args = {0}; + + args.vf_id = vf->id; + args.handle = handle; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_FREE, &args); + pthread_mutex_unlock(&dev->lock); + + return map_errno(ret < 0 ? errno : 0); +} + +vgpu_error_t vgpu_mem_map(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t gpu_va, + uint64_t size, uint32_t prot, uint32_t flags, + uint64_t offset, uint64_t *mapped_va) +{ + if (!vf || !mapped_va) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_map_args args = {0}; + + args.vf_id = vf->id; + args.handle = handle; + args.gpu_va = gpu_va; + args.size = size; + args.prot = prot; + args.flags = flags; + args.offset = offset; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_MAP, &args); + pthread_mutex_unlock(&dev->lock); + + if (ret < 0) + return map_errno(errno); + + *mapped_va = args.mapped_va; + return VGPU_OK; +} + +vgpu_error_t vgpu_mem_unmap(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t gpu_va, uint64_t size) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_unmap_args args = {0}; + + args.vf_id = vf->id; + args.handle = handle; + args.gpu_va = gpu_va; + args.size = size; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_UNMAP, &args); + pthread_mutex_unlock(&dev->lock); + + return map_errno(ret < 0 ? errno : 0); +} + +vgpu_error_t vgpu_mem_pin(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t offset, + uint64_t size, uint32_t flags, uint64_t *dma_addr) +{ + if (!vf || !dma_addr) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_pin_args args = {0}; + + args.vf_id = vf->id; + args.handle = handle; + args.offset = offset; + args.size = size; + args.flags = flags; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_PIN, &args); + pthread_mutex_unlock(&dev->lock); + + if (ret < 0) + return map_errno(errno); + + *dma_addr = args.dma_addr; + return VGPU_OK; +} + +vgpu_error_t vgpu_mem_unpin(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t offset, uint64_t size) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_unpin_args args = {0}; + + args.vf_id = vf->id; + args.handle = handle; + args.offset = offset; + args.size = size; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_UNPIN, &args); + pthread_mutex_unlock(&dev->lock); + + return map_errno(ret < 0 ? errno : 0); +} + +/* ============================================================ + * BAR Emulation + * ============================================================ */ + +static vgpu_error_t bar_fallback(struct vgpu_device *dev, vgpu_vf_t vf, int ioctl_err, int (*emu_fn)(uint32_t, uint64_t, uint64_t)) +{ + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int ret = emu_fn(vf->id, 0, 0); /* Will be overridden below */ + return map_errno(ret); + } + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_bar_config(vgpu_vf_t vf, uint64_t offset, uint64_t size) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_bar_config_args args = {0}; + + args.vf_id = vf->id; + args.offset = offset; + args.size = size; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_BAR_CONFIG, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + /* Fallback to userspace emulator if GSP lacks vGPU RPC handlers */ + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_bar_config(vf->id, offset, size); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_bar_force_access(vgpu_vf_t vf, bool enable) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_bar_force_args args = {0}; + + args.vf_id = vf->id; + args.enable = enable ? 1 : 0; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_BAR_FORCE, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_bar_force_access(vf->id, enable); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_bar_save(vgpu_vf_t vf, void *buf, size_t *size) +{ + if (!vf || !buf || !size) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_bar_save_args args = {0}; + + args.vf_id = vf->id; + args.buf = buf; + args.size = size; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_BAR_SAVE, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_bar_save(vf->id, buf, size); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_bar_restore(vgpu_vf_t vf, const void *buf, size_t size) +{ + if (!vf || !buf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_bar_restore_args args = {0}; + + args.vf_id = vf->id; + args.buf = buf; + args.size = size; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_BAR_RESTORE, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_bar_restore(vf->id, buf, size); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +/* ============================================================ + * WPR Management + * ============================================================ */ + +vgpu_error_t vgpu_wpr_protect_write(vgpu_vf_t vf, uint64_t base, uint64_t size) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_wpr_args args = {0}; + + args.vf_id = vf->id; + args.base = base; + args.size = size; + args.flags = VGPU_WPR_WRITE; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_WPR_PROTECT, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_wpr_protect_write(vf->id, base, size); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_wpr_protect_read(vgpu_vf_t vf, uint64_t base, uint64_t size) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_wpr_args args = {0}; + + args.vf_id = vf->id; + args.base = base; + args.size = size; + args.flags = VGPU_WPR_READ; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_WPR_PROTECT, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_wpr_protect_read(vf->id, base, size); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_wpr_unprotect(vgpu_vf_t vf, uint64_t base) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_wpr_args args = {0}; + + args.vf_id = vf->id; + args.base = base; + args.size = 0; + args.flags = 0; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_WPR_UNPROTECT, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_wpr_unprotect(vf->id, base); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_wpr_set_timeout(vgpu_vf_t vf, uint64_t timeout_ns) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_wpr_timeout_args args = {0}; + + args.vf_id = vf->id; + args.timeout_ns = timeout_ns; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_WPR_TIMEOUT, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_wpr_set_timeout(vf->id, timeout_ns); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_wpr_register_callback(vgpu_vf_t vf, uint32_t event_type, + vgpu_wpr_callback_t cb, void *ctx) +{ + /* Callback registration would require kernel support for eventfd or similar */ + (void)vf; (void)event_type; (void)cb; (void)ctx; + return VGPU_ERR_NOT_SUPPORTED; +} + +/* ============================================================ + * Scheduling + * ============================================================ */ + +vgpu_error_t vgpu_schedule(vgpu_vf_t vf, uint64_t work_handle, uint64_t callback, + uint64_t arg, uint32_t priority, uint32_t flags) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_schedule_args args = {0}; + + args.vf_id = vf->id; + args.work_handle = work_handle; + args.callback = callback; + args.arg = arg; + args.priority = priority; + args.flags = flags; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_SCHEDULE, &args); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + /* Fallback to userspace emulator if GSP lacks vGPU RPC handlers */ + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_schedule(vf->id, work_handle, callback, arg, priority, flags); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +vgpu_error_t vgpu_yield(vgpu_vf_t vf) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + uint32_t id = vf->id; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_YIELD, &id); + int ioctl_err = ret < 0 ? errno : 0; + pthread_mutex_unlock(&dev->lock); + + if (ret == 0) + return VGPU_OK; + + if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) { + int emu_ret = rpc_emu_yield(vf->id); + return map_errno(emu_ret); + } + + return map_errno(ioctl_err); +} + +/* ============================================================ + * Migration + * ============================================================ */ + +vgpu_error_t vgpu_migrate_start(vgpu_vf_t vf, uint64_t dst_vf, uint32_t flags) +{ + if (!vf) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = vf->device; + struct gsp_migrate_args args = {0}; + + args.vf_id = vf->id; + args.phase = VGPU_MIG_PREPARE; + args.subcmd = 3; /* Quiesce */ + args.src_vf = vf->id; + args.dst_vf = dst_vf; + args.flags = flags; + + pthread_mutex_lock(&dev->lock); + int ret = ioctl(dev->fd, GSP_IOCTL_MIGRATE, &args); + pthread_mutex_unlock(&dev->lock); + + return map_errno(ret < 0 ? errno : 0); +} + +vgpu_error_t vgpu_migrate_status(vgpu_vf_t vf, vgpu_mig_phase_t *phase, uint64_t *progress) +{ + /* Migration status would require a query ioctl */ + (void)vf; (void)phase; (void)progress; + return VGPU_ERR_NOT_SUPPORTED; +} + +vgpu_error_t vgpu_migrate_cancel(vgpu_vf_t vf) +{ + /* Migration cancel would require a cancel ioctl */ + (void)vf; + return VGPU_ERR_NOT_SUPPORTED; +} + +/* ============================================================ + * Error Handling + * ============================================================ */ + +const char *vgpu_strerror(vgpu_error_t err) +{ + switch (err) { + case VGPU_OK: return "Success"; + case VGPU_ERR_INVALID_ARG: return "Invalid argument"; + case VGPU_ERR_NO_DEVICE: return "No such device"; + case VGPU_ERR_NO_MEMORY: return "Out of memory"; + case VGPU_ERR_BUSY: return "Device or resource busy"; + case VGPU_ERR_TIMEOUT: return "Operation timed out"; + case VGPU_ERR_PERMISSION: return "Permission denied"; + case VGPU_ERR_NOT_SUPPORTED: return "Operation not supported"; + case VGPU_ERR_INTERNAL: return "Internal error"; + default: return "Unknown error"; + } +} + +/* ============================================================ + * Event Handling + * ============================================================ */ + +vgpu_error_t vgpu_register_event_callback(vgpu_vf_t vf, vgpu_event_type_t type, + vgpu_event_callback_t cb, void *ctx) +{ + struct vgpu_device *dev = vf->device; + + /* Try kernel ioctl first */ + /* Would need a new ioctl for notification registration */ + + /* Fallback to userspace emulator */ + if (dev->emulation_enabled) { + rpc_emu_notify_cb_t emu_cb = (rpc_emu_notify_cb_t)cb; + int ret = rpc_emu_notify_register(vf->id, type, emu_cb, ctx); + return map_errno(ret); + } + + return VGPU_ERR_NOT_SUPPORTED; +} + +vgpu_error_t vgpu_unregister_event_callback(vgpu_vf_t vf, vgpu_event_type_t type) +{ + struct vgpu_device *dev = vf->device; + + if (dev->emulation_enabled) { + int ret = rpc_emu_notify_unregister(vf->id, type); + return map_errno(ret); + } + + return VGPU_ERR_NOT_SUPPORTED; +} + +vgpu_error_t vgpu_process_events(vgpu_device_t device, int timeout_ms) +{ + if (!device) + return VGPU_ERR_INVALID_ARG; + + struct vgpu_device *dev = device; + struct pollfd pfd = { .fd = dev->fd, .events = POLLIN }; + + int ret = poll(&pfd, 1, timeout_ms); + if (ret < 0) + return map_errno(errno); + + if (ret == 0) + return VGPU_ERR_TIMEOUT; + + /* Process events would read from device */ + return VGPU_OK; +}
\ No newline at end of file diff --git a/userspace/lib/libvgpu.pc.in b/userspace/lib/libvgpu.pc.in new file mode 100644 index 0000000..82ba073 --- /dev/null +++ b/userspace/lib/libvgpu.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +exec_prefix=${prefix} +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@ + +Name: libvgpu +Description: GSP vGPU-IO Userspace Library +Version: @VERSION@ +Libs: -L${libdir} -lvgpu +Cflags: -I${includedir}
\ No newline at end of file diff --git a/userspace/lib/rpc_emulator.c b/userspace/lib/rpc_emulator.c new file mode 100644 index 0000000..9f7f2c7 --- /dev/null +++ b/userspace/lib/rpc_emulator.c @@ -0,0 +1,528 @@ +/* + * GSP vGPU-IO Userspace RPC Emulator + * + * Emulates missing vGPU RPC handlers (BAR_TRAP, WPR, etc.) that exist only + * in GRID host driver GSP firmware. Allows operation with consumer/OpenRM GSP. + */ + +#define _GNU_SOURCE +#include "rpc_emulator.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> + +/* ============================================================ + * BAR Emulation State + * ============================================================ */ + +struct emu_bar_state { + uint64_t aperture_offset; + uint64_t aperture_size; + bool force_access; + uint32_t force_vf; + void *backing_store; + size_t backing_size; + pthread_mutex_t lock; +}; + +static struct emu_bar_state g_bar_state = { + .aperture_offset = 0, + .aperture_size = 0, + .force_access = false, + .force_vf = 0, + .backing_store = NULL, + .backing_size = 0, +}; + +/* ============================================================ + * WPR Emulation State + * ============================================================ */ + +struct emu_wpr_region { + uint64_t base; + uint64_t size; + uint32_t flags; + uint64_t timeout_ns; + struct emu_wpr_region *next; +}; + +struct emu_wpr_state { + struct emu_wpr_region *regions; + pthread_mutex_t lock; +}; + +static struct emu_wpr_state g_wpr_state = { + .regions = NULL, +}; + +/* ============================================================ + * Emulation Mode Control + * ============================================================ */ + +static bool g_emulation_enabled = false; +static pthread_mutex_t g_emu_lock = PTHREAD_MUTEX_INITIALIZER; + +void rpc_emu_enable(bool enable) +{ + pthread_mutex_lock(&g_emu_lock); + g_emulation_enabled = enable; + pthread_mutex_unlock(&g_emu_lock); +} + +bool rpc_emu_is_enabled(void) +{ + bool enabled; + pthread_mutex_lock(&g_emu_lock); + enabled = g_emulation_enabled; + pthread_mutex_unlock(&g_emu_lock); + return enabled; +} + +/* ============================================================ + * BAR Emulation Implementation + * ============================================================ */ + +int rpc_emu_bar_config(uint32_t vf_id, uint64_t offset, uint64_t size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + if (offset & 0xFFF || size & 0xFFF) + return -EINVAL; + + if (size > 0x40000) /* 256KB max per firmware spec */ + return -EINVAL; + + pthread_mutex_lock(&g_bar_state.lock); + + g_bar_state.aperture_offset = offset; + g_bar_state.aperture_size = size; + + /* Allocate/resize backing store */ + if (g_bar_state.backing_store) { + free(g_bar_state.backing_store); + g_bar_state.backing_store = NULL; + g_bar_state.backing_size = 0; + } + + if (size > 0) { + g_bar_state.backing_size = size; + g_bar_state.backing_store = calloc(1, size); + if (!g_bar_state.backing_store) { + g_bar_state.backing_size = 0; + pthread_mutex_unlock(&g_bar_state.lock); + return -ENOMEM; + } + } + + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +int rpc_emu_bar_force_access(uint32_t vf_id, bool enable) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_bar_state.lock); + g_bar_state.force_access = enable; + g_bar_state.force_vf = enable ? vf_id : 0; + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +int rpc_emu_bar_save(uint32_t vf_id, void *buf, size_t *size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + if (!buf || !size) + return -EINVAL; + + pthread_mutex_lock(&g_bar_state.lock); + + size_t needed = sizeof(struct emu_bar_state) + g_bar_state.backing_size; + if (*size < needed) { + *size = needed; + pthread_mutex_unlock(&g_bar_state.lock); + return -ENOSPC; + } + + memcpy(buf, &g_bar_state, sizeof(struct emu_bar_state)); + if (g_bar_state.backing_store && g_bar_state.backing_size > 0) + memcpy(buf + sizeof(struct emu_bar_state), g_bar_state.backing_store, g_bar_state.backing_size); + + *size = needed; + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +int rpc_emu_bar_restore(uint32_t vf_id, const void *buf, size_t size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + if (!buf || size < sizeof(struct emu_bar_state)) + return -EINVAL; + + pthread_mutex_lock(&g_bar_state.lock); + + memcpy(&g_bar_state, buf, sizeof(struct emu_bar_state)); + + if (size > sizeof(struct emu_bar_state) && g_bar_state.backing_size > 0) { + if (!g_bar_state.backing_store) { + g_bar_state.backing_store = calloc(1, g_bar_state.backing_size); + if (!g_bar_state.backing_store) { + pthread_mutex_unlock(&g_bar_state.lock); + return -ENOMEM; + } + } + memcpy(g_bar_state.backing_store, buf + sizeof(struct emu_bar_state), + size - sizeof(struct emu_bar_state)); + } + + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +int rpc_emu_bar_get_state(uint32_t vf_id, uint64_t *offset, uint64_t *size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_bar_state.lock); + if (offset) *offset = g_bar_state.aperture_offset; + if (size) *size = g_bar_state.aperture_size; + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +/* ============================================================ + * WPR Emulation Implementation + * ============================================================ */ + +static struct emu_wpr_region *emu_wpr_find(uint64_t base) +{ + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + if (rgn->base == base) + return rgn; + rgn = rgn->next; + } + return NULL; +} + +static struct emu_wpr_region *emu_wpr_find_containing(uint64_t addr) +{ + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + if (addr >= rgn->base && addr < rgn->base + rgn->size) + return rgn; + rgn = rgn->next; + } + return NULL; +} + +int rpc_emu_wpr_protect_write(uint32_t vf_id, uint64_t base, uint64_t size) +{ + return rpc_emu_wpr_protect(vf_id, base, size, VGPU_WPR_WRITE); +} + +int rpc_emu_wpr_protect_read(uint32_t vf_id, uint64_t base, uint64_t size) +{ + return rpc_emu_wpr_protect(vf_id, base, size, VGPU_WPR_READ); +} + +int rpc_emu_wpr_protect(uint32_t vf_id, uint64_t base, uint64_t size, uint32_t flags) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + if (base & 0xFFF || size & 0xFFF) + return -EINVAL; + + pthread_mutex_lock(&g_wpr_state.lock); + + /* Check for overlap */ + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + if (base < rgn->base + rgn->size && base + size > rgn->base) { + pthread_mutex_unlock(&g_wpr_state.lock); + return -EBUSY; /* Overlaps existing region */ + } + rgn = rgn->next; + } + + struct emu_wpr_region *new_rgn = calloc(1, sizeof(*new_rgn)); + if (!new_rgn) { + pthread_mutex_unlock(&g_wpr_state.lock); + return -ENOMEM; + } + + new_rgn->base = base; + new_rgn->size = size; + new_rgn->flags = flags; + new_rgn->timeout_ns = 10000000; /* 10ms default */ + + new_rgn->next = g_wpr_state.regions; + g_wpr_state.regions = new_rgn; + + pthread_mutex_unlock(&g_wpr_state.lock); + return 0; +} + +int rpc_emu_wpr_unprotect(uint32_t vf_id, uint64_t base) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_wpr_state.lock); + + struct emu_wpr_region **prev = &g_wpr_state.regions; + struct emu_wpr_region *rgn = g_wpr_state.regions; + bool found = false; + + while (rgn) { + if (rgn->base == base) { + *prev = rgn->next; + free(rgn); + found = true; + break; + } + prev = &rgn->next; + rgn = rgn->next; + } + + pthread_mutex_unlock(&g_wpr_state.lock); + return found ? 0 : -ENOENT; +} + +int rpc_emu_wpr_set_timeout(uint32_t vf_id, uint64_t timeout_ns) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_wpr_state.lock); + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + rgn->timeout_ns = timeout_ns; + rgn = rgn->next; + } + pthread_mutex_unlock(&g_wpr_state.lock); + return 0; +} + +int rpc_emu_wpr_check_access(uint32_t vf_id, uint64_t addr, uint32_t access_type) +{ + if (!rpc_emu_is_enabled()) + return 0; /* Allow if emulation disabled */ + + pthread_mutex_lock(&g_wpr_state.lock); + struct emu_wpr_region *rgn = emu_wpr_find_containing(addr); + int ret = 0; + + if (rgn) { + if ((access_type == VGPU_WPR_WRITE && (rgn->flags & VGPU_WPR_WRITE)) || + (access_type == VGPU_WPR_READ && (rgn->flags & VGPU_WPR_READ))) { + ret = -EPERM; /* Access violation */ + } + } + + pthread_mutex_unlock(&g_wpr_state.lock); + return 0; +} + +/* ============================================================ + * Scheduling Emulation Implementation + * ============================================================ */ + +struct emu_sched_work { + uint64_t work_handle; + uint64_t callback; + uint64_t arg; + uint32_t priority; + uint32_t flags; + struct emu_sched_work *next; +}; + +static struct emu_sched_work *g_sched_queue = NULL; +static pthread_mutex_t g_sched_lock = PTHREAD_MUTEX_INITIALIZER; + +int rpc_emu_schedule(uint32_t vf_id, uint64_t work_handle, uint64_t callback, uint64_t arg, uint32_t priority, uint32_t flags) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + struct emu_sched_work *work = calloc(1, sizeof(*work)); + if (!work) + return -ENOMEM; + + work->work_handle = work_handle; + work->callback = callback; + work->arg = arg; + work->priority = priority; + work->flags = flags; + + pthread_mutex_lock(&g_sched_lock); + work->next = g_sched_queue; + g_sched_queue = work; + pthread_mutex_unlock(&g_sched_lock); + + /* In emulation, we just return success - work is queued locally */ + (void)vf_id; + return 0; +} + +int rpc_emu_yield(uint32_t vf_id) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + /* In emulation, yield is a no-op */ + (void)vf_id; + return 0; +} + +/* ============================================================ + * Notification Emulation Implementation + * ============================================================ */ + +struct emu_notify_cb { + uint32_t type; + rpc_emu_notify_cb_t callback; + void *ctx; + struct emu_notify_cb *next; +}; + +static struct emu_notify_cb *g_notify_callbacks = NULL; +static pthread_mutex_t g_notify_lock = PTHREAD_MUTEX_INITIALIZER; + +int rpc_emu_notify_register(uint32_t vf_id, uint32_t type, rpc_emu_notify_cb_t cb, void *ctx) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + struct emu_notify_cb *nc = calloc(1, sizeof(*nc)); + if (!nc) + return -ENOMEM; + + nc->type = type; + nc->callback = cb; + nc->ctx = ctx; + + pthread_mutex_lock(&g_notify_lock); + nc->next = g_notify_callbacks; + g_notify_callbacks = nc; + pthread_mutex_unlock(&g_notify_lock); + + (void)vf_id; + return 0; +} + +int rpc_emu_notify_unregister(uint32_t vf_id, uint32_t type) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_notify_lock); + struct emu_notify_cb **prev = &g_notify_callbacks; + struct emu_notify_cb *nc = g_notify_callbacks; + bool found = false; + + while (nc) { + if (nc->type == type) { + *prev = nc->next; + free(nc); + found = true; + break; + } + prev = &nc->next; + nc = nc->next; + } + pthread_mutex_unlock(&g_notify_lock); + + (void)vf_id; + return found ? 0 : -ENOENT; +} + +int rpc_emu_notify_send(uint32_t vf_id, uint32_t type, void *data, size_t size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_notify_lock); + struct emu_notify_cb *nc = g_notify_callbacks; + while (nc) { + if (nc->type == type || nc->type == 0) { + nc->callback(vf_id, type, data, size, nc->ctx); + } + nc = nc->next; + } + pthread_mutex_unlock(&g_notify_lock); + + return 0; +} + +/* ============================================================ + * Initialization / Cleanup + * ============================================================ */ + +int rpc_emu_init(void) +{ + pthread_mutex_init(&g_bar_state.lock, NULL); + pthread_mutex_init(&g_wpr_state.lock, NULL); + pthread_mutex_init(&g_sched_lock, NULL); + pthread_mutex_init(&g_notify_lock, NULL); + return 0; +} + +void rpc_emu_fini(void) +{ + pthread_mutex_lock(&g_bar_state.lock); + if (g_bar_state.backing_store) { + free(g_bar_state.backing_store); + g_bar_state.backing_store = NULL; + g_bar_state.backing_size = 0; + } + pthread_mutex_unlock(&g_bar_state.lock); + pthread_mutex_destroy(&g_bar_state.lock); + + pthread_mutex_lock(&g_wpr_state.lock); + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + struct emu_wpr_region *next = rgn->next; + free(rgn); + rgn = next; + } + g_wpr_state.regions = NULL; + pthread_mutex_unlock(&g_wpr_state.lock); + pthread_mutex_destroy(&g_wpr_state.lock); + + pthread_mutex_lock(&g_sched_lock); + struct emu_sched_work *work = g_sched_queue; + while (work) { + struct emu_sched_work *next = work->next; + free(work); + work = next; + } + g_sched_queue = NULL; + pthread_mutex_unlock(&g_sched_lock); + pthread_mutex_destroy(&g_sched_lock); + + pthread_mutex_lock(&g_notify_lock); + struct emu_notify_cb *nc = g_notify_callbacks; + while (nc) { + struct emu_notify_cb *next = nc->next; + free(nc); + nc = next; + } + g_notify_callbacks = NULL; + pthread_mutex_unlock(&g_notify_lock); + pthread_mutex_destroy(&g_notify_lock); + + pthread_mutex_destroy(&g_emu_lock); +} diff --git a/userspace/lib/rpc_emulator.h b/userspace/lib/rpc_emulator.h new file mode 100644 index 0000000..1b641bf --- /dev/null +++ b/userspace/lib/rpc_emulator.h @@ -0,0 +1,53 @@ +/* + * GSP vGPU-IO Userspace RPC Emulator - Header + */ + +#ifndef _RPC_EMULATOR_H_ +#define _RPC_EMULATOR_H_ + +#include <stdint.h> +#include <stdbool.h> +#include "libvgpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Emulation mode control */ +void rpc_emu_enable(bool enable); +bool rpc_emu_is_enabled(void); + +/* BAR Emulation */ +int rpc_emu_bar_config(uint32_t vf_id, uint64_t offset, uint64_t size); +int rpc_emu_bar_force_access(uint32_t vf_id, bool enable); +int rpc_emu_bar_save(uint32_t vf_id, void *buf, size_t *size); +int rpc_emu_bar_restore(uint32_t vf_id, const void *buf, size_t size); +int rpc_emu_bar_get_state(uint32_t vf_id, uint64_t *offset, uint64_t *size); + +/* WPR Emulation */ +int rpc_emu_wpr_protect_write(uint32_t vf_id, uint64_t base, uint64_t size); +int rpc_emu_wpr_protect_read(uint32_t vf_id, uint64_t base, uint64_t size); +int rpc_emu_wpr_protect(uint32_t vf_id, uint64_t base, uint64_t size, uint32_t flags); +int rpc_emu_wpr_unprotect(uint32_t vf_id, uint64_t base); +int rpc_emu_wpr_set_timeout(uint32_t vf_id, uint64_t timeout_ns); +int rpc_emu_wpr_check_access(uint32_t vf_id, uint64_t addr, uint32_t access_type); + +/* Scheduling Emulation */ +int rpc_emu_schedule(uint32_t vf_id, uint64_t work_handle, uint64_t callback, uint64_t arg, uint32_t priority, uint32_t flags); +int rpc_emu_yield(uint32_t vf_id); + +/* Notification Emulation */ +typedef void (*rpc_emu_notify_cb_t)(uint32_t vf_id, uint32_t type, void *data, size_t size, void *ctx); +int rpc_emu_notify_register(uint32_t vf_id, uint32_t type, rpc_emu_notify_cb_t cb, void *ctx); +int rpc_emu_notify_unregister(uint32_t vf_id, uint32_t type); +int rpc_emu_notify_send(uint32_t vf_id, uint32_t type, void *data, size_t size); + +/* Init/Fini */ +int rpc_emu_init(void); +void rpc_emu_fini(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _RPC_EMULATOR_H_ */ diff --git a/userspace/tests/Makefile b/userspace/tests/Makefile new file mode 100644 index 0000000..3be451d --- /dev/null +++ b/userspace/tests/Makefile @@ -0,0 +1,22 @@ +# Userspace Test Makefile + +CC = gcc +CFLAGS = -Wall -Wextra -O2 -std=gnu11 -I../../kernel/include +LDFLAGS = + +TARGET = test_vgpu_userspace +SRC = test_vgpu_userspace.c + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) -o $(TARGET) $(SRC) $(LDFLAGS) + +clean: + rm -f $(TARGET) + +test: $(TARGET) + @echo "Running userspace test (requires driver loaded)..." + sudo ./$(TARGET) + +.PHONY: all clean test
\ No newline at end of file diff --git a/userspace/tests/test_guest_init.c b/userspace/tests/test_guest_init.c new file mode 100644 index 0000000..135c16f --- /dev/null +++ b/userspace/tests/test_guest_init.c @@ -0,0 +1,176 @@ +/* + * Test for guest vGPU driver initialization + * This test verifies that the guest vGPU driver can initialize properly + * when passed through to a VM via VFIO/mdev + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <linux/vfio.h> +#include <linux/mdev.h> +#include <errno.h> + +#define NV_VGPU_IOCTL_MAGIC 'N' +#define NV_VGPU_IOCTL_GET_INFO \ + _IOWR(NV_VGPU_IOCTL_MAGIC, 0x01, struct nv_vgpu_ioctl_get_info) +#define NV_VGPU_IOCTL_MAP_BAR \ + _IOWR(NV_VGPU_IOCTL_MAGIC, 0x02, struct nv_vgpu_ioctl_map_bar) +#define NV_VGPU_IOCTL_GSP_RPC \ + _IOWR(NV_VGPU_IOCTL_MAGIC, 0x06, struct nv_vgpu_ioctl_gsp_rpc) + +struct nv_vgpu_ioctl_get_info { + uint32_t vgpu_type; + uint32_t vgpu_id; + uint32_t gpu_instance_id; + uint32_t compute_instance_id; + uint32_t state; + uint64_t fb_base; + uint64_t fb_size; + uint64_t bar0_config_base; + uint32_t num_bars; + struct { + uint32_t index; + uint64_t gpa; + uint64_t size; + uint64_t iova; + bool mapped; + bool trapped; + } bars[8]; +}; + +struct nv_vgpu_ioctl_map_bar { + uint32_t bar_index; + uint64_t gpa; + uint64_t size; + uint64_t iova; + uint32_t flags; +}; + +struct nv_vgpu_ioctl_gsp_rpc { + uint32_t type; + uint32_t flags; + uint64_t data_ptr; + uint32_t data_size; + uint32_t result; +}; + +int main(int argc, char *argv[]) +{ + int ret = 0; + int vfio_fd = -1; + int mdev_fd = -1; + char *mdev_path = NULL; + struct nv_vgpu_ioctl_get_info info = {0}; + struct nv_vgpu_ioctl_map_bar map_bar = {0}; + struct vfio_device_info dev_info = {0}; + + printf("Testing guest vGPU driver initialization...\n"); + + if (argc < 2) { + printf("Usage: %s <mdev_uuid>\n", argv[0]); + printf("Example: %s 00000000-0000-0000-0000-000000000000\n", argv[0]); + return 1; + } + + mdev_path = argv[1]; + + vfio_fd = open("/dev/vfio/vfio", O_RDWR); + if (vfio_fd < 0) { + printf("FAIL: Cannot open /dev/vfio/vfio: %s\n", strerror(errno)); + return 1; + } + + dev_info.argsz = sizeof(dev_info); + if (ioctl(vfio_fd, VFIO_GET_API_VERSION, &dev_info) < 0) { + printf("FAIL: VFIO_GET_API_VERSION failed: %s\n", strerror(errno)); + ret = 1; + goto cleanup; + } + + if (dev_info.version != VFIO_API_VERSION) { + printf("FAIL: VFIO API version mismatch\n"); + ret = 1; + goto cleanup; + } + + printf("PASS: VFIO API version %d\n", dev_info.version); + + char mdev_dev_path[256]; + snprintf(mdev_dev_path, sizeof(mdev_dev_path), "/dev/vfio/%s", mdev_path); + + mdev_fd = open(mdev_dev_path, O_RDWR); + if (mdev_fd < 0) { + printf("FAIL: Cannot open mdev device %s: %s\n", mdev_dev_path, strerror(errno)); + ret = 1; + goto cleanup; + } + + printf("PASS: Opened mdev device %s\n", mdev_dev_path); + + dev_info.argsz = sizeof(dev_info); + if (ioctl(mdev_fd, VFIO_DEVICE_GET_INFO, &dev_info) < 0) { + printf("FAIL: VFIO_DEVICE_GET_INFO failed: %s\n", strerror(errno)); + ret = 1; + goto cleanup; + } + + printf("PASS: Device info - flags: 0x%x, num_regions: %d, num_irqs: %d\n", + dev_info.flags, dev_info.num_regions, dev_info.num_irqs); + + if (ioctl(mdev_fd, NV_VGPU_IOCTL_GET_INFO, &info) < 0) { + printf("FAIL: NV_VGPU_IOCTL_GET_INFO failed: %s\n", strerror(errno)); + ret = 1; + goto cleanup; + } + + printf("PASS: vGPU info - type: %d, id: %d, state: %d\n", + info.vgpu_type, info.vgpu_id, info.state); + printf(" fb_base: 0x%lx, fb_size: 0x%lx\n", info.fb_base, info.fb_size); + printf(" bar0_config_base: 0x%lx\n", info.bar0_config_base); + printf(" num_bars: %d\n", info.num_bars); + + for (int i = 0; i < info.num_bars; i++) { + printf(" BAR%d: gpa=0x%lx, size=0x%lx, iova=0x%lx, mapped=%d, trapped=%d\n", + info.bars[i].index, info.bars[i].gpa, info.bars[i].size, + info.bars[i].iova, info.bars[i].mapped, info.bars[i].trapped); + } + + if (info.num_bars > 0) { + map_bar.bar_index = 0; + map_bar.gpa = info.bars[0].gpa; + map_bar.size = info.bars[0].size; + map_bar.flags = 0x1; + + if (ioctl(mdev_fd, NV_VGPU_IOCTL_MAP_BAR, &map_bar) < 0) { + printf("FAIL: NV_VGPU_IOCTL_MAP_BAR failed: %s\n", strerror(errno)); + ret = 1; + goto cleanup; + } + + printf("PASS: Mapped BAR0 at IOVA 0x%lx\n", map_bar.iova); + } + + struct nv_vgpu_ioctl_gsp_rpc rpc = {0}; + rpc.type = 0x01; + rpc.data_size = 0; + + if (ioctl(mdev_fd, NV_VGPU_IOCTL_GSP_RPC, &rpc) < 0) { + printf("WARN: NV_VGPU_IOCTL_GSP_RPC failed: %s (may not be implemented yet)\n", strerror(errno)); + } else { + printf("PASS: GSP RPC test completed\n"); + } + + printf("\nAll guest initialization tests PASSED\n"); + +cleanup: + if (mdev_fd >= 0) + close(mdev_fd); + if (vfio_fd >= 0) + close(vfio_fd); + + return ret; +}
\ No newline at end of file diff --git a/userspace/tests/test_host_init.c b/userspace/tests/test_host_init.c new file mode 100644 index 0000000..a08fe33 --- /dev/null +++ b/userspace/tests/test_host_init.c @@ -0,0 +1,68 @@ +/* + * Test for host vGPU initialization + */ +#include <gvm/apis.h> +#include <gvm/nvidia/manager.h> +#include <gvm/nvidia/creator.h> +#include <gvm/nvidia/open/signature.h> +#include <utils/configs.h> + +#include <stdio.h> +#include <stdlib.h> + +int main(int argc, char *argv[]) +{ + int ret = 0; + enum SupportedGpus selected_gpu = Nvidia; + const struct ManagerAPI *api = MANAGER_APIS[selected_gpu]; + struct NvManager *mgr = NULL; + + printf("Testing host vGPU initialization...\n"); + + mgr = calloc(1, sizeof(struct NvManager)); + if (!mgr) { + printf("Failed to allocate manager\n"); + return 1; + } + + mgr->ignore_version_check = 1; + mgr->create_signature = nvidia_open_create_signature; + mgr->use_vmiop = 1; + mgr->vmiop_plugin = "/usr/lib/x86_64-linux-gnu/libnvidia-vgpu.so"; + + if (api->init(mgr)) { + printf("FAIL: Manager initialization failed\n"); + ret = 1; + goto cleanup; + } + + printf("PASS: Manager initialized successfully\n"); + + if (!api->running(mgr)) { + printf("FAIL: Manager not running\n"); + ret = 1; + goto cleanup; + } + + printf("PASS: Manager is running\n"); + + if (mgr->vmiop) { + printf("PASS: VMIOP initialized\n"); + } else { + printf("FAIL: VMIOP not initialized\n"); + ret = 1; + } + +cleanup: + if (mgr) { + api->stop(mgr); + free(mgr); + } + + if (ret == 0) + printf("\nAll host initialization tests PASSED\n"); + else + printf("\nSome host initialization tests FAILED\n"); + + return ret; +}
\ No newline at end of file diff --git a/userspace/tests/test_vgpu_userspace.c b/userspace/tests/test_vgpu_userspace.c new file mode 100644 index 0000000..cd22111 --- /dev/null +++ b/userspace/tests/test_vgpu_userspace.c @@ -0,0 +1,283 @@ +/* + * GSP vGPU-IO Userspace Test + * Tests VFIO interface and driver functionality from userspace + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <linux/vfio.h> +#include <errno.h> +#include <stdint.h> +#include <inttypes.h> + +#define GSP_VGPU_DEVICE "/dev/vfio/gsp-vgpu" + +#define TEST_PASS 0 +#define TEST_FAIL 1 + +static int test_count = 0; +static int pass_count = 0; +static int fail_count = 0; + +#define TEST_ASSERT(cond, msg) \ + do { \ + test_count++; \ + if (cond) { \ + pass_count++; \ + printf("PASS: %s\n", msg); \ + } else { \ + fail_count++; \ + fprintf(stderr, "FAIL: %s\n", msg); \ + } \ + } while (0) + +#define TEST_ASSERT_EQ(a, b, msg) \ + TEST_ASSERT((a) == (b), msg " (expected " #b ", got " #a ")") + +/* IOCTL definitions matching kernel driver */ +#define GSP_IOCTL_MAGIC 'G' +#define GSP_IOCTL_CREATE_VF _IOW(GSP_IOCTL_MAGIC, 0x01, struct gsp_vf_config) +#define GSP_IOCTL_DESTROY_VF _IOW(GSP_IOCTL_MAGIC, 0x02, uint32_t) +#define GSP_IOCTL_ALLOC _IOWR(GSP_IOCTL_MAGIC, 0x10, struct gsp_alloc_args) +#define GSP_IOCTL_FREE _IOW(GSP_IOCTL_MAGIC, 0x11, struct gsp_free_args) +#define GSP_IOCTL_MAP _IOWR(GSP_IOCTL_MAGIC, 0x12, struct gsp_map_args) +#define GSP_IOCTL_UNMAP _IOW(GSP_IOCTL_MAGIC, 0x13, struct gsp_unmap_args) +#define GSP_IOCTL_PIN _IOWR(GSP_IOCTL_MAGIC, 0x14, struct gsp_pin_args) +#define GSP_IOCTL_UNPIN _IOW(GSP_IOCTL_MAGIC, 0x15, struct gsp_unpin_args) +#define GSP_IOCTL_BAR_TRAP _IOWR(GSP_IOCTL_MAGIC, 0x16, struct gsp_bar_trap_args) +#define GSP_IOCTL_MIGRATE _IOWR(GSP_IOCTL_MAGIC, 0x17, struct gsp_migrate_args) +#define GSP_IOCTL_WPR_PROTECT _IOWR(GSP_IOCTL_MAGIC, 0x18, struct gsp_wpr_args) +#define GSP_IOCTL_WPR_UNPROTECT _IOW(GSP_IOCTL_MAGIC, 0x19, struct gsp_wpr_args) + +struct gsp_vf_config { + uint32_t id; + uint64_t bar1_offset; + uint64_t bar1_size; + uint64_t pt_base; + uint64_t pt_size; + uint64_t cmd_base; + uint64_t cmd_size; + uint32_t max_memory_mb; + uint32_t num_engines; +}; + +struct gsp_alloc_args { + uint32_t vf_id; + uint64_t size; + uint32_t type; + uint64_t alignment; + uint64_t va_hint; + uint32_t flags; + uint32_t pid; + uint64_t handle; + uint64_t va; + uint64_t actual_size; +}; + +struct gsp_free_args { + uint32_t vf_id; + uint64_t handle; +}; + +struct gsp_map_args { + uint32_t vf_id; + uint64_t handle; + uint64_t gpu_va; + uint64_t size; + uint32_t prot; + uint32_t flags; + uint64_t offset; + uint64_t mapped_va; +}; + +struct gsp_unmap_args { + uint32_t vf_id; + uint64_t handle; + uint64_t gpu_va; + uint64_t size; +}; + +struct gsp_pin_args { + uint32_t vf_id; + uint64_t handle; + uint64_t offset; + uint64_t size; + uint32_t flags; + uint64_t dma_addr; + uint32_t num_pages; +}; + +struct gsp_unpin_args { + uint32_t vf_id; + uint64_t handle; + uint64_t offset; + uint64_t size; +}; + +struct gsp_bar_trap_args { + uint32_t vf_id; + uint32_t bar_index; + uint32_t trap_type; + uint64_t offset; + uint64_t value; + uint32_t size; + uint64_t ret_value; +}; + +struct gsp_migrate_args { + uint32_t vf_id; + uint32_t phase; + uint32_t subcmd; + uint64_t src_vf; + uint64_t dst_vf; + uint64_t data_ptr; + uint64_t data_size; + uint32_t flags; + uint64_t token; +}; + +struct gsp_wpr_args { + uint32_t vf_id; + uint64_t base; + uint64_t size; + uint32_t flags; +}; + +int main(int argc, char *argv[]) +{ + int fd, ret; + uint32_t vf_id = 1; + + printf("========================================\n"); + printf("GSP vGPU-IO Userspace Test Suite\n"); + printf("========================================\n"); + + /* Open device */ + fd = open(GSP_VGPU_DEVICE, O_RDWR); + if (fd < 0) { + fprintf(stderr, "Failed to open %s: %s\n", GSP_VGPU_DEVICE, strerror(errno)); + fprintf(stderr, "Make sure driver is loaded and VFIO device created\n"); + return TEST_FAIL; + } + printf("Opened device: %s (fd=%d)\n", GSP_VGPU_DEVICE, fd); + + /* Test 1: Create VF */ + { + struct gsp_vf_config config = { + .id = vf_id, + .bar1_offset = 0x10000000, + .bar1_size = 0x100000, + .pt_base = 0x20000000, + .pt_size = 0x100000, + .cmd_base = 0x30000000, + .cmd_size = 0x10000, + .max_memory_mb = 1024, + .num_engines = 4, + }; + + ret = ioctl(fd, GSP_IOCTL_CREATE_VF, &config); + TEST_ASSERT(ret == 0, "Create VF"); + if (ret == 0) + printf(" VF %u created successfully\n", vf_id); + } + + /* Test 2: Allocate memory */ + { + struct gsp_alloc_args args = { + .vf_id = vf_id, + .size = 0x10000, // 64KB + .type = 0, // VRAM + .alignment = 0x1000, + .va_hint = 0, + .flags = 0, + .pid = getpid(), + }; + + ret = ioctl(fd, GSP_IOCTL_ALLOC, &args); + TEST_ASSERT(ret == 0, "Allocate memory"); + if (ret == 0) { + printf(" Allocated: handle=0x%" PRIx64 ", va=0x%" PRIx64 ", size=0x%" PRIx64 "\n", + args.handle, args.va, args.actual_size); + } + } + + /* Test 3: Pin memory */ + { + struct gsp_pin_args args = { + .vf_id = vf_id, + .handle = 0, // Would use handle from alloc + .offset = 0, + .size = 0x1000, + .flags = 3, // READ | WRITE + }; + + ret = ioctl(fd, GSP_IOCTL_PIN, &args); + TEST_ASSERT(ret == 0 || errno == ENODEV, "Pin memory (may fail without real allocation)"); + } + + /* Test 4: BAR Trap */ + { + struct gsp_bar_trap_args args = { + .vf_id = vf_id, + .bar_index = 1, + .trap_type = 1, // WRITE + .offset = 0x1000, + .value = 0xDEADBEEF, + .size = 4, + }; + + ret = ioctl(fd, GSP_IOCTL_BAR_TRAP, &args); + TEST_ASSERT(ret == 0 || errno == ENODEV, "BAR trap (may fail without real HW)"); + } + + /* Test 5: WPR Protect */ + { + struct gsp_wpr_args args = { + .vf_id = vf_id, + .base = 0x10000000, + .size = 0x10000, + .flags = 3, // WRITE | READ + }; + + ret = ioctl(fd, GSP_IOCTL_WPR_PROTECT, &args); + TEST_ASSERT(ret == 0 || errno == ENODEV, "WPR protect (may fail without real HW)"); + } + + /* Test 6: Migration */ + { + struct gsp_migrate_args args = { + .vf_id = vf_id, + .phase = 1, // PREPARE + .subcmd = 3, + .src_vf = vf_id, + .dst_vf = 2, + .flags = 1, // LIVE + }; + + ret = ioctl(fd, GSP_IOCTL_MIGRATE, &args); + TEST_ASSERT(ret == 0 || errno == ENODEV, "Migration (may fail without real HW)"); + } + + /* Test 7: Destroy VF */ + { + ret = ioctl(fd, GSP_IOCTL_DESTROY_VF, &vf_id); + TEST_ASSERT(ret == 0, "Destroy VF"); + if (ret == 0) + printf(" VF %u destroyed successfully\n", vf_id); + } + + close(fd); + + printf("========================================\n"); + printf("Userspace Test Results: %d total, %d passed, %d failed\n", + test_count, pass_count, fail_count); + printf("========================================\n"); + + return (fail_count > 0) ? TEST_FAIL : TEST_PASS; +}
\ No newline at end of file |
