diff options
208 files changed, 26272 insertions, 7876 deletions
diff --git a/.editorconfig b/.editorconfig index 62b7bb2..f907b33 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,6 @@ # .editorconfig - How files in sbc-harness should be formatted # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later root = true @@ -30,18 +30,29 @@ _mode = markdown [*.9p{,.wip}] _mode = 9p -[{cmd/srv9p/static.h.gen,build-aux/embed-sources.h.gen}] +[{lib9p/tests/test_server/static.h.gen,build-aux/embed-sources.h.gen,build-aux/lint-{generic,unknown},lib9p/tests/test_compile.c.gen}] _mode = sh -[{build-aux/linux-errno.txt.gen,libusb/include/libusb/tusb_helpers.h.gen}] +[{build-aux/lint-h,build-aux/lint-bin,build-aux/get-dscname,build-aux/linux-errno.txt.gen,libusb/include/libusb/tusb_helpers.h.gen,lib9p/tests/runtest}] _mode = bash -[{lib9p/idl.gen,lib9p/include/lib9p/linux-errno.h.gen,build-aux/stack.c.gen,gdb-helpers/*.py}] +[{lib9p/proto.gen,lib9p/include/lib9p/linux-errno.h.gen,build-aux/stack.c.gen}] _mode = python3 indent_style = space indent_size = 4 -[{.editorconfig,.gitmodules}] +[*.py] +_mode = python3 +indent_style = space +indent_size = 4 + +[requirements.txt] +_mode = pip + +[**/Documentation/**.txt] +_mode = man-cat + +[{.editorconfig,.gitmodules,.pylintrc}] _mode = ini [.gitignore] @@ -7,6 +7,7 @@ *.log *.tmp .mypy_cache/ +__pycache__/ .gdb_history /build/ diff --git a/.gitmodules b/.gitmodules index d8d8265..17dec29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,8 +1,11 @@ # .gitmodules - Were to get some 3rd-party sources # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later [submodule "3rd-party/pico-sdk"] path = 3rd-party/pico-sdk - url = https://github.com/raspberrypi/pico-sdk + url = https://github.com/LukeShu/pico-sdk +[submodule "3rd-party/pico-fmt"] + path = 3rd-party/pico-fmt + url = https://github.com/LukeShu/pico-fmt diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..944973f --- /dev/null +++ b/.pylintrc @@ -0,0 +1,40 @@ +# .pylintrc - Configuration for Pylint +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +[MAIN] +analyse-fallback-blocks=yes +enable-all-extensions=yes +fail-on=all +#init-hook='sys.path.insert(0, os.path.normpath(os.path.join(__file__, "..")))' + +[MESSAGES CONTROL] + +disable=missing-module-docstring, + missing-class-docstring, + missing-function-docstring, + fixme, + line-too-long, + unused-argument, + too-few-public-methods, + invalid-name, + too-many-lines, + too-many-locals, + too-many-statements, + too-many-nested-blocks, + too-many-branches, + too-many-instance-attributes, + too-many-arguments, + too-many-positional-arguments, + too-many-return-statements, + global-statement, + import-outside-toplevel + +[REPORTS] +reports=no +score=no + +[VARIABLES] +allow-global-unused-variables=no +init-import=yes diff --git a/3rd-party/COPYING.newlib.txt b/3rd-party/COPYING.newlib.txt new file mode 100644 index 0000000..9a2680d --- /dev/null +++ b/3rd-party/COPYING.newlib.txt @@ -0,0 +1,1572 @@ +The newlib subdirectory is a collection of software from several sources. + +Each file may have its own copyright/license that is embedded in the source +file. Unless otherwise noted in the body of the source file(s), the following copyright +notices will apply to the contents of the newlib subdirectory: + +(1) Red Hat Incorporated + +Copyright (c) 1994-2009 Red Hat, Inc. All rights reserved. + +This copyrighted material is made available to anyone wishing to use, +modify, copy, or redistribute it subject to the terms and conditions +of the BSD License. This program is distributed in the hope that +it will be useful, but WITHOUT ANY WARRANTY expressed or implied, +including the implied warranties of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. A copy of this license is available at +http://www.opensource.org/licenses. Any Red Hat trademarks that are +incorporated in the source code or documentation are not subject to +the BSD License and may only be used or replicated with the express +permission of Red Hat, Inc. + +(2) University of California, Berkeley + +Copyright (c) 1981-2000 The Regents of the University of California. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. + +(3) David M. Gay (AT&T 1991, Lucent 1998) + +The author of this software is David M. Gay. + +Copyright (c) 1991 by AT&T. + +Permission to use, copy, modify, and distribute this software for any +purpose without fee is hereby granted, provided that this entire notice +is included in all copies of any software which is or includes a copy +or modification of this software and in all copies of the supporting +documentation for such software. + +THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR AT&T MAKES ANY +REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + +------------------------------------------------------------------- + +The author of this software is David M. Gay. + +Copyright (C) 1998-2001 by Lucent Technologies +All Rights Reserved + +Permission to use, copy, modify, and distribute this software and +its documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appear in all +copies and that both that the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name of Lucent or any of its entities +not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER +IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + +(4) Advanced Micro Devices + +Copyright 1989, 1990 Advanced Micro Devices, Inc. + +This software is the property of Advanced Micro Devices, Inc (AMD) which +specifically grants the user the right to modify, use and distribute this +software provided this notice is not removed or altered. All other rights +are reserved by AMD. + +AMD MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS +SOFTWARE. IN NO EVENT SHALL AMD BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL +DAMAGES IN CONNECTION WITH OR ARISING FROM THE FURNISHING, PERFORMANCE, OR +USE OF THIS SOFTWARE. + +So that all may benefit from your experience, please report any problems +or suggestions about this software to the 29K Technical Support Center at +800-29-29-AMD (800-292-9263) in the USA, or 0800-89-1131 in the UK, or +0031-11-1129 in Japan, toll free. The direct dial number is 512-462-4118. + +Advanced Micro Devices, Inc. +29K Support Products +Mail Stop 573 +5900 E. Ben White Blvd. +Austin, TX 78741 +800-292-9263 + +(5) + +(6) + +(7) Sun Microsystems + +Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + +Developed at SunPro, a Sun Microsystems, Inc. business. +Permission to use, copy, modify, and distribute this +software is freely granted, provided that this notice is preserved. + +(8) Hewlett Packard + +(c) Copyright 1986 HEWLETT-PACKARD COMPANY + +To anyone who acknowledges that this file is provided "AS IS" +without any express or implied warranty: + permission to use, copy, modify, and distribute this file +for any purpose is hereby granted without fee, provided that +the above copyright notice and this notice appears in all +copies, and that the name of Hewlett-Packard Company not be +used in advertising or publicity pertaining to distribution +of the software without specific, written prior permission. +Hewlett-Packard Company makes no representations about the +suitability of this software for any purpose. + +(9) Hans-Peter Nilsson + +Copyright (C) 2001 Hans-Peter Nilsson + +Permission to use, copy, modify, and distribute this software is +freely granted, provided that the above copyright notice, this notice +and the following disclaimer are preserved with no changes. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. + +(10) Stephane Carrez (m68hc11-elf/m68hc12-elf targets only) + +Copyright (C) 1999, 2000, 2001, 2002 Stephane Carrez (stcarrez@nerim.fr) + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +(11) Christopher G. Demetriou + +Copyright (c) 2001 Christopher G. Demetriou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(12) SuperH, Inc. + +Copyright 2002 SuperH, Inc. All rights reserved + +This software is the property of SuperH, Inc (SuperH) which specifically +grants the user the right to modify, use and distribute this software +provided this notice is not removed or altered. All other rights are +reserved by SuperH. + +SUPERH MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO +THIS SOFTWARE. IN NO EVENT SHALL SUPERH BE LIABLE FOR INDIRECT, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES IN CONNECTION WITH OR ARISING FROM +THE FURNISHING, PERFORMANCE, OR USE OF THIS SOFTWARE. + +So that all may benefit from your experience, please report any problems +or suggestions about this software to the SuperH Support Center via +e-mail at softwaresupport@superh.com . + +SuperH, Inc. +405 River Oaks Parkway +San Jose +CA 95134 +USA + +(13) Royal Institute of Technology + +Copyright (c) 1999 Kungliga Tekniska Högskolan +(Royal Institute of Technology, Stockholm, Sweden). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of KTH nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(14) Alexey Zelkin + +Copyright (c) 2000, 2001 Alexey Zelkin <phantom@FreeBSD.org> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(15) Andrey A. Chernov + +Copyright (C) 1997 by Andrey A. Chernov, Moscow, Russia. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(16) FreeBSD + +Copyright (c) 1997-2002 FreeBSD Project. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(17) S. L. Moshier + +Author: S. L. Moshier. + +Copyright (c) 1984,2000 S.L. Moshier + +Permission to use, copy, modify, and distribute this software for any +purpose without fee is hereby granted, provided that this entire notice +is included in all copies of any software which is or includes a copy +or modification of this software and in all copies of the supporting +documentation for such software. + +THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION +OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS +SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + +(18) Citrus Project + +Copyright (c)1999 Citrus Project, +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(19) Todd C. Miller + +Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(20) DJ Delorie (i386 / arm) +Copyright (C) 1991 DJ Delorie +All rights reserved. + +Redistribution, modification, and use in source and binary forms is permitted +provided that the above copyright notice and following paragraph are +duplicated in all such forms. + +This file is distributed WITHOUT ANY WARRANTY; without even the implied +warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +(21) Free Software Foundation LGPL License (*-linux* targets only) + + Copyright (C) 1990-1999, 2000, 2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Mark Kettenis <kettenis@phys.uva.nl>, 1997. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + +(22) Xavier Leroy LGPL License (i[3456]86-*-linux* targets only) + +Copyright (C) 1996 Xavier Leroy (Xavier.Leroy@inria.fr) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library 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 Library General Public License for more details. + +(23) Intel (i960) + +Copyright (c) 1993 Intel Corporation + +Intel hereby grants you permission to copy, modify, and distribute this +software and its documentation. Intel grants this permission provided +that the above copyright notice appears in all copies and that both the +copyright notice and this permission notice appear in supporting +documentation. In addition, Intel grants this permission provided that +you prominently mark as "not part of the original" any modifications +made to this software or documentation, and that the name of Intel +Corporation not be used in advertising or publicity pertaining to +distribution of the software or the documentation without specific, +written prior permission. + +Intel Corporation provides this AS IS, WITHOUT ANY WARRANTY, EXPRESS OR +IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY +OR FITNESS FOR A PARTICULAR PURPOSE. Intel makes no guarantee or +representations regarding the use of, or the results of the use of, +the software and documentation in terms of correctness, accuracy, +reliability, currentness, or otherwise; and you rely on the software, +documentation and results solely at your own risk. + +IN NO EVENT SHALL INTEL BE LIABLE FOR ANY LOSS OF USE, LOSS OF BUSINESS, +LOSS OF PROFITS, INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES +OF ANY KIND. IN NO EVENT SHALL INTEL'S TOTAL LIABILITY EXCEED THE SUM +PAID TO INTEL FOR THE PRODUCT LICENSED HEREUNDER. + +(24) Hewlett-Packard (hppa targets only) + +(c) Copyright 1986 HEWLETT-PACKARD COMPANY + +To anyone who acknowledges that this file is provided "AS IS" +without any express or implied warranty: + permission to use, copy, modify, and distribute this file +for any purpose is hereby granted without fee, provided that +the above copyright notice and this notice appears in all +copies, and that the name of Hewlett-Packard Company not be +used in advertising or publicity pertaining to distribution +of the software without specific, written prior permission. +Hewlett-Packard Company makes no representations about the +suitability of this software for any purpose. + +(25) Henry Spencer (only *-linux targets) + +Copyright 1992, 1993, 1994 Henry Spencer. All rights reserved. +This software is not subject to any license of the American Telephone +and Telegraph Company or of the Regents of the University of California. + +Permission is granted to anyone to use this software for any purpose on +any computer system, and to alter it and redistribute it, subject +to the following restrictions: + +1. The author is not responsible for the consequences of use of this + software, no matter how awful, even if they arise from flaws in it. + +2. The origin of this software must not be misrepresented, either by + explicit claim or by omission. Since few users ever read sources, + credits must appear in the documentation. + +3. Altered versions must be plainly marked as such, and must not be + misrepresented as being the original software. Since few users + ever read sources, credits must appear in the documentation. + +4. This notice may not be removed or altered. + +(26) Mike Barcroft + +Copyright (c) 2001 Mike Barcroft <mike@FreeBSD.org> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(27) Konstantin Chuguev (--enable-newlib-iconv) + +Copyright (c) 1999, 2000 + Konstantin Chuguev. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + + iconv (Charset Conversion Library) v2.0 + +(28) Artem Bityuckiy (--enable-newlib-iconv) + +Copyright (c) 2003, Artem B. Bityuckiy, SoftMine Corporation. +Rights transferred to Franklin Electronic Publishers. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(29) IBM, Sony, Toshiba (only spu-* targets) + + (C) Copyright 2001,2006, + International Business Machines Corporation, + Sony Computer Entertainment, Incorporated, + Toshiba Corporation, + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the names of the copyright holders nor the names of their + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +(30) - Alex Tatmanjants (targets using libc/posix) + + Copyright (c) 1995 Alex Tatmanjants <alex@elvisti.kiev.ua> + at Electronni Visti IA, Kiev, Ukraine. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +(31) - M. Warner Losh (targets using libc/posix) + + Copyright (c) 1998, M. Warner Losh <imp@freebsd.org> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +(32) - Andrey A. Chernov (targets using libc/posix) + + Copyright (C) 1996 by Andrey A. Chernov, Moscow, Russia. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +(33) - Daniel Eischen (targets using libc/posix) + + Copyright (c) 2001 Daniel Eischen <deischen@FreeBSD.org>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + +(34) - Jon Beniston (only lm32-* targets) + + Contributed by Jon Beniston <jon@beniston.com> + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + +(35) - Arm Ltd + + SPDX-License-Identifier: BSD-3-Clause + + Copyright (c) 2009-2022 Arm Ltd + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the company may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY ARM LTD ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL ARM LTD BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(36) - Xilinx, Inc. (microblaze-* and powerpc-* targets) + +Copyright (c) 2004, 2009 Xilinx, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of Xilinx nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +(37) Texas Instruments Incorporated (tic6x-*, *-tirtos targets) + +Copyright (c) 1996-2010,2014 Texas Instruments Incorporated +http://www.ti.com/ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + Neither the name of Texas Instruments Incorporated nor the names + of its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(38) National Semiconductor (cr16-* and crx-* targets) + +Copyright (c) 2004 National Semiconductor Corporation + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +(39) - Adapteva, Inc. (epiphany-* targets) + +Copyright (c) 2011, Adapteva, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Adapteva nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(40) - Altera Corportion (nios2-* targets) + +Copyright (c) 2003 Altera Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + o Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + o Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + o Neither the name of Altera Corporation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ALTERA CORPORATION, THE COPYRIGHT HOLDER, +AND ITS CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(41) Ed Schouten - Free BSD + +Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(42) - Rolls-Royce Controls and Data Services Limited (visium-* targets) + +Copyright (c) 2015 Rolls-Royce Controls and Data Services Limited. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Rolls-Royce Controls and Data Services Limited nor + the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(43) - FTDI (ft32-* targets) + +Copyright (C) 2014 FTDI (support@ftdichip.com) + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +(44) - Synopsys Inc (arc*-* targets) + +Copyright (c) 2015, Synopsys, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1) Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2) Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3) Neither the name of the Synopsys, Inc., nor the names of its contributors +may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +(45) embedded brains - RTEMS targets + +Copyright (c) 2017 embedded brains GmbH +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(46) John Baldwin - RTEMS targets + +Copyright (c) 2015 John Baldwin <jhb@FreeBSD.org>. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(47) Jeffrey Roberson - RTEMS targets + +Copyright (c) 2008, Jeffrey Roberson <jeff@freebsd.org> +All rights reserved. + +Copyright (c) 2008 Nokia Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice unmodified, this list of conditions, and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(48) - SiFive Inc. (riscv-* targets) + +Copyright (c) 2017 SiFive Inc. All rights reserved. + +This copyrighted material is made available to anyone wishing to use, +modify, copy, or redistribute it subject to the terms and conditions +of the FreeBSD License. This program is distributed in the hope that +it will be useful, but WITHOUT ANY WARRANTY expressed or implied, +including the implied warranties of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. A copy of this license is available at +http://www.opensource.org/licenses. + +(49) Michael R. Neilly (riscv-* targets) + +(c) Copyright 2017 Michael R. Neilly +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +* Neither the names of the copyright holders nor the names of their +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +(50) Mentor Graphics (amdgcn-* targets) + +Copyright (c) 2014-2017 Mentor Graphics. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +(51) BSD-2-Clause-FreeBSD (pru-* targets) + +SPDX-License-Identifier: BSD-2-Clause-FreeBSD + +Copyright (c) 2018-2019 Dimitar Dimitrov <dimitar@dinux.eu> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(52) Andrew Turner (arm-* targets) + +Copyright (c) 2013 Andrew Turner <andrew@FreeBSD.ORG> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(53) BSD-2-Clause-FreeBSD David Schultz (arm-* targets) + +SPDX-License-Identifier: BSD-2-Clause-FreeBSD + +Copyright (c) 2004-2011 David Schultz <das@FreeBSD.ORG> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(54) - C-SKY Microsystems (csky-* targets) + +Copyright (c) 2020 C-SKY Microsystems All rights reserved. + +This copyrighted material is made available to anyone wishing to use, +modify, copy, or redistribute it subject to the terms and conditions +of the FreeBSD License. This program is distributed in the hope that +it will be useful, but WITHOUT ANY WARRANTY expressed or implied, +including the implied warranties of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. A copy of this license is available at +http://www.opensource.org/licenses. + +(55) BSD-3-Clause-FreeBSD Peter Wemm (rtems targets) + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (c) 1997 Peter Wemm <peter@freebsd.org> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +(56) MIT OR Apache-2.0 WITH LLVM-exception (newlib/libc/machine/aarch64) + +SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception + + +MIT License +----------- + +Copyright (c) 1999-2023, Arm Limited. + +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. + + +Apache-2.0 WITH LLVM-exception +------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +(57) Steven G. Kargl - libm ld128 functions + +/*- + * Copyright (c) 2017-2023 Steven G. Kargl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + diff --git a/3rd-party/pico-fmt b/3rd-party/pico-fmt new file mode 160000 +Subproject beaecdcba7fdf0d584d245a9d0ad6be6bdc94a1 diff --git a/3rd-party/pico-sdk b/3rd-party/pico-sdk -Subproject a029d9c1cc930ca972e7b52616e773e4d420a07 +Subproject c8a16c00453e4db4b771d7f1281391057c7477d diff --git a/CMakeLists.txt b/CMakeLists.txt index eef2617..22756c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,12 @@ # CMakeLists.txt - Main per-platform build script for sbc-harness project # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later cmake_minimum_required(VERSION 3.30) if (NOT PICO_PLATFORM) - message(FATAL_ERROR "PICO_PLATFORM must be specified; use the GNUmakefile to set this") + message(FATAL_ERROR "PICO_PLATFORM must be specified; use the GNUmakefile to set this") endif() set(PICO_SDK_PATH "${CMAKE_SOURCE_DIR}/3rd-party/pico-sdk") @@ -14,29 +14,45 @@ include("${PICO_SDK_PATH}/external/pico_sdk_import.cmake") project(sbc_harness) +add_subdirectory(3rd-party/pico-fmt/pico_fmt) +add_subdirectory(3rd-party/pico-fmt/pico_printf) pico_sdk_init() +if ((PICO_PLATFORM STREQUAL "host") AND (NOT PICO_NO_GC_SECTIONS)) + # On non-host builds, this is done by `pico_standard_link`. + add_compile_options(-ffunction-sections -fdata-sections) + add_link_options("LINKER:--gc-sections") +endif() + add_compile_options(-Wall -Wextra -Wswitch-enum -Werror) +string(TOUPPER "${CMAKE_BUILD_TYPE}" _upper_cmake_build_type) +string(REPLACE " " ";" _build_type_flags "${CMAKE_C_FLAGS_${_upper_cmake_build_type}}") +if ("-DNDEBUG" IN_LIST _build_type_flags) + add_compile_options(-Wno-unused-variable -Wno-unused-parameter -Wno-unused-but-set-variable) + target_compile_definitions(pico_printf INTERFACE PICO_PRINTF_ALWAYS_INCLUDED=1) +endif() + function(_suppress_tinyusb_warnings) - __suppress_tinyusb_warnings() + __suppress_tinyusb_warnings() set_source_files_properties( ${PICO_TINYUSB_PATH}/src/device/usbd.c PROPERTIES COMPILE_OPTIONS "-Wno-switch-enum") endfunction() -function(target_embed_sources arg_target arg_hdrname) +function(target_embed_sources arg_compile_target arg_link_target arg_hdrname) set(embed_objs) foreach(embed_src IN LISTS ARGN) add_custom_command( - OUTPUT "${embed_src}.obj" + OUTPUT "${embed_src}.o" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMAND mkdir -p -- "$<PATH:GET_PARENT_PATH,${CMAKE_CURRENT_BINARY_DIR}/${embed_src}>" && - ${CMAKE_LINKER} -r -b binary -o "${CMAKE_CURRENT_BINARY_DIR}/${embed_src}.obj" "${embed_src}" + ${CMAKE_LINKER} -r -b binary -o "${CMAKE_CURRENT_BINARY_DIR}/${embed_src}.o" "${embed_src}" && + ${CMAKE_OBJCOPY} --rename-section .data=.rodata,alloc,load,readonly,data,contents "${CMAKE_CURRENT_BINARY_DIR}/${embed_src}.o" "${CMAKE_CURRENT_BINARY_DIR}/${embed_src}.o" DEPENDS "${embed_src}" ) - list(APPEND embed_objs "${embed_src}.obj") + list(APPEND embed_objs "${embed_src}.o") endforeach() set_source_files_properties("${embed_objs}" PROPERTIES EXTERNAL_OBJECT true @@ -53,7 +69,8 @@ function(target_embed_sources arg_target arg_hdrname) DEPENDS "${embed_objs}" "${CMAKE_SOURCE_DIR}/build-aux/embed-sources.h.gen" ) - target_sources("${arg_target}" PRIVATE "${embed_objs}" "${arg_hdrname}") + target_sources("${arg_compile_target}" PRIVATE "${arg_hdrname}") + target_sources("${arg_link_target}" PRIVATE "${embed_objs}") endfunction() function(add_stack_analysis arg_outfile arg_objlib_target) @@ -63,9 +80,18 @@ function(add_stack_analysis arg_outfile arg_objlib_target) ) add_custom_command( OUTPUT "${arg_outfile}" - COMMAND "${CMAKE_SOURCE_DIR}/build-aux/stack.c.gen" "$<TARGET_OBJECTS:${arg_objlib_target}>" >"${arg_outfile}" + COMMAND "${CMAKE_SOURCE_DIR}/build-aux/stack.c.gen" "${PICO_PLATFORM}" "${CMAKE_SOURCE_DIR}" "$<TARGET_OBJECTS:${arg_objlib_target}>" >"${arg_outfile}" COMMAND_EXPAND_LISTS - DEPENDS "$<TARGET_OBJECTS:${arg_objlib_target}>" "${CMAKE_SOURCE_DIR}/build-aux/stack.c.gen" + DEPENDS "$<TARGET_OBJECTS:${arg_objlib_target}>" + "${CMAKE_SOURCE_DIR}/build-aux/stack.c.gen" + "${CMAKE_SOURCE_DIR}/build-aux/measurestack/__init__.py" + "${CMAKE_SOURCE_DIR}/build-aux/measurestack/analyze.py" + "${CMAKE_SOURCE_DIR}/build-aux/measurestack/app_main.py" + "${CMAKE_SOURCE_DIR}/build-aux/measurestack/app_output.py" + "${CMAKE_SOURCE_DIR}/build-aux/measurestack/app_plugins.py" + "${CMAKE_SOURCE_DIR}/build-aux/measurestack/test_analyze.py" + "${CMAKE_SOURCE_DIR}/build-aux/measurestack/util.py" + "${CMAKE_SOURCE_DIR}/build-aux/measurestack/vcg.py" COMMENT "Calculating ${arg_objlib_target} required stack sizes" ) endfunction() @@ -83,6 +109,7 @@ function(add_lib_test arg_libname arg_testname) if (ENABLE_TESTS) add_executable("${arg_testname}" "tests/${arg_testname}.c") target_link_libraries("${arg_testname}" "${arg_libname}") + target_include_directories("${arg_testname}" PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests) add_test( NAME "${arg_libname}/${arg_testname}" COMMAND valgrind --error-exitcode=2 "./${arg_testname}" @@ -91,13 +118,15 @@ function(add_lib_test arg_libname arg_testname) endfunction() add_subdirectory(libmisc) +add_subdirectory(libobj) +add_subdirectory(libfmt) add_subdirectory(libcr) add_subdirectory(libcr_ipc) add_subdirectory(libhw_generic) -add_subdirectory(libhw) +add_subdirectory(libhw_cr) add_subdirectory(libdhcp) add_subdirectory(libusb) add_subdirectory(lib9p) +add_subdirectory(lib9p_util) add_subdirectory(cmd/sbc_harness) -add_subdirectory(cmd/srv9p) diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + 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 +them 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<https://www.gnu.org/licenses/>. diff --git a/GNUmakefile b/GNUmakefile index 518f8d6..5a2ff93 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,6 +1,6 @@ # GNUmakefile - Main build script for sbc-harness project # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later # Project configuration ######################################################## @@ -25,6 +25,14 @@ endif generate/files = +generate/files += COPYING.txt +COPYING.txt: + wget --no-use-server-timestamps -O $@ https://www.gnu.org/licenses/agpl-3.0.txt + +generate/files += 3rd-party/COPYING.newlib.txt +3rd-party/COPYING.newlib.txt: /usr/share/licenses/arm-none-eabi-newlib/COPYING.NEWLIB + cp $< $@ + generate/files += 3rd-party/linux-errno.txt 3rd-party/linux-errno.txt: build-aux/linux-errno.txt.gen $< $(linux.git) $@ @@ -34,14 +42,19 @@ lib9p/include/lib9p/linux-errno.h: %: %.gen 3rd-party/linux-errno.txt $^ >$@ generate/files += lib9p/9p.generated.c lib9p/include/lib9p/9p.generated.h -lib9p/9p.generated.c lib9p/include/lib9p/9p.generated.h &: lib9p/idl.gen lib9p/idl/*.9p - $^ +lib9p/9p.generated.c lib9p/include/lib9p/9p.generated.h &: lib9p/proto.gen lib9p/idl/__init__.py lib9p/protogen lib9p/protogen/*.py lib9p/idl lib9p/idl/*.9p + $< $(filter %.9p,$^) + +generate/files += lib9p/tests/test_compile.c +lib9p/tests/test_compile.c: %: %.gen lib9p/include/lib9p/9p.generated.h + $^ $@ generate/files += libusb/include/libusb/tusb_helpers.h 3rd-party/MS-LCID.pdf 3rd-party/MS-LCID.txt libusb/include/libusb/tusb_helpers.h 3rd-party/MS-LCID.pdf 3rd-party/MS-LCID.txt &: libusb/include/libusb/tusb_helpers.h.gen $^ generate/files += build-aux/sources.mk +ifeq ($(INNER),) build-aux/sources.mk: $(if $(wildcard .git),FORCE) git ls-files | grep -vFx $(foreach f,$(generate/files),-e $f) \ | sed 's,^,$(CURDIR)/,' | xargs editorconfig \ @@ -50,6 +63,8 @@ build-aux/sources.mk: $(if $(wildcard .git),FORCE) | sort \ >$@.tmp if ! cmp -s $@.tmp $@; then mv $@.tmp $@; fi + @echo '################################################################################' +endif generate: $(generate/files) .PHONY: generate @@ -60,101 +75,81 @@ generate-clean: # `build` and `check` ########################################################## -platforms := $(shell sed -nE 's/if *\(PICO_PLATFORM STREQUAL "(.*)"\)/\1/p' cmd/*/CMakeLists.txt) +platforms := rp2040 host # $(shell sed -nE 's/if *\(PICO_PLATFORM STREQUAL "(.*)"\)/\1/p' cmd/*/CMakeLists.txt) +build_types = Debug Release RelWithDebInfo MinSizeRel + +export CTEST_PARALLEL_LEVEL = 0 +export CTEST_OUTPUT_ON_FAILURE = 1 -build: $(foreach p,$(platforms),build/$p/build) +build: $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/build)) .PHONY: build -$(foreach p,$(platforms),build/$p/Makefile): build/%/Makefile: - mkdir -p $(@D) && cd $(@D) && cmake -DCMAKE_BUILD_TYPE=Debug -DPICO_PLATFORM=$* ../.. +$(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/Makefile)): build/%/Makefile: + mkdir -p $(@D) && cd $(@D) && cmake -DPICO_PLATFORM=$(firstword $(subst -, ,$*)) -DCMAKE_BUILD_TYPE=$(lastword $(subst -, ,$*)) ../.. -$(foreach p,$(platforms),build/$p/build): build/%/build: build/%/Makefile generate +$(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/build)): build/%/build: build/%/Makefile generate $(MAKE) -C $(<D) -.PHONY: $(foreach p,$(platforms),build/$p/build) +.PHONY: $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/build)) check: build - $(MAKE) -j1 -k $(foreach p,$(platforms),build/$p/check) + $(MAKE) -j1 -k INNER=t $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)) .PHONY: check -$(foreach p,$(platforms),build/$p/check): build/%/check: build/%/Makefile - CTEST_OUTPUT_ON_FAILURE=1 $(MAKE) -C $(<D) test -.PHONY: $(foreach p,$(platforms),build/$p/check) +$(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)): build/%/check: build/%/Makefile + $(MAKE) -C $(<D) test +.PHONY: $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)) # `lint` and `format` ########################################################## -include build-aux/sources.mk sources_all := $(foreach v,$(filter sources_%,$(.VARIABLES)),$($v)) -get_dscname = sed -n '1,3{ /^\#!/d; /^<!--$$/d; /-\*- .* -\*-/d; s,[/*\# ]*,,; s/ - .*//;p; q; }' - build-aux/venv: build-aux/requirements.txt python3 -m venv $@ $@/bin/pip install -r $< touch --no-create $@ -# `lint` ########### +# `lint` ############################### +# generic ########## lint: - $(MAKE) -k $(patsubst sources_%,lint/%,$(filter sources_%,$(.VARIABLES))) + $(MAKE) -k INNER=t $(patsubst sources_%,lint/%,$(filter sources_%,$(.VARIABLES))) +# Only lint binaries if the build scripts pass lint. + $(MAKE) INNER=t lint/bin +lint/bin: build build-aux/lint-bin + ./build-aux/lint-bin $(foreach t,$(build_types),build/rp2040-$t/cmd/sbc_harness/sbc_harness.elf) +lint/all: lint/%: build-aux/lint-generic build-aux/get-dscname + ./build-aux/lint-generic '%s\n' $(sources_$*) +lint/unknown: lint/%: build-aux/lint-unknown + ./build-aux/lint-unknown $(sources_$*) +.PHONY: lint lint/% +# specific ######### +lint/c: lint/%: build-aux/lint-h build-aux/get-dscname + ./build-aux/lint-h $(filter %.h,$(sources_$*)) lint/sh lint/bash: lint/%: shellcheck $(sources_$*) + shfmt --diff --case-indent --simplify $(sources_$*) lint/python3: lint/%: build-aux/venv ./build-aux/venv/bin/mypy --strict --scripts-are-modules $(sources_$*) ./build-aux/venv/bin/black --check $(sources_$*) ./build-aux/venv/bin/isort --check $(sources_$*) -lint/c: lint/%: - @for filename in $(filter %.h,$(sources_$*)); do \ - dscname=$$($(get_dscname) $$filename); \ - guard=$${dscname//'/'/'_'}; \ - guard=$${guard//'.'/'_'}; \ - guard="_$${guard^^}_"; \ - if ! { grep -Fxq "#ifndef $${guard}" "$$filename" && \ - grep -Fxq "#define $${guard}" "$$filename" && \ - grep -Fxq "#endif /* $${guard} */" "$$filename"; }; then \ - echo "$$filename does not have $${guard} guard"; r=1; \ - fi; \ - done; exit $$r -lint/make lint/cmake lint/gitignore lint/ini lint/9p lint/markdown: lint/%: - @: -lint/unknown: lint/%: - @printf "%s: cannot lint unknown file type\n" $(sources_$*) >&2 -lint/all: lint/%: - $(eval export sources_$*) - @find $$(printf '%s\n' $${sources_$*} | grep -vE '^cmd/[^/]+/static/') \ - -maxdepth 0 -type f | \ - { r=0; while read -r filename; do \ - if ! grep -q 'Copyright (C) 2024 Luke T. Shumaker' $$filename; then \ - echo "$$filename is missing a copyright statement"; r=1; \ - fi; \ - if ! grep -q ' SPDX-License-Identifier[:] ' $$filename; then \ - echo "$$filename is missing an SPDX-License-Identifier"; r=1; \ - fi; \ - dscname_act=$$($(get_dscname) $$filename); \ - dscname_exp=$$(echo "$$filename" | sed \ - -e 's,.*/config/,,' \ - -e 's,.*include/,,' \ - -e 's,^lib9p/idl/,,' \ - -e 's/\.wip$$//'); \ - if [ "$$dscname_act" != "$$dscname_exp" ] && [ "cmd/$$dscname_act" != "$$dscname_exp" ]; then \ - echo "$$filename self-identifies as $$dscname_act (expected $$dscname_exp)"; r=1; \ - fi; \ - if grep -n --color=auto "$$(printf '\\S\t')" $$filename; then \ - echo "$$filename uses tabs for alignment"; r=1; \ - fi; \ - done; exit $$r; } -.PHONY: lint lint/% - -# `format` ######### + ./build-aux/venv/bin/pylint $(sources_$*) + ! grep -nh 'SPECIAL$$' -- lib9p/proto.gen lib9p/protogen/*.py + ./build-aux/venv/bin/pytest $(foreach f,$(sources_python3),$(if $(filter test_%.py,$(notdir $f)),$f)) +lint/make lint/cmake lint/gitignore lint/ini lint/9p lint/markdown lint/pip lint/man-cat: lint/%: + @: TODO: Write/adopt linters for these file types + +# `format` ############################# +# generic ########## format: - $(MAKE) -k $(patsubst sources_%,format/%,$(filter-out sources_all,$(filter sources_%,$(.VARIABLES)))) + $(MAKE) -k INNER=t $(patsubst sources_%,format/%,$(filter-out sources_all sources_unknown,$(filter sources_%,$(.VARIABLES)))) +.PHONY: format format/% +# specific ######### +format/c: format/%: + @: TODO: Adopt a C code-formatter +format/sh format/bash: format/%: + shfmt --write --case-indent --simplify $(sources_$*) format/python3: format/%: ./build-aux/venv ./build-aux/venv/bin/black $(sources_$*) ./build-aux/venv/bin/isort $(sources_$*) -format/sh format/bash: format/% - @: -format/c: format/%: - @: TODO -format/make format/cmake format/gitignore format/ini format/9p format/markdown: format/%: - @: -format/unknown: format/%: - @: -.PHONY: format format/% +format/make format/cmake format/gitignore format/ini format/9p format/markdown format/pip format/man-cat: format/%: + @: TODO: Write/adopt formatters for these file types @@ -1,16 +1,193 @@ <!-- HACKING.md - Description of sbc-harness sources - Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> SPDX-License-Identifier: AGPL-3.0-or-later --> -Source layout +# Source Layout - - cmd/sbc_harness - - cmd/srv9p - - libcr - A simple coroutine (cooperative multitasking) library - - libcr_ipc - IPC primatives for coroutines built on top of libcr - - libnet - TODO - - lib9p - A simple 9P protocol server library - - libusb - TODO +Our own "flavor" of C: GNU C plus `-fplan9-extensions`, making use of: + + - `libobj/`: For Go-like object-oriented programming + - `libmisc/`: Low-level C programming utilities; sort of an augmented + "libc" + +Our own tiny cooperative-multitasking OS: + + - `libcr/`: The kernel (a simple coroutine library) + - `libcr_ipc/`: IPC primatives (semaphores, mutexes, channels, ...) + for libcr + - `libhw_generic/`: The hardware abstraction layer + - `libhw_cr/`: The hardware drivers + +Libraries for generic parts of what the harness is doing: + + - `lib9p/`: a 9P network filesystem server library + - `lib9p_util/`: Utilities for use with lib9p + - `libdhcp/`: A DHCP client + - `libusb/`: Wrapper and utilities for TinyUSB + + - `cmd/sbc_harness/`: The main firmware image + - `3rd-party/`: Sources from third parties; some definitions of + things (USB language codes, Linux kernel errnos), and the Pico-SDK + (for the RP2040 CPU, and its included TinyUSB). Does not include + newlib, which you need to have installed separately. + - `build-aux/`: Build-system files + +# Debugging + +There are 2 "debug" connectors on the harness: + +## UART (Universal Asynchronous Receiver/Transmitter) logging + +Baud rate: 115200 + +Pinout: +| RPi Pico | RP2040 | Use | +|----------|--------|----------------------------------------| +| pin1 | gpio0 | TX (so connect it to your FTDI's RX) | +| pin2 | gpio1 | RX (so connect it to your FTDI's TX) | +| pin3 | | gnd (so connect it to your FTDI's GND) | + +Example use: + +> I use `picocom` (because it's small and Parabola GNU/Linux-libre +> has it), but other programs such as GNU Screen or minicom work +> as well. +> +> I sometimes use a Rasberry Pi Debug Probe (see below) as my +> UART-to-USB adapter because it's convenient because I'm using it +> anyway for ARM SWD. I sometimes use a FTDI-brand adapter because it +> seems to be more reliable; the Debug Probe can sometimes get +> messed-up byte boundaries. +> +> Depending on your UART adapter, on GNU/Linux the device may be +> named either `/dev/ttyUSB{n}` or `/dev/ttyACM{n}`. +> +> - `picocom --baud=115200 /dev/ttyUSB0` +> - `picocom --baud=115200 /dev/ttyACM0` + +## ARM SWD (Serial Wire Debug) + +Pinout: Separate from the main 40-pin layout of the RPi Pico, there +are 3 separate ARM SWD pins; they may have a [Raspberry Pi 3-pin Debug +Connector][#raspberry-pi-3-pin-debug-connector] socket soldered to +them, or may just be regular pin headers. +| Pin # | ARM SW-DP (Single-Wire Debug-Port) | +|-------|------------------------------------| +| 1 | SC/SWCLK (serial clock) | +| 2 | GND | +| 3 | SD/SWDIO (bidi serial data) | +| N/C | SWO (optional: serial wire output) | + +The RP2040 CPU speaks the ADIv5 SWD protocol on this port; you need a +separate USB device to translate this to the ARM CMSIS-DAP protocol (a +USB-based protocol), and then a piece of software on your PC to talk +CMSIS-DAP; likely [OpenOCD](https://openocd.org/) to bridge to the GNU +Debugger (GDB): + +``` +┌─[Host PC]─────────────────────────┐ +│ ┌─────┐ │ +│ │ GDB │ │ +│ └─────┘ │ +│ ⇑ │ +│ ║ GDB Remote Serial Protocol on localhost:3333 +│ ⇓ │ +│ ┌─────────┐ │ +│ │ OpenOCD │ │ +│ └─────────┘ │ +│ ⇑ │ +└─────────────────║─────────────────┘ + USB-A + ║ ARM CMSIS-DAP protocol (a USB-based protocol) + (micro)USB-B + ┌─[Debug Probe]───────┐ + │ ⇓ │ + │ ┌────────────────┐ │ + │ │ ARM's │ │ + │ │ reference │ │ + │ │ CMSIS-DAP │ │ + │ │ implementation │ │ + │ └────────────────┘ │ + │ ⇑ │ + └──────────║──────────┘ + SW-DP (3-pin JST-SH) + ║ ADIv5 SWD protocol + SW-DP (3 .1" headers) +┌─[SBC-Harness]───║─────────────────┐ +│ ╔═══════════════╝ │ +│ ║ ┌────────────────────────────┐ │ +│ ║ │ Cortex-M0+ (core 0) │ │ +│ ║ │ ┌────────────────────────┐ │ │ +│ ╠═══⇒│ CoreSight ADIv5 module │ │ │ +│ ║ │ └────────────────────────┘ │ │ +│ ║ └────────────────────────────┘ │ +│ ║ ┌────────────────────────────┐ │ +│ ║ │ Cortex-M0+ (core 1) │ │ +│ ║ │ ┌────────────────────────┐ │ │ +│ ╚═══⇒│ CoreSight ADIv5 module │ │ │ +│ │ └────────────────────────┘ │ │ +│ └────────────────────────────┘ │ +└───────────────────────────────────┘ +``` + +Example use: + +> For the device that translates from ADIv5 SWD to CMSIS-DAP, I use +> the [Raspberry Pi Debug +> Probe](https://www.raspberrypi.com/products/debug-probe/) because it +> is cheap, runs Free Software, and is also a UART-to-USB adapter, so +> I can use it for both ports with less clutter on my desk ([source +> code](https://github.com/raspberrypi/debugprobe), +> [tutorial](https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html). +> Another freedom-friendly option is the +> https://oshwlab.com/wagiminator/samd11c-swd-programmer which uses +> https://github.com/ataradov/free-dap instead of ARM's reference +> CMSIS implementation; but I have not tried it. +> +> I use OpenOCD for talking CMSIS-DAP from my laptop, but pyOCD is +> another option. +> +> In one terminal: +> - `openocd -f interface/cmsis-dap.cfg -c 'set USE_CORE 0' -f target/rp2040.cfg -c "adapter speed 5000"` +> (Explanation of `USE_CORE`: https://github.com/raspberrypi/debugprobe/issues/45) +> In another terminal: +> - `arm-none-eabi-gdb ./build/rp2040-Debug/cmd/sbc_harness/sbc_harness.elf` +> ``` +> target extended-remote localhost:3333 +> monitor reset init +> continue +> ``` +> See https://openocd.org/doc/html/General-Commands.html for +> documentation on the commands that you can send from GDB to +> OpenOCD with `monitor`. + +References: +- https://developer.arm.com/documentation/101761/1-0/Debug-and-trace-interface/Serial-Wire-Debug-signals +- https://developer.arm.com/documentation/ihi0031/a/The-Serial-Wire-Debug-Port--SW-DP-/Introduction-to-the-ARM-Serial-Wire-Debug--SWD--protocol +- https://arm-software.github.io/CMSIS-DAP/latest/index.html +- ADIv5: https://developer.arm.com/documentation/ihi0031/g/ +- ADIv6: https://developer.arm.com/documentation/ihi0074/e/ + +# Other references + +## Raspberry Pi 3-pin Debug Connector + +- https://datasheets.raspberrypi.com/debug/debug-connector-specification.pdf + +| Pin # | UART | ARM SW-DP | cJTAG (IEEE 1149.7) | +|-------|----------------------|------------------------------------|----------------------------------| +| 1 | RX (target←debugger) | SC/SWCLK (serial clock) | TMSC - Test Clock Control | +| 2 | GND | GND | GND | +| 3 | TX (target→debugger) | SD/SWDIO (bidi serial data) | TCKC - Test Master/Slave Control | +| | | SWO (optional: serial wire output) | | + +Note that the RP2040 chip has a pure SW-DP (just speaks SWD), not a +JTAG-DP (just speaks JTAG) nor a SWJ-DP (can speak either SWD or +JTAG). + +The Raspberry Pi Debug Probe has 2 debug connectors on it; one to +connect to the UART (labeled "U"), one to connect to the SWD (labeled +"D"). @@ -1,10 +1,20 @@ <!-- PLAN.md - Misc planning notes - Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> SPDX-License-Identifier: AGPL-3.0-or-later --> +- [ ] syslog on 9p +- [ ] better 9p-shutdown +- [ ] PIO-based USB +- [ ] wire the USB keyboard to 9P +- [ ] wire uart to 9p + +- SDcard to 9p +- PicoDVI +- solder up the bus switch + - with hardware I have: 1. [X] type "hello world" as a USB keyboard 2. [ ] get networking up (ping) @@ -19,5 +29,3 @@ 1. [ ] PicoDVI hello-world 2. [ ] "PiciDVI" to network 3. [ ] reverse the flow of PicoDVI - -https://hackaday.com/2022/08/26/bit-banged-ethernet-on-the-raspberry-pi-pico/ @@ -1,15 +1,15 @@ <!-- README.md - Description of sbc-harness - Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> SPDX-License-Identifier: AGPL-3.0-or-later --> # Building -Building requires cmake, make, an "arm-none-eabi" toolchain (including -newlib), and picotool (including the .cmake files; -eg. /usr/lib/cmake/picotool/*.cmake). +Building requires CMake, GNU Make, an "arm-none-eabi" toolchain +(including newlib), and picotool (including the `.cmake` files; +e.g. `/usr/lib/cmake/picotool/*.cmake`). At the time of this writing, on Parabola GNU/Linux-libre that means: @@ -18,133 +18,61 @@ At the time of this writing, on Parabola GNU/Linux-libre that means: - arm-none-eabi-binutils 2.42-1 - arm-none-eabi-gcc 14.1.0-1 - arm-none-eabi-newlib 4.4.0.20231231-1 - - picotool 2.0.0-2 - -```bash -mkdir build -cd build -cmake -DCMAKE_BUILD_TYPE=Debug .. -make -``` -# Debugging - -UART: -- pin1: gpio0: TX (so connect it to your FTDI's RX) -- pin2: gpio1: RX (so connect it to your FTDI's TX) -- pin3: gnd (so connect it to your FTDI's GND) -- `picocom --baud=115200 /dev/ttyUSB0` -- `picocom --baud=115200 /dev/ttyACM0` - -- `openocd -f interface/cmsis-dap.cfg -c set 'USE_CORE 0' -f target/rp2040.cfg -c "adapter speed 5000"` - (Explanation of `USE_CORE`: https://github.com/raspberrypi/debugprobe/issues/45) -- `arm-none-eabi-gdb ./build/rp2040/cmd/sbc_harness/sbc_harness.elf` - ``` - target extended-remote localhost:3333 - monitor reset init - continue - ``` - https://openocd.org/doc/html/General-Commands.html - -SWD: -- https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html -- https://github.com/raspberrypi/debugprobe -- https://developer.arm.com/documentation/101451/0100/About-CMSIS-DAP - -## Raspberry Pi 3-pin Debug Connector -- https://datasheets.raspberrypi.com/debug/debug-connector-specification.pdf - -| Pin # | UART | ARM SW-DP | cJTAG (IEEE 1149.7) | -|-------|----------------------|------------------------------------|----------------------------------| -| 1 | RX (target←debugger) | SC/SWCLK (serial clock) | TMSC - Test Clock Control | -| 2 | GND | GND | GND | -| 3 | TX (target→debugger) | SD/SWDIO (bidi serial data) | TCKC - Test Master/Slave Control | -| | | SWO (optional: serial wire output) | | - -## UART (Universal asynchronous receiver/transmitter) - -## ARM SWD (Serial Wire Debug) -- https://developer.arm.com/documentation/101761/1-0/Debug-and-trace-interface/Serial-Wire-Debug-signals -- https://developer.arm.com/documentation/ihi0031/a/The-Serial-Wire-Debug-Port--SW-DP-/Introduction-to-the-ARM-Serial-Wire-Debug--SWD--protocol -- https://arm-software.github.io/CMSIS-DAP/latest/index.html -- ADIv5: https://developer.arm.com/documentation/ihi0031/g/ -- ADIv6: https://developer.arm.com/documentation/ihi0074/e/ - -Alternative to Raspberry Pi Debug Probe: -https://oshwlab.com/wagiminator/samd11c-swd-programmer which uses -https://github.com/ataradov/free-dap instead of ARM's reference CMSIS -implementation - -Note that the RP2040 chip has a pure SW-DP (just speaks SWD), not a -JTAG-DP (just speaks JTAG) or a SWJ-DP (can speak either SWD or JTAG). - -``` -+-[Host PC]-------------------------+ -| | -| +-----+ | -| | GDB | | -| +-----+ | -| ^ | -| | GDB Remote Serial Protocol -| | on localhost:3333 -| V | -| +---------+ | -| | OpenOCD | | -| +---------+ | -| ^ | -| | | -+-----------------|-----------------+ - USB-A - | - | ARM CMSIS-DAP protocol - | (a USB-based protocol) - | - (micro)USB-B - +-[Debug Probe]-------+ - | | | - | V | - | +----------------+ | - | | ARM's | | - | | reference | | - | | CMSIS-DAP | | - | | implementation | | - | +----------------+ | - | ^ | - | | | - +----------|----------+ - SW-DP (3-pin JST-SH) - | - | ADIv5 SWD protocol - | - SW-DP (3 .1" headers) -+-[SBC-Harness]---|-----------------+ -| | | -| ,---------------' | -| | | -| | +----------------------------+ | -| | | Cortex-M0+ | | -| | | +------------------------+ | | -| +--->| CoreSight ADIv5 module | | | -| | | +------------------------+ | | -| | +----------------------------+ | -| | | -| | +----------------------------+ | -| | | Cortex-M0+ | | -| | | +------------------------+ | | -| `--->| CoreSight ADIv5 module | | | -| | +------------------------+ | | -| +----------------------------+ | -| | -+-----------------------------------+ -``` + - picotool 2.1.1-1 + +Then, simply run `make`. This will create +`build/rp2040-*/cmd/sbc_harness/sbc_harness.{elf,bin,hex,uf2}` files: + + - The `.elf` is the firmware image plus debugger symbols and + relocation data. + - The `.bin` file is the raw firmware image (`objcopy -Obinary + INFILE.elf OUTFILE.bin`). + - The `.hex` file is the raw firmware image, encoded in the [Intel + HEX](https://archive.org/details/IntelHEXStandard) format (`objcopy + -Oihex INFILE.elf OUTFILE.hex`). Note that unlike the `.bin`, the + `.hex` may contain gaps/holes; holes are filled with 0x00-bytes in + the `.bin`, but tools for working with the `.hex` may fill them + with other data, causing minor differences when comparing the + `.bin` and `.hex`. (Yes, the `.hex` is expected to be about 2.8 + times the size of the `.bin`; twice for being ASCII-encoded hex, + plus another 13 bytes overhead for every 16 bytes of input.) + - The `.uf2` file is the `.bin` wrapped into a [USB Flashing Format + (UF2)](https://github.com/microsoft/uf2) container that can be used + with the bootrom flasher. (Yes, the `.uf2` is expected to be about + twice the size of the `.bin`; each 128-byte block of input is + wrapped in a 256-byte UF2 block.) + +There are several ways of putting this firmware file onto the harness: + + 1. bootrom flasher: Hold the "BOOTSEL" button when powering on. The + harness will appear to a host PC as a USB storage device. Simply + mount the device and copy the `.uf2` file to the device. It will + automatically reboot into the new firmware image. + 2. debug port: Using OpenOCD (see `HACKING.md`), run the OpenOCD command + `program /path/to/sbc_harness.elf reset` (TODO: I don't really + understand what OpenOCD is doing that it wants the `.elf` instead + of the `.bin`) + . + + If OpenOCD is not already running: + ``` + openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "program $PWD/build/rp2040-Debug/cmd/sbc_harness/sbc_harness.elf reset exit"` + ``` + + If OpenOCD is already running: + ``` + socat STDIO TCP:localhost:4444 <<<"program $PWD/build/rp2040-Debug/cmd/sbc_harness/sbc_harness.elf reset" + ``` + 3. Use `flashprog` or `flashrom` and a SOIC-8 clip to directly + program the flash chip. I'm not sure why you would do this + instead of one of the above. # Usage The harness uses DHCP to acquire an IPv4 address, then serves the 9P protocol over TCP: - - TCP port: 564 (9P does not have a standard TCP port number, but - this is the default port number used by most 9P-over-TCP clients, - including the Linux kernel's v9fs driver). + - TCP port: 564 - Supported protocol versions: - `9P2000` (base protocol): Yes - `9P2000.u` (Unix extension): Yes, with Linux kernel @@ -164,28 +92,28 @@ There are lots of 9P clients that you can use. 9P is a filesystem protocol; and you can mount it directly with the the Linux kernel's v9fs filesystem driver, with plan9port's `9pfuse`; or interact with it without mounting it using the shell commands `9p` (from plan9port), -`wmiir`, `ixpc`; or interact with it without mounting it by using a +`wmiir`, or `ixpc`; or interact with it without mounting it by using a library for your programming language of choice. Some notes on choosing a client: - On x86-32, the Linux kernel v9fs driver is known to drop entries - from directory listings; I advise using 9pfuse instead of you want + from directory listings; I advise using 9pfuse instead if you want to mount it on 32-bit systems. - I generally like mounting it as a real filesystem, but this means that you only get errno errors, and the more-helpful error strings are discarded. - - The sbc-harness only supports 8 concurrent connections to the 9P + - The sbc-harness only supports 7 concurrent connections to the 9P server, so while shell commands are handy for poking around, for real use where you're doing things in parallel you'll likely want to mount it or use a library that can reuse existing connections. # Bugs/Limitations - - Only supports 8 concurrent TCP connectsions to the 9P server (due + - Only supports 7 concurrent TCP connectsions to the 9P server (due to limitations in the W5500 TCP-offload chip; TODO: investigate using a software TCP/IP stack with the W5500 in MAC-raw mode) - - Only supports 16 concurrent 9P requests (I wanted a static limit, - and "connections*2" seemed reasonable) + - Only supports 2 concurrent 9P requests per connection (I wanted a + static limit, and 2 seemed reasonable) - Only supports IPv4, not IPv6 (due to limitations in the W5500 TCP-offload chip; TODO: investigate upgrading to the W6100 or using a software TCP/IP stack with the W5500 in MAC-raw mode) diff --git a/build-aux/embed-sources.h.gen b/build-aux/embed-sources.h.gen index d473094..ee9eb42 100755 --- a/build-aux/embed-sources.h.gen +++ b/build-aux/embed-sources.h.gen @@ -1,10 +1,10 @@ #!/bin/sh # build-aux/embed-sources.h.gen - Generate C definitions for GNU `ld -r -b binary` files # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later nm --format=posix "$@" | sed -n -E \ - -e 's/(.*_(end|start)) D .*/extern char \1[];/p' \ - -e 's/(.*_size) A .*/extern size_t \1;/p' + -e 's/(.*_(end|start)) [DR] .*/extern char \1[];/p' \ + -e 's/(.*_size) A .*/extern size_t \1;/p' diff --git a/build-aux/get-dscname b/build-aux/get-dscname new file mode 100755 index 0000000..34a1b08 --- /dev/null +++ b/build-aux/get-dscname @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# build-aux/get-dscname - Get a file's self-described filename +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +if [ $# -ne 1 ]; then + echo "$0: expected exactly 1 argument" + exit 2 +fi + +if [[ $1 == */Documentation/* ]] && [[ "$(sed 1q -- "$1")" == 'NAME' ]]; then + sed -n ' + 2{ + s,[/.],_,g; + s,^\s*_,Documentation/,; + s,$,.txt,; + + p; + q; + } + ' -- "$1" +else + sed -n ' + 1,3{ + /^\#!/d; + /^<!--$/d; + /-\*- .* -\*-/d; + s,[/*\# ]*,,; + s/ - .*//; + + p; + q; + } + ' -- "$1" +fi diff --git a/build-aux/lint-bin b/build-aux/lint-bin new file mode 100755 index 0000000..91f1612 --- /dev/null +++ b/build-aux/lint-bin @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# build-aux/lint-bin - Lint final binary images +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +set -euE -o pipefail +shopt -s extglob + +# There are several exisi files we can use: +# +# Binaries: +# - ${elf} : firmware image with debug symbols and relocationd data +# - ${elf%.elf}.bin : raw firmware image +# - ${elf%.elf}.hex : .bin as Intel HEX +# - ${elf%.elf}.uf2 : .bin as USB Flashing Format (UF2) +# +# Textual info: +# - ${elf%.elf}.dis : `objdump --section-headers ${elf}; objdump --disassemble ${elf}; picotool coprodis --quiet ${elf}` +# - ${elf}.map : `ld --print-map` info +# - ${elf%.elf}_stack.c : `stack.c.gen` + +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +err() { + printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 + r=1 +} + +# Input is `ld --print-map` format. +# +# Output is a series of lines in the format "symbol location size +# source". Whitespace may seem silly. +objdump_globals() { + sed -E -n '/^ \.t?(data|bss)\./{ / 0x/{ p; D; }; N; s/\n/ /; p; }' <"$1" +} + +readelf_funcs() { + local in_elffile + in_elffile=$1 + + readelf --syms --wide -- "$in_elffile" | + awk '$4 == "FUNC" { print $8 }' +} + +lint_globals() { + local in_mapfile + in_mapfile=$1 + + local rel_base + rel_base=${in_mapfile#build/*/} + rel_base=${rel_base%/*} + + local topdir + topdir=$PWD + + { + echo 'Source Symbol Size' + objdump_globals "$in_mapfile" | + { + cd "$rel_base" + total=0 + while read -r symbol addr size source; do + if ((addr == 0)); then + continue + fi + case "$source" in + /*) + # libg.a(whatever.o) -> libg.a + source="${source%(*)}" + # resolve `..` components + source="$(realpath --canonicalize-missing --no-symlinks -- "$source")" + ;; + CMakeFiles/*.dir/*.@(obj|o)) + # CMakeFiles/sbc_harnes_objs.dir/... + source="${source#CMakeFiles/*.dir/}" + source="${source%.@(obj|o)}" + source="${source//__/..}" + source="$(realpath --canonicalize-missing --no-symlinks --relative-to="$topdir" -- "$source")" + ;; + esac + printf "%s %s 0x%04x (%'d)\n" "$source" "$symbol" "$size" "$size" + total=$((total + size)) + done + printf "~ Total 0x%04x (%'d)\n" "$total" "$total" + } | + LC_COLLATE=C sort + } | column -t +} + +lint_stack() { + local in_elffile + in_elffile=$1 + + IFS='' + while read -r line; do + func=${line#$'\t'} + if [[ $line == $'\t'* ]]; then + err "$in_elffile" "function in binary but not _stack.c: ${func}" + else + err "$in_elffile" "function in _stack.c but not binary: ${func}" + fi + done < <( + comm -3 \ + <(sed -En 's/^included: (.*:)?//p' "${in_elffile%.elf}_stack.c" | sort -u) \ + <(readelf_funcs "$in_elffile" | sed -E -e 's/\.part\.[0-9]*$//' -e 's/^__(.*)_veneer$/\1/' | sort -u) + ) +} + +lint_func_blocklist() { + local in_elffile + in_elffile=$1 + + local blocklist=( + gpio_default_irq_handler + ) + + while read -r func; do + err "$in_elffile" "Contains blocklisted function: ${func}" + done < <(readelf --syms --wide -- "$in_elffile" | + awk '$4 == "FUNC" { print $8 }' | + grep -Fx "${blocklist[@]/#/-e}") +} + +main() { + r=0 + + local elf + for elf in "$@"; do + { + echo 'Global variables:' + lint_globals "${elf}.map" | sed 's/^/ /' + } >"${elf%.elf}.lint.globals" + (lint_stack "$elf") &>"${elf%.elf}.lint.stack" + lint_func_blocklist "$elf" + done + + return $r +} + +main "$@" diff --git a/build-aux/lint-generic b/build-aux/lint-generic new file mode 100755 index 0000000..290988c --- /dev/null +++ b/build-aux/lint-generic @@ -0,0 +1,60 @@ +#!/bin/sh +# build-aux/lint-generic - Non-language-specific lint checks +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +err() { + printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 + r=1 +} + +r=0 +for filename in "$@"; do + if ! { [ -f "$filename" ] && ! [ -h "$filename" ]; }; then + # Ignore non-files + continue + fi + + # File header ########################################################## + + shebang="$(sed -n '1{/^#!/{/^#!\/hint\//q; p;};}' "$filename")" + if [ -x "$filename" ] && [ -z "$shebang" ]; then + err "$filename" 'is executable but does not have a shebang' + elif [ -n "$shebang" ] && ! [ -x "$filename" ]; then + err "$filename" 'has a shebang but is executable' + fi + + if ! grep -E -q 'Copyright \(C\) 202[4-9]((-|, )202[5-9])* Luke T. Shumaker' "$filename"; then + err "$filename" 'is missing a copyright statement' + fi + if test -e .git && ! git diff --quiet milestone/2025-01-01 HEAD -- "$filename"; then + if ! grep -E -q 'Copyright \(C\) .*2025 Luke T. Shumaker' "$filename"; then + err "$filename" 'has an outdated copyright statement' + fi + fi + if ! grep -q '\sSPDX-License-Identifier[:] ' "$filename"; then + err "$filename" 'is missing an SPDX-License-Identifier' + fi + + dscname_act=$(./build-aux/get-dscname "$filename") + dscname_exp=$(echo "$filename" | sed \ + -e 's,.*/config/,,' \ + -e 's,.*/config\.h$,config.h,' \ + -e 's,.*include/,,' \ + -e 's,.*static/,,' \ + -e 's/\.wip$//') + if [ "$dscname_act" != "$dscname_exp" ] && [ "cmd/$dscname_act" != "$dscname_exp" ]; then + err "$filename" "self-identifies as $dscname_act (expected $dscname_exp)" + fi + + # File body ############################################################ + + if grep -n --color=auto "$(printf '\\S\t')" "$filename"; then + err "$filename" 'uses tabs for alignment' + fi +done +exit $r diff --git a/build-aux/lint-h b/build-aux/lint-h new file mode 100755 index 0000000..7459032 --- /dev/null +++ b/build-aux/lint-h @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# build-aux/lint-h - Lint checks for C header files +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +err() { + printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 + r=1 +} + +r=0 +for filename in "$@"; do + dscname=$(./build-aux/get-dscname "$filename") + guard=${dscname//'/'/'_'} + guard=${guard//'.'/'_'} + guard="_${guard^^}_" + if ! { grep -Fxq "#ifndef ${guard}" "$filename" && + grep -Fxq "#define ${guard}" "$filename" && + grep -Fxq "#endif /* ${guard} */" "$filename"; }; then + err "$filename" "does not have ${guard} guard" + fi +done +exit $r diff --git a/build-aux/lint-unknown b/build-aux/lint-unknown new file mode 100755 index 0000000..dda9541 --- /dev/null +++ b/build-aux/lint-unknown @@ -0,0 +1,24 @@ +#!/bin/sh +# build-aux/lint-unknown - Lint checks for unknown files +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +err() { + printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 + r=1 +} + +r=0 +for filename in "$@"; do + if ! { [ -f "$filename" ] && ! [ -h "$filename" ]; }; then + # Ignore non-files + continue + fi + + err "$filename" 'cannot lint unknown file type' +done +exit $r diff --git a/build-aux/measurestack/__init__.py b/build-aux/measurestack/__init__.py new file mode 100644 index 0000000..c1b9d7f --- /dev/null +++ b/build-aux/measurestack/__init__.py @@ -0,0 +1,38 @@ +# build-aux/measurestack/__init__.py - Analyze stack sizes for compiled objects +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import re +import sys + +from . import app_main + +# pylint: disable=unused-variable +__all__ = [ + "main", +] + + +re_c_obj_suffix = re.compile(r"\.c\.(?:o|obj)$") + + +def main() -> None: + pico_platform = sys.argv[1] + base_dir = sys.argv[2] + obj_fnames = set(sys.argv[3:]) + + c_fnames: set[str] = set() + ci_fnames: set[str] = set() + for obj_fname in obj_fnames: + if re_c_obj_suffix.search(obj_fname): + ci_fnames.add(re_c_obj_suffix.sub(".c.ci", obj_fname)) + with open(obj_fname + ".d", "r", encoding="utf-8") as fh: + c_fnames.update(fh.read().replace("\\\n", " ").split(":")[-1].split()) + + app_main.main( + arg_pico_platform=pico_platform, + arg_base_dir=base_dir, + arg_ci_fnames=ci_fnames, + arg_c_fnames=c_fnames, + ) diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py new file mode 100644 index 0000000..a93874f --- /dev/null +++ b/build-aux/measurestack/analyze.py @@ -0,0 +1,429 @@ +# build-aux/measurestack/analyze.py - Analyze stack sizes for compiled objects +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import re +import sys +import typing + +from . import vcg + +# pylint: disable=unused-variable +__all__ = [ + "BaseName", + "QName", + "UsageKind", + "Node", + "AnalyzeResultVal", + "AnalyzeResultGroup", + "AnalyzeResult", + "analyze", +] + +# types ######################################################################## + + +class BaseName: + # class ########################################################## + + _interned: dict[str, "BaseName"] = {} + + def __new__(cls, content: str) -> "BaseName": + if ":" in content: + raise ValueError(f"invalid non-qualified name: {content!r}") + content = sys.intern(content) + if content not in cls._interned: + self = super().__new__(cls) + self._content = content + cls._interned[content] = self + return cls._interned[content] + + # instance ####################################################### + + _content: str + + def __str__(self) -> str: + return self._content + + def __repr__(self) -> str: + return f"BaseName({self._content!r})" + + def __format__(self, fmt_spec: str, /) -> str: + return repr(self) + + def __eq__(self, other: typing.Any) -> bool: + assert isinstance( + other, BaseName + ), f"comparing BaseName with {other.__class__.__name__}" + return self._content == other._content + + def __lt__(self, other: "BaseName") -> bool: + return self._content < other._content + + def __hash__(self) -> int: + return self._content.__hash__() + + def as_qname(self) -> "QName": + return QName(self._content) + + +class QName: + # class ########################################################## + + _interned: dict[str, "QName"] = {} + + def __new__(cls, content: str) -> "QName": + content = sys.intern(content) + if content not in cls._interned: + self = super().__new__(cls) + self._content = content + self._base = None + cls._interned[content] = self + return cls._interned[content] + + # instance ####################################################### + + _content: str + _base: BaseName | None + + def __str__(self) -> str: + return self._content + + def __repr__(self) -> str: + return f"QName({self._content!r})" + + def __format__(self, fmt_spec: str, /) -> str: + return repr(self) + + def __eq__(self, other: typing.Any) -> bool: + assert isinstance( + other, QName + ), f"comparing QName with {other.__class__.__name__}" + return self._content == other._content + + def __lt__(self, other: "QName") -> bool: + return self._content < other._content + + def __hash__(self) -> int: + return self._content.__hash__() + + def base(self) -> BaseName: + if self._base is None: + self._base = BaseName(self._content.rsplit(":", 1)[-1].split(".", 1)[0]) + return self._base + + +UsageKind: typing.TypeAlias = typing.Literal["static", "dynamic", "dynamic,bounded"] + + +class Node: + # from .title (`static` and `__weak` functions are prefixed with + # the compilation unit .c file. For static functions that's fine, + # but we'll have to handle it specially for __weak.). + funcname: QName + # .label is "{funcname}\n{location}\n{nstatic} bytes (static}\n{ndynamic} dynamic objects" + location: str + usage_kind: UsageKind + nstatic: int + ndynamic: int + + # edges with .sourcename set to this node, val is if it's + # OK/expected that the function be missing. + calls: dict[QName, bool] + + +class AnalyzeResultVal(typing.NamedTuple): + nstatic: int + cnt: int + + +class AnalyzeResultGroup(typing.NamedTuple): + rows: dict[QName, AnalyzeResultVal] + + +class AnalyzeResult(typing.NamedTuple): + groups: dict[str, AnalyzeResultGroup] + missing: set[QName] + dynamic: set[QName] + + included_funcs: set[QName] + + +class SkipModel(typing.NamedTuple): + """Running the skipmodel calls `.fn(chain, ...)` with the chain + consisting of the last `.nchain` items (if .nchain is an int), or + the chain starting with the *last* occurance of `.nchain` (if + .nchain is a collection). If the chain is not that long or does + not contain a member of the collection, then .fn is not called and + the call is *not* skipped. + + """ + + nchain: int | typing.Collection[BaseName] + fn: typing.Callable[[typing.Sequence[QName], QName], bool] + + def __call__(self, chain: typing.Sequence[QName], call: QName) -> tuple[bool, int]: + if isinstance(self.nchain, int): + if len(chain) >= self.nchain: + _chain = chain[-self.nchain :] + return self.fn(_chain, call), len(_chain) + else: + for i in reversed(range(len(chain))): + if chain[i].base() in self.nchain: + _chain = chain[i - 1 :] + return self.fn(_chain, call), len(_chain) + return False, 0 + + +class Application(typing.Protocol): + def extra_nodes(self) -> typing.Collection[Node]: ... + def indirect_callees( + self, elem: vcg.VCGElem + ) -> tuple[typing.Collection[QName], bool]: ... + def skipmodels(self) -> dict[BaseName, SkipModel]: ... + + +# code ######################################################################### + +re_node_label = re.compile( + r"(?P<funcname>[^\n]+)\n" + + r"(?P<location>[^\n]+:[0-9]+:[0-9]+)\n" + + r"(?P<nstatic>[0-9]+) bytes \((?P<usage_kind>static|dynamic|dynamic,bounded)\)\n" + + r"(?P<ndynamic>[0-9]+) dynamic objects" + + r"(?:\n.*)*", + flags=re.MULTILINE, +) + + +class _Graph: + graph: dict[QName, Node] + qualified: dict[BaseName, QName] + + _resolve_cache: dict[QName, QName | None] + + def __init__(self) -> None: + self._resolve_cache = {} + + def _resolve_funcname(self, funcname: QName) -> QName | None: + s = str(funcname) + is_qualified = ":" in s + + # Handle `ld --wrap` functions + if not is_qualified: + with_wrap = QName(f"__wrap_{s}") + if with_wrap in self.graph: + return with_wrap + if s.startswith("__real_"): + without_real = QName(s[len("__real_") :]) + if without_real in self.graph: + funcname = without_real + + # Usual case + if funcname in self.graph: + return funcname + + # Handle `__weak`/`[[gnu::weak]]` functions + if not is_qualified: + return self.qualified.get(BaseName(s)) + + return None + + def resolve_funcname(self, funcname: QName) -> QName | None: + if funcname not in self._resolve_cache: + self._resolve_cache[funcname] = self._resolve_funcname(funcname) + return self._resolve_cache[funcname] + + +def _make_graph( + ci_fnames: typing.Collection[str], + app: Application, +) -> _Graph: + graph: dict[QName, Node] = {} + qualified: dict[BaseName, set[QName]] = {} + + def handle_elem(elem: vcg.VCGElem) -> None: + match elem.typ: + case "node": + node = Node() + node.calls = {} + skip = False + for k, v in elem.attrs.items(): + match k: + case "title": + node.funcname = QName(v) + case "label": + if elem.attrs.get("shape", "") != "ellipse": + m = re_node_label.fullmatch(v) + if not m: + raise ValueError(f"unexpected label value {v!r}") + node.location = m.group("location") + node.usage_kind = typing.cast( + UsageKind, m.group("usage_kind") + ) + node.nstatic = int(m.group("nstatic")) + node.ndynamic = int(m.group("ndynamic")) + case "shape": + if v != "ellipse": + raise ValueError(f"unexpected shape value {v!r}") + skip = True + case _: + raise ValueError(f"unknown edge key {k!r}") + if not skip: + if node.funcname in graph: + raise ValueError(f"duplicate node {node.funcname}") + graph[node.funcname] = node + if ":" in str(node.funcname): + basename = node.funcname.base() + if basename not in qualified: + qualified[basename] = set() + qualified[basename].add(node.funcname) + case "edge": + caller: QName | None = None + callee: QName | None = None + for k, v in elem.attrs.items(): + match k: + case "sourcename": + caller = QName(v) + case "targetname": + callee = QName(v) + case "label": + pass + case _: + raise ValueError(f"unknown edge key {k!r}") + if caller is None or callee is None: + raise ValueError(f"incomplete edge: {elem.attrs!r}") + if caller not in graph: + raise ValueError(f"unknown caller: {caller}") + if callee == QName("__indirect_call"): + callees, missing_ok = app.indirect_callees(elem) + for callee in callees: + if callee not in graph[caller].calls: + graph[caller].calls[callee] = missing_ok + else: + graph[caller].calls[callee] = False + case _: + raise ValueError(f"unknown elem type {elem.typ!r}") + + for ci_fname in ci_fnames: + with open(ci_fname, "r", encoding="utf-8") as fh: + for elem in vcg.parse_vcg(fh): + handle_elem(elem) + + for node in app.extra_nodes(): + if node.funcname in graph: + raise ValueError(f"duplicate node {node.funcname}") + graph[node.funcname] = node + + ret = _Graph() + ret.graph = graph + ret.qualified = {} + for bname, qnames in qualified.items(): + if len(qnames) == 1: + ret.qualified[bname] = next(name for name in qnames) + return ret + + +def analyze( + *, + ci_fnames: typing.Collection[str], + app_func_filters: dict[str, typing.Callable[[QName], tuple[int, bool]]], + app: Application, + cfg_max_call_depth: int, +) -> AnalyzeResult: + graphdata = _make_graph(ci_fnames, app) + + missing: set[QName] = set() + dynamic: set[QName] = set() + included_funcs: set[QName] = set() + + dbg = False + + track_inclusion: bool = True + + skipmodels = app.skipmodels() + for name, model in skipmodels.items(): + if isinstance(model.nchain, int): + assert model.nchain > 1 + else: + assert len(model.nchain) > 0 + + _nstatic_cache: dict[QName, int] = {} + + def _nstatic(chain: list[QName], funcname: QName) -> tuple[int, int]: + nonlocal dbg + nonlocal track_inclusion + + assert funcname in graphdata.graph + + node = graphdata.graph[funcname] + if dbg: + print(f"//dbg: {'- '*len(chain)}{funcname}\t{node.nstatic}") + if node.usage_kind == "dynamic" or node.ndynamic > 0: + dynamic.add(funcname) + if track_inclusion: + included_funcs.add(funcname) + + max_call_nstatic = 0 + max_call_nchain = 0 + + if node.calls: + skipmodel = skipmodels.get(funcname.base()) + chain.append(funcname) + if len(chain) == cfg_max_call_depth: + raise ValueError(f"max call depth exceeded: {chain}") + for call_orig_qname, call_missing_ok in node.calls.items(): + skip_nchain = 0 + # 1. Resolve + call_qname = graphdata.resolve_funcname(call_orig_qname) + if not call_qname: + if skipmodel: + skip, _ = skipmodel(chain, call_orig_qname) + if skip: + if dbg: + print( + f"//dbg: {'- '*len(chain)}{call_orig_qname}\tskip missing" + ) + continue + if not call_missing_ok: + missing.add(call_orig_qname) + if dbg: + print(f"//dbg: {'- '*len(chain)}{call_orig_qname}\tmissing") + continue + + # 2. Skip + if skipmodel: + skip, skip_nchain = skipmodel(chain, call_qname) + max_call_nchain = max(max_call_nchain, skip_nchain) + if skip: + if dbg: + print(f"//dbg: {'- '*len(chain)}{call_qname}\tskip") + continue + + # 3. Call + if skip_nchain == 0 and call_qname in _nstatic_cache: + max_call_nstatic = max(max_call_nstatic, _nstatic_cache[call_qname]) + else: + call_nstatic, call_nchain = _nstatic(chain, call_qname) + max_call_nstatic = max(max_call_nstatic, call_nstatic) + max_call_nchain = max(max_call_nchain, call_nchain) + if skip_nchain == 0 and call_nchain == 0: + _nstatic_cache[call_qname] = call_nstatic + chain.pop() + return node.nstatic + max_call_nstatic, max(0, max_call_nchain - 1) + + def nstatic(funcname: QName) -> int: + return _nstatic([], funcname)[0] + + groups: dict[str, AnalyzeResultGroup] = {} + for grp_name, grp_filter in app_func_filters.items(): + rows: dict[QName, AnalyzeResultVal] = {} + for funcname in graphdata.graph: + cnt, track_inclusion = grp_filter(funcname) + if cnt: + rows[funcname] = AnalyzeResultVal(nstatic=nstatic(funcname), cnt=cnt) + groups[grp_name] = AnalyzeResultGroup(rows=rows) + + return AnalyzeResult( + groups=groups, missing=missing, dynamic=dynamic, included_funcs=included_funcs + ) diff --git a/build-aux/measurestack/app_main.py b/build-aux/measurestack/app_main.py new file mode 100644 index 0000000..7573146 --- /dev/null +++ b/build-aux/measurestack/app_main.py @@ -0,0 +1,132 @@ +# build-aux/measurestack/app_main.py - Application-specific wrapper around analyze.py +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import os.path +import typing + +from . import analyze, app_output, app_plugins, util +from .analyze import BaseName, QName + +# pylint: disable=unused-variable +__all__ = [ + "main", +] + + +def main( + *, + arg_pico_platform: str, + arg_base_dir: str, + arg_ci_fnames: typing.Collection[str], + arg_c_fnames: typing.Collection[str], +) -> None: + + plugins: list[util.Plugin] = [] + + # sbc-harness #################################################### + + libobj_plugin = app_plugins.LibObjPlugin(arg_c_fnames) + lib9p_plugin = app_plugins.Lib9PPlugin(arg_base_dir, arg_c_fnames, libobj_plugin) + + def sbc_is_thread(name: QName) -> int: + if str(name).endswith("_cr") and name.base() != BaseName("lib9p_srv_read_cr"): + if "9p" in str(name.base()) or "lib9p/tests/test_server/main.c:" in str( + name + ): + return lib9p_plugin.thread_count(name) + return 1 + if name.base() == ( + BaseName("_entry_point") + if arg_pico_platform == "rp2040" + else BaseName("main") + ): + return 1 + return 0 + + plugins += [ + app_plugins.CmdPlugin(), + libobj_plugin, + app_plugins.PicoFmtPlugin(arg_pico_platform), + app_plugins.LibHWPlugin(arg_pico_platform, libobj_plugin), + app_plugins.LibCRPlugin(), + app_plugins.LibCRIPCPlugin(), + lib9p_plugin, + app_plugins.LibMiscPlugin(), + ] + + # pico-sdk ####################################################### + + if arg_pico_platform == "rp2040": + + def get_init_array() -> typing.Collection[QName]: + ret: list[QName] = [] + for plugin in plugins: + ret.extend(plugin.init_array()) + return ret + + plugins += [ + app_plugins.PicoSDKPlugin( + get_init_array=get_init_array, + ), + app_plugins.TinyUSBDevicePlugin(arg_c_fnames), + app_plugins.NewlibPlugin(), + app_plugins.LibGCCPlugin(), + ] + + # Tie it all together ############################################ + + def thread_filter(name: QName) -> tuple[int, bool]: + return sbc_is_thread(name), True + + def intrhandler_filter(name: QName) -> tuple[int, bool]: + for plugin in plugins: + if plugin.is_intrhandler(name): + return 1, True + return 0, False + + def misc_filter(name: QName) -> tuple[int, bool]: + if name in [ + QName("__assert_msg_fail"), + QName("__lm_printf"), + QName("__lm_light_printf"), + QName("fmt_vfctprintf"), + QName("fmt_vsnprintf"), + ]: + return 1, False + return 0, False + + extra_includes: list[BaseName] = [] + for plugin in plugins: + extra_includes.extend(plugin.extra_includes()) + + def extra_filter(name: QName) -> tuple[int, bool]: + nonlocal extra_includes + if name.base() in extra_includes: + return 1, True + return 0, False + + def _str_location_xform(loc: str) -> str: + if not loc.startswith("/"): + return loc + parts = loc.split(":", 1) + parts[0] = "./" + os.path.relpath(parts[0], arg_base_dir) + return ":".join(parts) + + def location_xform(_loc: QName) -> str: + return _str_location_xform(str(_loc)) + + result = analyze.analyze( + ci_fnames=arg_ci_fnames, + app_func_filters={ + "Threads": thread_filter, + "Interrupt handlers": intrhandler_filter, + "Misc": misc_filter, + "Extra": extra_filter, + }, + app=util.PluginApplication(_str_location_xform, plugins), + cfg_max_call_depth=100, + ) + + app_output.print_c(result, location_xform) diff --git a/build-aux/measurestack/app_output.py b/build-aux/measurestack/app_output.py new file mode 100644 index 0000000..5336b85 --- /dev/null +++ b/build-aux/measurestack/app_output.py @@ -0,0 +1,139 @@ +# build-aux/measurestack/app_output.py - Generate `*_stack.c` files +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import typing + +from . import analyze +from .analyze import QName + +# pylint: disable=unused-variable +__all__ = [ + "print_c", +] + + +def print_group( + result: analyze.AnalyzeResult, + location_xform: typing.Callable[[QName], str], + grp_name: str, +) -> None: + grp = result.groups[grp_name] + if not grp.rows: + print(f"= {grp_name} (empty) =") + return + + nsum = sum(v.nstatic * v.cnt for v in grp.rows.values()) + nmax = max(v.nstatic for v in grp.rows.values()) + + # Figure sizes. + namelen = max( + [len(location_xform(k)) for k in grp.rows.keys()] + [len(grp_name) + 4] + ) + numlen = len(str(nsum)) + sep1 = ("=" * namelen) + " " + "=" * numlen + sep2 = ("-" * namelen) + " " + "-" * numlen + + # Print. + print("= " + grp_name + " " + sep1[len(grp_name) + 3 :]) + for qname, val in sorted(grp.rows.items()): + name = location_xform(qname) + if val.nstatic == 0: + continue + print( + f"{name:<{namelen}} {val.nstatic:>{numlen}}" + + (f" * {val.cnt}" if val.cnt != 1 else "") + ) + print(sep2) + print(f"{'Total':<{namelen}} {nsum:>{numlen}}") + print(f"{'Maximum':<{namelen}} {nmax:>{numlen}}") + print(sep1) + + +def next_power_of_2(x: int) -> int: + return 1 << (x.bit_length()) + + +def print_c( + result: analyze.AnalyzeResult, location_xform: typing.Callable[[QName], str] +) -> None: + print("#include <stddef.h> /* for size_t */") + print() + print("/*") + print_group(result, location_xform, "Threads") + print_group(result, location_xform, "Interrupt handlers") + print("*/") + intrstack = max( + v.nstatic for v in result.groups["Interrupt handlers"].rows.values() + ) + stack_guard_size = 16 * 2 + + class CrRow(typing.NamedTuple): + name: str + cnt: int + base: int + size: int + + rows: list[CrRow] = [] + mainrow: CrRow | None = None + for funcname, val in result.groups["Threads"].rows.items(): + name = str(funcname.base()) + base = val.nstatic + size = base + intrstack + if name in ["main", "_entry_point"]: + mainrow = CrRow(name=name, cnt=1, base=base, size=size) + else: + size = next_power_of_2(size + stack_guard_size) - stack_guard_size + rows.append(CrRow(name=name, cnt=val.cnt, base=base, size=size)) + namelen = max(len(r.name) for r in rows) + baselen = max(len(str(r.base)) for r in rows) + sizesum = sum(r.cnt * (r.size + stack_guard_size) for r in rows) + sizelen = len(str(max(sizesum, mainrow.size if mainrow else 0))) + + def print_row(comment: bool, name: str, size: int, eqn: str | None = None) -> None: + prefix = "const size_t CONFIG_COROUTINE_STACK_SIZE_" + if comment: + print(f"/* {name}".ljust(len(prefix) + namelen), end="") + else: + print(f"{prefix}{name:<{namelen}}", end="") + print(f" = {size:>{sizelen}};", end="") + if comment: + print(" */", end="") + elif eqn: + print(" ", end="") + if eqn: + print(f" /* {eqn} */", end="") + print() + + for row in sorted(rows): + print_row( + False, + row.name, + row.size, + f"LM_NEXT_POWER_OF_2({row.base:>{baselen}}+{intrstack}+{stack_guard_size})-{stack_guard_size}", + ) + print_row(True, "TOTAL (inc. stack guard)", sizesum) + if mainrow: + print_row( + True, + "MAIN/KERNEL", + mainrow.size, + f" {mainrow.base:>{baselen}}+{intrstack}", + ) + print() + print("/*") + print_group(result, location_xform, "Misc") + + for funcname in sorted(result.missing): + print(f"warning: missing: {location_xform(funcname)}") + for funcname in sorted(result.dynamic): + print(f"warning: dynamic-stack-usage: {location_xform(funcname)}") + + print("*/") + print("") + print("/*") + print_group(result, location_xform, "Extra") + for funcname in sorted(result.included_funcs): + print(f"included: {location_xform(funcname)}") + print("*/") diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py new file mode 100644 index 0000000..6eeb35b --- /dev/null +++ b/build-aux/measurestack/app_plugins.py @@ -0,0 +1,1006 @@ +# build-aux/measurestack/app_plugins.py - Application-specific plugins for analyze.py +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import re +import typing + +from . import analyze, util +from .analyze import BaseName, Node, QName +from .util import synthetic_node + +# pylint: disable=unused-variable +__all__ = [ + "CmdPlugin", + "LibObjPlugin", + "LibHWPlugin", + "LibCRPlugin", + "LibCRIPCPlugin", + "Lib9PPlugin", + "LibMiscPlugin", + "PicoFmtPlugin", + "PicoSDKPlugin", + "TinyUSBDevicePlugin", + "NewlibPlugin", + "LibGCCPlugin", +] + + +class CmdPlugin: + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + if "/3rd-party/" in loc: + return None + if "srv->auth" in line: + return [], False + if "srv->rootdir" in line: + return [QName("get_root")], False + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} + + +re_comment = re.compile(r"/\*.*?\*/") +re_ws = re.compile(r"\s+") +re_lo_iface = re.compile(r"^\s*#\s*define\s+(?P<name>\S+)_LO_IFACE") +re_lo_func = re.compile(r"LO_FUNC *\([^,]*, *(?P<name>[^,) ]+) *[,)]") +re_lo_implementation = re.compile( + r"^LO_IMPLEMENTATION_[HC]\s*\(\s*(?P<iface>[^, ]+)\s*,\s*(?P<impl_typ>[^,]+)\s*,\s*(?P<impl_name>[^, ]+)\s*[,)].*" +) +re_call_objcall = re.compile(r"LO_CALL\((?P<obj>[^,]+), (?P<meth>[^,)]+)[,)].*") + + +class LibObjPlugin: + objcalls: dict[str, set[QName]] # method_name => {method_impls} + + def __init__(self, arg_c_fnames: typing.Collection[str]) -> None: + ifaces: dict[str, set[str]] = {} # iface_name => {method_names} + for fname in arg_c_fnames: + with open(fname, "r", encoding="utf-8") as fh: + while line := fh.readline(): + if m := re_lo_iface.match(line): + iface_name = m.group("name") + if iface_name not in ifaces: + ifaces[iface_name] = set() + while line.endswith("\\\n"): + line += fh.readline() + line = line.replace("\\\n", " ") + line = re_comment.sub(" ", line) + line = re_ws.sub(" ", line) + for m2 in re_lo_func.finditer(line): + ifaces[iface_name].add(m2.group("name")) + + implementations: dict[str, set[str]] = {} # iface_name => {impl_names} + for iface_name in ifaces: + implementations[iface_name] = set() + for fname in arg_c_fnames: + with open(fname, "r", encoding="utf-8") as fh: + for line in fh: + line = line.strip() + if m := re_lo_implementation.match(line): + implementations[m.group("iface")].add(m.group("impl_name")) + + objcalls: dict[str, set[QName]] = {} # method_name => {method_impls} + for iface_name, iface in ifaces.items(): + for method_name in iface: + if method_name not in objcalls: + objcalls[method_name] = set() + for impl_name in implementations[iface_name]: + objcalls[method_name].add(QName(impl_name + "_" + method_name)) + self.objcalls = objcalls + + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + + if "/3rd-party/" in loc: + return None + if m := re_call_objcall.fullmatch(line): + if m.group("meth") in self.objcalls: + return self.objcalls[m.group("meth")], False + return [ + QName(f"__indirect_call:{m.group('obj')}.vtable->{m.group('meth')}") + ], False + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} + + +class LibHWPlugin: + pico_platform: str + libobj: LibObjPlugin + + def __init__(self, arg_pico_platform: str, libobj: LibObjPlugin) -> None: + self.pico_platform = arg_pico_platform + self.libobj = libobj + + def is_intrhandler(self, name: QName) -> bool: + return name.base() in [ + BaseName("rp2040_hwtimer_intrhandler"), + BaseName("hostclock_handle_sig_alarm"), + BaseName("hostnet_handle_sig_io"), + BaseName("gpioirq_handler"), + BaseName("dmairq_handler"), + ] + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + if "/3rd-party/" in loc: + return None + for fn in [ + "io_readv", + "io_writev", + "io_close", + "io_close_read", + "io_close_write", + "io_readwritev", + ]: + if f"{fn}(" in line: + return self.libobj.indirect_callees(loc, f"LO_CALL(x, {fn[3:]})") + if "io_read(" in line: + return self.libobj.indirect_callees(loc, "LO_CALL(x, readv)") + if "io_writev(" in line: + return self.libobj.indirect_callees(loc, "LO_CALL(x, writev)") + if "trigger->cb(trigger->cb_arg)" in line: + ret = [ + QName("alarmclock_sleep_intrhandler"), + ] + if self.pico_platform == "rp2040": + ret += [ + QName("w5500_tcp_alarm_handler"), + QName("w5500_udp_alarm_handler"), + ] + return ret, False + if "/rp2040_gpioirq.c:" in loc and "handler->fn" in line: + return [ + QName("w5500_intrhandler"), + ], False + if "/rp2040_dma.c:" in loc and "handler->fn" in line: + return [ + QName("rp2040_hwspi_intrhandler"), + ], False + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} + + +class LibCRPlugin: + def is_intrhandler(self, name: QName) -> bool: + return name.base() in [ + BaseName("_cr_gdb_intrhandler"), + ] + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} + + +class LibCRIPCPlugin: + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + if "/3rd-party/" in loc: + return None + if "/chan.c:" in loc and "front->dequeue(" in line: + return [ + QName("cr_chan_dequeue"), + QName("cr_select_dequeue"), + ], False + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} + + +re_tmessage_handler = re.compile( + r"^\s*\[LIB9P_TYP_T[^]]+\]\s*=\s*\(tmessage_handler\)\s*(?P<handler>\S+),\s*$" +) +re_lib9p_msg_entry = re.compile(r"^\s*_MSG_(?:[A-Z]+)\((?P<typ>\S+)\),$") +re_lib9p_caller = re.compile( + r"^lib9p_(?P<grp>[TR])msg_(?P<meth>validate|unmarshal|marshal)$" +) +re_lib9p_callee = re.compile( + r"^(?P<meth>validate|unmarshal|marshal)_(?P<msg>(?P<grp>[TR]).*)$" +) + + +class Lib9PPlugin: + tmessage_handlers: set[QName] | None + lib9p_msgs: set[str] + _CONFIG_9P_NUM_SOCKS: int | None + CONFIG_9P_SRV_MAX_REQS: int | None + CONFIG_9P_SRV_MAX_DEPTH: int | None + formatters: typing.Collection[BaseName] + + def __init__( + self, + arg_base_dir: str, + arg_c_fnames: typing.Collection[str], + libobj_plugin: LibObjPlugin, + ) -> None: + self.formatters = { + x.base() + for x in libobj_plugin.objcalls["format"] + if str(x.base()).startswith("lib9p_") + } + + # Find filenames ####################################################### + + def _is_config_h(fname: str) -> bool: + if not fname.startswith(arg_base_dir + "/"): + return False + suffix = fname[len(arg_base_dir) + 1 :] + if suffix.startswith("3rd-party/"): + return False + return suffix.endswith("/config.h") + + config_h_fname = util.get_zero_or_one(_is_config_h, arg_c_fnames) + + lib9p_srv_c_fname = util.get_zero_or_one( + lambda fname: fname.endswith("lib9p/srv.c"), arg_c_fnames + ) + + lib9p_generated_c_fname = util.get_zero_or_one( + lambda fname: fname.endswith("lib9p/9p.generated.c"), arg_c_fnames + ) + + # Read config ########################################################## + + def config_h_get(varname: str) -> int | None: + if config_h_fname: + with open(config_h_fname, "r", encoding="utf-8") as fh: + for line in fh: + line = line.rstrip() + if line.startswith("#define"): + parts = line.split() + if parts[1] == varname: + return int(parts[2]) + return None + + self._CONFIG_9P_NUM_SOCKS = config_h_get("_CONFIG_9P_NUM_SOCKS") + self.CONFIG_9P_SRV_MAX_REQS = config_h_get("CONFIG_9P_SRV_MAX_REQS") + self.CONFIG_9P_SRV_MAX_DEPTH = config_h_get("CONFIG_9P_SRV_MAX_DEPTH") + + # Read sources ######################################################### + + tmessage_handlers: set[QName] | None = None + if lib9p_srv_c_fname: + tmessage_handlers = set() + with open(lib9p_srv_c_fname, "r", encoding="utf-8") as fh: + for line in fh: + line = line.rstrip() + if m := re_tmessage_handler.fullmatch(line): + tmessage_handlers.add(QName(m.group("handler"))) + self.tmessage_handlers = tmessage_handlers + + lib9p_msgs: set[str] = set() + if lib9p_generated_c_fname: + with open(lib9p_generated_c_fname, "r", encoding="utf-8") as fh: + for line in fh: + line = line.rstrip() + if m := re_lib9p_msg_entry.fullmatch(line): + typ = m.group("typ") + lib9p_msgs.add(typ) + self.lib9p_msgs = lib9p_msgs + + def thread_count(self, name: QName) -> int: + assert self._CONFIG_9P_NUM_SOCKS + assert self.CONFIG_9P_SRV_MAX_REQS + if "read" in str(name.base()): + return self._CONFIG_9P_NUM_SOCKS + if "write" in str(name.base()): + return self._CONFIG_9P_NUM_SOCKS * self.CONFIG_9P_SRV_MAX_REQS + return 1 + + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + if "/3rd-party/" in loc: + return None + if ( + self.tmessage_handlers + and "/srv.c:" in loc + and "tmessage_handlers[typ](" in line + ): + # Functions for disabled protocol extensions will be missing. + return self.tmessage_handlers, True + if self.lib9p_msgs and "/9p.c:" in loc: + for meth in ["validate", "unmarshal", "marshal"]: + if line.startswith(f"tentry.{meth}("): + # Functions for disabled protocol extensions will be missing. + return [QName(f"{meth}_{msg}") for msg in self.lib9p_msgs], True + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + ret: dict[BaseName, analyze.SkipModel] = { + BaseName("_lib9p_validate"): analyze.SkipModel( + 2, + self._skipmodel__lib9p_validate_unmarshal_marshal, + ), + BaseName("_lib9p_unmarshal"): analyze.SkipModel( + 2, + self._skipmodel__lib9p_validate_unmarshal_marshal, + ), + BaseName("_lib9p_marshal"): analyze.SkipModel( + 2, + self._skipmodel__lib9p_validate_unmarshal_marshal, + ), + BaseName("_vfctprintf"): analyze.SkipModel( + self.formatters, self._skipmodel__vfctprintf + ), + } + if isinstance(self.CONFIG_9P_SRV_MAX_DEPTH, int): + ret[BaseName("srv_util_pathfree")] = analyze.SkipModel( + self.CONFIG_9P_SRV_MAX_DEPTH, + self._skipmodel_srv_util_pathfree, + ) + return ret + + def _skipmodel__lib9p_validate_unmarshal_marshal( + self, chain: typing.Sequence[QName], call: QName + ) -> bool: + m_caller = re_lib9p_caller.fullmatch(str(chain[-2].base())) + assert m_caller + + m_callee = re_lib9p_callee.fullmatch(str(call.base())) + if not m_callee: + return False + return m_caller.group("grp") != m_callee.group("grp") + + def _skipmodel_srv_util_pathfree( + self, chain: typing.Sequence[QName], call: QName + ) -> bool: + assert isinstance(self.CONFIG_9P_SRV_MAX_DEPTH, int) + if call.base() == BaseName("srv_util_pathfree"): + return len(chain) >= self.CONFIG_9P_SRV_MAX_DEPTH and all( + c.base() == BaseName("srv_util_pathfree") + for c in chain[-self.CONFIG_9P_SRV_MAX_DEPTH :] + ) + return False + + def _skipmodel__vfctprintf( + self, chain: typing.Sequence[QName], call: QName + ) -> bool: + if call.base() == BaseName("libfmt_conv_formatter"): + return any(c.base() in self.formatters for c in chain) + return False + + +class LibMiscPlugin: + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return { + BaseName("__assert_msg_fail"): analyze.SkipModel( + {BaseName("__assert_msg_fail")}, self._skipmodel___assert_msg_fail + ), + } + + def _skipmodel___assert_msg_fail( + self, chain: typing.Sequence[QName], call: QName + ) -> bool: + if call.base() in [BaseName("__lm_printf"), BaseName("__lm_light_printf")]: + return any( + c.base() == BaseName("__assert_msg_fail") for c in reversed(chain[:-1]) + ) + return False + + +class PicoFmtPlugin: + known_fct: dict[BaseName, BaseName] + + def __init__(self, arg_pico_platform: str) -> None: + self.known_fct = { + # pico_fmt + BaseName("fmt_vsnprintf"): BaseName("_out_buffer"), + } + match arg_pico_platform: + case "rp2040": + self.known_fct.update( + { + # pico_stdio + BaseName("__wrap_vprintf"): BaseName("stdio_buffered_printer"), + BaseName("stdio_vprintf"): BaseName("stdio_buffered_printer"), + # libfmt + BaseName("__lm_light_printf"): BaseName("libfmt_light_fct"), + } + ) + case "host": + self.known_fct.update( + { + # libfmt + BaseName("__lm_printf"): BaseName("libfmt_libc_fct"), + BaseName("__lm_light_printf"): BaseName("libfmt_libc_fct"), + } + ) + + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + if "/3rd-party/pico-fmt/" not in loc: + return None + if "/printf.c:" in loc: + m = util.re_call_other.fullmatch(line) + call: str | None = m.group("func") if m else None + if "->fct" in line: + return [x.as_qname() for x in self.known_fct.values()], False + if "specifier_table" in line: + return [ + # pico-fmt + QName("conv_sint"), + QName("conv_uint"), + # QName("conv_double"), + QName("conv_char"), + QName("conv_str"), + QName("conv_ptr"), + QName("conv_pct"), + # libfmt + QName("libfmt_conv_formatter"), + QName("libfmt_conv_quote"), + ], False + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + ret: dict[BaseName, analyze.SkipModel] = { + BaseName("fmt_state_putchar"): analyze.SkipModel( + self.known_fct.keys(), self._skipmodel_fmt_state_putchar + ), + } + return ret + + def _skipmodel_fmt_state_putchar( + self, chain: typing.Sequence[QName], call: QName + ) -> bool: + if call.base() in self.known_fct.values(): + fct: BaseName | None = None + for pcall in reversed(chain): + if pcall.base() in self.known_fct: + fct = self.known_fct[pcall.base()] + return call.base() != fct + return True + return False + + +class PicoSDKPlugin: + get_init_array: typing.Callable[[], typing.Collection[QName]] + app_init_array: typing.Collection[QName] | None + app_preinit_array: typing.Collection[QName] + + def __init__( + self, + *, + get_init_array: typing.Callable[[], typing.Collection[QName]], + ) -> None: + # grep for '__attribute__((constructor))' / '[[gnu::constructor]]'. + self.get_init_array = get_init_array + self.app_init_array = None + + # git grep '^PICO_RUNTIME_INIT_FUNC\S*(' + self.app_preinit_array = [ + # QName("runtime_init_mutex"), # pico_mutex + # QName("runtime_init_default_alarm_pool"), # pico_time + # QName("runtime_init_boot_locks_reset"), # hardware_boot_lock + QName("runtime_init_per_core_irq_priorities"), # hardware_irq + # QName("spinlock_set_extexclall"), # hardware_sync_spin_lock + QName("__aeabi_bits_init"), # pico_bit_ops + # QName("runtime_init_bootrom_locking_enable"), # pico_bootrom, rp2350-only + # QName("runtime_init_pre_core_tls_setup"), # pico_clib_interface, picolibc-only + # QName("__aeabi_double_init"), # pico_double + # QName("__aeabi_float_init"), # pico_float + QName("__aeabi_mem_init"), # pico_mem_ops + QName("first_per_core_initializer"), # pico_runtime + # pico_runtime_init + # QName("runtime_init_bootrom_reset"), # rp2350-only + # QName("runtime_init_per_core_bootrom_reset"), # rp2350-only + # QName("runtime_init_per_core_h3_irq_registers"), # rp2350-only + QName("runtime_init_early_resets"), + QName("runtime_init_usb_power_down"), + # QName("runtime_init_per_core_enable_coprocessors"), # PICO_RUNTIME_SKIP_INIT_PER_CORE_ENABLE_COPROCESSORS + QName("runtime_init_clocks"), + QName("runtime_init_post_clock_resets"), + QName("runtime_init_rp2040_gpio_ie_disable"), + QName("runtime_init_spin_locks_reset"), + QName("runtime_init_install_ram_vector_table"), + ] + + def is_intrhandler(self, name: QName) -> bool: + return name.base() in [ + BaseName("isr_invalid"), + BaseName("isr_nmi"), + BaseName("isr_hardfault"), + BaseName("isr_svcall"), + BaseName("isr_pendsv"), + BaseName("isr_systick"), + *[BaseName(f"isr_irq{n}") for n in range(32)], + ] + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + if "/3rd-party/pico-sdk/" not in loc or "/3rd-party/pico-sdk/lib/" in loc: + return None + m = util.re_call_other.fullmatch(line) + call: str | None = m.group("func") if m else None + + match call: + case "connect_internal_flash_func": + return [ + QName("rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH)") + ], False + case "flash_exit_xip_func": + return [QName("rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP)")], False + case "flash_range_erase_func": + return [QName("rom_func_lookup(ROM_FUNC_FLASH_RANGE_ERASE)")], False + case "flash_flush_cache_func": + return [QName("rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE)")], False + case "rom_table_lookup": + return [QName("rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)")], False + if "/flash.c:" in loc and "boot2_copyout" in line: + return [QName("_stage2_boot")], False + if "/stdio.c:" in loc: + if call == "out_func": + return [ + QName("stdio_out_chars_crlf"), + QName("stdio_out_chars_no_crlf"), + ], False + if call and (call.startswith("d->") or call.startswith("driver->")): + _, meth = call.split("->", 1) + match meth: + case "out_chars": + return [QName("stdio_uart_out_chars")], False + case "out_flush": + return [QName("stdio_uart_out_flush")], False + case "in_chars": + return [QName("stdio_uart_in_chars")], False + if "/newlib_interface.c:" in loc: + if line == "*p)();": + if self.app_init_array is None: + self.app_init_array = self.get_init_array() + return self.app_init_array, False + if "/pico_runtime/runtime.c:" in loc: + return self.app_preinit_array, False + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} + + def extra_nodes(self) -> typing.Collection[Node]: + ret = [] + + # src/rp2_common/hardware_divider/include/hardware/divider_helper.S + save_div_state_and_lr = 5 * 4 + # src/rp2_common/pico_divider/divider_hardware.S + save_div_state_and_lr_64 = 5 * 4 + + # src/src/rp2_common/pico_crt0/crt0.S + for n in range(32): + ret += [synthetic_node(f"isr_irq{n}", 0, {"__unhandled_user_irq"})] + ret += [ + synthetic_node("isr_invalid", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_nmi", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_hardfault", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_svcall", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_pendsv", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_systick", 0, {"__unhandled_user_irq"}), + synthetic_node("__unhandled_user_irq", 0), + synthetic_node("_entry_point", 0, {"_reset_handler"}), + synthetic_node("_reset_handler", 0, {"runtime_init", "main", "exit"}), + ] + + ret += [ + # src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S + synthetic_node("__wrap___aeabi_lmul", 4), + # src/rp2_common/pico_divider/divider_hardware.S + # s32 aliases + synthetic_node("div_s32s32", 0, {"divmod_s32s32"}), + synthetic_node("__wrap___aeabi_idiv", 0, {"divmod_s32s32"}), + synthetic_node("__wrap___aeabi_idivmod", 0, {"divmod_s32s32"}), + # s32 impl + synthetic_node("divmod_s32s32", 0, {"divmod_s32s32_savestate"}), + synthetic_node( + "divmod_s32s32_savestate", + save_div_state_and_lr, + {"divmod_s32s32_unsafe"}, + ), + synthetic_node("divmod_s32s32_unsafe", 2 * 4, {"__aeabi_idiv0"}), + # u32 aliases + synthetic_node("div_u32u32", 0, {"divmod_u32u32"}), + synthetic_node("__wrap___aeabi_uidiv", 0, {"divmod_u32u32"}), + synthetic_node("__wrap___aeabi_uidivmod", 0, {"divmod_u32u32"}), + # u32 impl + synthetic_node("divmod_u32u32", 0, {"divmod_u32u32_savestate"}), + synthetic_node( + "divmod_u32u32_savestate", + save_div_state_and_lr, + {"divmod_u32u32_unsafe"}, + ), + synthetic_node("divmod_u32u32_unsafe", 2 * 4, {"__aeabi_idiv0"}), + # s64 aliases + synthetic_node("div_s64s64", 0, {"divmod_s64s64"}), + synthetic_node("__wrap___aeabi_ldiv", 0, {"divmod_s64s64"}), + synthetic_node("__wrap___aeabi_ldivmod", 0, {"divmod_s64s64"}), + # s64 impl + synthetic_node("divmod_s64s64", 0, {"divmod_s64s64_savestate"}), + synthetic_node( + "divmod_s64s64_savestate", + save_div_state_and_lr_64 + (2 * 4), + {"divmod_s64s64_unsafe"}, + ), + synthetic_node( + "divmod_s64s64_unsafe", 4, {"divmod_u64u64_unsafe", "__aeabi_ldiv0"} + ), + # u64 aliases + synthetic_node("div_u64u64", 0, {"divmod_u64u64"}), + synthetic_node("__wrap___aeabi_uldivmod", 0, {"divmod_u64u64"}), + # u64 impl + synthetic_node("divmod_u64u64", 0, {"divmod_u64u64_savestate"}), + synthetic_node( + "divmod_u64u64_savestate", + save_div_state_and_lr_64 + (2 * 4), + {"divmod_u64u64_unsafe"}, + ), + synthetic_node( + "divmod_u64u64_unsafe", (1 + 1 + 2 + 5 + 5 + 2) * 4, {"__aeabi_ldiv0"} + ), + # *_rem + synthetic_node("divod_s64s64_rem", 2 * 4, {"divmod_s64s64"}), + synthetic_node("divod_u64u64_rem", 2 * 4, {"divmod_u64u64"}), + # src/rp2_common/pico_mem_ops/mem_ops_aeabi.S + synthetic_node("__aeabi_mem_init", 0, {"rom_funcs_lookup"}), + synthetic_node( + "__wrap___aeabi_memset", 0, {"rom_func_lookup(ROM_FUNC_MEMSET)"} + ), + synthetic_node("__wrap___aeabi_memset4", 0, {"__wrap___aeabi_memset8"}), + synthetic_node( + "__wrap___aeabi_memset8", 0, {"rom_func_lookup(ROM_FUNC_MEMSET4)"} + ), + synthetic_node("__wrap___aeabi_memcpy4", 0, {"__wrap___aeabi_memcpy8"}), + synthetic_node( + "__wrap___aeabi_memcpy7", 0, {"rom_func_lookup(ROM_FUNC_MEMCPY4)"} + ), + synthetic_node("__wrap_memset", 0, {"rom_func_lookup(ROM_FUNC_MEMSET)"}), + synthetic_node("__wrap___aeabi_memcpy", 0, {"__wrap_memcpy"}), + synthetic_node("__wrap_memcpy", 0, {"rom_func_lookup(ROM_FUNC_MEMCPY)"}), + # src/rp2_common/pico_bit_ops/bit_ops_aeabi.S + synthetic_node("__aeabi_bits_init", 0, {"rom_funcs_lookup"}), + synthetic_node("__wrap___clz", 0, {"__wrap___clzsi2"}), + synthetic_node("__wrap___clzl", 0, {"__wrap___clzsi2"}), + synthetic_node("__wrap___clzsi2", 0, {"rom_func_lookup(ROM_FUNC_CLZ32)"}), + synthetic_node("__wrap___ctzsi2", 0, {"rom_func_lookup(ROM_FUNC_CTZ32)"}), + synthetic_node( + "__wrap___popcountsi2", 0, {"rom_func_lookup(ROM_FUNC_POPCOUNT32)"} + ), + synthetic_node("__wrap___clzll", 0, {"__wrap___clzdi2"}), + synthetic_node("__wrap___clzdi2", 4, {"rom_func_lookup(ROM_FUNC_CLZ32)"}), + synthetic_node("__wrap___ctzdi2", 4, {"rom_func_lookup(ROM_FUNC_CTZ32)"}), + synthetic_node( + "__wrap___popcountdi2", 3 * 4, {"rom_func_lookup(ROM_FUNC_POPCOUNT32)"} + ), + synthetic_node("__rev", 0, {"reverse32"}), + synthetic_node("__revl", 0, {"reverse32"}), + synthetic_node("reverse32", 0, {"rom_func_lookup(ROM_FUNC_REVERSE32)"}), + synthetic_node("__revll", 0, {"reverse64"}), + synthetic_node("reverse64", 3 * 4, {"rom_func_lookup(ROM_FUNC_REVERSE32)"}), + # src/rp2040/boot_stage2/boot2_${name,,}.S for name=W25Q080, + # controlled by `#define PICO_BOOT_STAGE2_{name} 1` in + # src/boards/include/boards/pico.h + # synthetic_node("_stage2_boot", 0), # TODO + # https://github.com/raspberrypi/pico-bootrom-rp2040 + # synthetic_node("rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH)", 0), # TODO + # synthetic_node("rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP)", 0), # TODO + # synthetic_node("rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE)", 0), # TODO + # synthetic_node("rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)", 0), # TODO + ] + return ret + + +re_tud_class = re.compile( + r"^\s*#\s*define\s+(?P<k>CFG_TUD_(?:\S{3}|AUDIO|VIDEO|MIDI|VENDOR|USBTMC|DFU_RUNTIME|ECM_RNDIS))\s+(?P<v>\S+).*" +) +re_tud_entry = re.compile(r"^\s+\.(?P<meth>\S+)\s*=\s*(?P<impl>[a-zA-Z0-9_]+)(?:,.*)?") +re_tud_if1 = re.compile(r"^\s*#\s*if (\S+)\s*") +re_tud_if2 = re.compile(r"^\s*#\s*if (\S+)\s*\|\|\s*(\S+)\s*") +re_tud_endif = re.compile(r"^\s*#\s*endif\s*") + + +class TinyUSBDevicePlugin: + tud_drivers: dict[str, set[QName]] # method_name => {method_impls} + + def __init__(self, arg_c_fnames: typing.Collection[str]) -> None: + usbd_c_fname = util.get_zero_or_one( + lambda fname: fname.endswith("/tinyusb/src/device/usbd.c"), arg_c_fnames + ) + + tusb_config_h_fname = util.get_zero_or_one( + lambda fname: fname.endswith("/tusb_config.h"), arg_c_fnames + ) + + if not usbd_c_fname: + self.tud_drivers = {} + return + + assert tusb_config_h_fname + tusb_config: dict[str, bool] = {} + with open(tusb_config_h_fname, "r", encoding="utf-8") as fh: + in_table = False + for line in fh: + line = line.rstrip() + if m := re_tud_class.fullmatch(line): + k = m.group("k") + v = m.group("v") + tusb_config[k] = bool(int(v)) + + tud_drivers: dict[str, set[QName]] = {} + with open(usbd_c_fname, "r", encoding="utf-8") as fh: + in_table = False + enabled = True + for line in fh: + line = line.rstrip() + if in_table: + if m := re_tud_if1.fullmatch(line): + enabled = tusb_config[m.group(1)] + elif m := re_tud_if2.fullmatch(line): + enabled = tusb_config[m.group(1)] or tusb_config[m.group(2)] + elif re_tud_endif.fullmatch(line): + enabled = True + if m := re_tud_entry.fullmatch(line): + meth = m.group("meth") + impl = m.group("impl") + if meth == "name" or not enabled: + continue + if meth not in tud_drivers: + tud_drivers[meth] = set() + if impl != "NULL": + tud_drivers[meth].add(QName(impl)) + if line.startswith("}"): + in_table = False + elif " _usbd_driver[] = {" in line: + in_table = True + self.tud_drivers = tud_drivers + + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + return [] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + if "/tinyusb/" not in loc or "/tinyusb/src/host/" in loc or "_host.c:" in loc: + return None + m = util.re_call_other.fullmatch(line) + assert m + call = m.group("func") + if call == "_ctrl_xfer.complete_cb": + ret = { + # QName("process_test_mode_cb"), + QName("tud_vendor_control_xfer_cb"), + } + ret.update(self.tud_drivers["control_xfer_cb"]) + return ret, False + if call.startswith("driver->"): + return self.tud_drivers[call[len("driver->") :]], False + if call == "event.func_call.func": + # callback from usb_defer_func() + return [], False + + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} + + +class NewlibPlugin: + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [QName("register_fini")] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [ + # register_fini() calls atexit(__libc_fini_array) + BaseName("__libc_fini_array"), + ] + + def extra_nodes(self) -> typing.Collection[Node]: + # This is accurate to + # /usr/arm-none-eabi/lib/thumb/v6-m/nofp/libg.a as of + # Parabola's arm-none-eabi-newlib 4.5.0.20241231-1. + return [ + # malloc + synthetic_node("free", 8, {"_free_r"}), + synthetic_node("malloc", 8, {"_malloc_r"}), + synthetic_node("realloc", 8, {"_realloc_r"}), + synthetic_node("aligned_alloc", 8, {"_memalign_r"}), + synthetic_node("reallocarray", 24, {"realloc", "__errno"}), + # synthetic_node("_free_r", 0), # TODO + # synthetic_node("_malloc_r", 0), # TODO + # synthetic_node("_realloc_r", 0), # TODO + # synthetic_node("_memalign_r", 0), # TODO + # execution + synthetic_node("raise", 16, {"_getpid_r"}), + synthetic_node("abort", 8, {"raise", "_exit"}), + synthetic_node("longjmp", 0), + synthetic_node("setjmp", 0), + # <strings.h> + synthetic_node("memcmp", 12), + synthetic_node("memcpy", 28), + synthetic_node("memset", 20), + synthetic_node("strcmp", 16), + synthetic_node("strlen", 8), + synthetic_node("strncpy", 16), + synthetic_node("strnlen", 8), + # other + synthetic_node("__errno", 0), + synthetic_node("_getpid_r", 8, {"_getpid"}), + synthetic_node("random", 8), + synthetic_node("register_fini", 8, {"atexit"}), + synthetic_node("atexit", 8, {"__register_exitproc"}), + synthetic_node( + "__register_exitproc", + 32, + { + "__retarget_lock_acquire_recursive", + "__retarget_lock_release_recursive", + }, + ), + synthetic_node("__libc_fini_array", 16, {"_fini"}), + ] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} + + +class LibGCCPlugin: + def is_intrhandler(self, name: QName) -> bool: + return False + + def init_array(self) -> typing.Collection[QName]: + return [ + QName("libfmt_install_formatter"), + QName("libfmt_install_quote"), + ] + + def extra_includes(self) -> typing.Collection[BaseName]: + return [] + + def extra_nodes(self) -> typing.Collection[Node]: + # This is accurate to Parabola's arm-none-eabi-gcc 14.2.0-1. + return [ + # /usr/lib/gcc/arm-none-eabi/14.2.0/thumb/v6-m/nofp/libgcc.a + synthetic_node("__aeabi_idiv0", 0), + synthetic_node("__aeabi_ldiv0", 0), + synthetic_node("__aeabi_llsr", 0), + # /usr/lib/gcc/arm-none-eabi/14.2.0/thumb/v6-m/nofp/crti.o + synthetic_node("_fini", 24), + ] + + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: + return None + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + return {} diff --git a/build-aux/measurestack/test_analyze.py b/build-aux/measurestack/test_analyze.py new file mode 100644 index 0000000..ff1732d --- /dev/null +++ b/build-aux/measurestack/test_analyze.py @@ -0,0 +1,34 @@ +# build-aux/measurestack/test_analyze.py - Tests for analyze.py +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +# pylint: disable=unused-variable + +import pytest + +from .analyze import BaseName, QName + + +def test_name_base() -> None: + assert QName("foo.c:bar.1").base() == BaseName("bar") + + +def test_name_pretty() -> None: + name = QName("foo.c:bar.1") + assert f"{name}" == "QName('foo.c:bar.1')" + assert f"{name.base()}" == "BaseName('bar')" + assert f"{[name]}" == "[QName('foo.c:bar.1')]" + assert f"{[name.base()]}" == "[BaseName('bar')]" + + +def test_name_eq() -> None: + name = QName("foo.c:bar.1") + with pytest.raises(AssertionError) as e: + if name == "foo": + pass + assert "comparing QName with str" in str(e) + with pytest.raises(AssertionError) as e: + if name.base() == "foo": + pass + assert "comparing BaseName with str" in str(e) diff --git a/build-aux/measurestack/test_app_output.py b/build-aux/measurestack/test_app_output.py new file mode 100644 index 0000000..4653d4e --- /dev/null +++ b/build-aux/measurestack/test_app_output.py @@ -0,0 +1,52 @@ +# build-aux/measurestack/test_app_output.py - Tests for app_output.py +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +# pylint: disable=unused-variable + +import contextlib +import io + +from . import analyze, app_output + + +def test_print_group() -> None: + result = analyze.AnalyzeResult( + groups={ + "A": analyze.AnalyzeResultGroup( + rows={ + analyze.QName("short"): analyze.AnalyzeResultVal(nstatic=8, cnt=1), + analyze.QName( + "anamethatisnttoolongbutisnttooshort" + ): analyze.AnalyzeResultVal(nstatic=9, cnt=2), + } + ), + "B": analyze.AnalyzeResultGroup(rows={}), + }, + missing=set(), + dynamic=set(), + included_funcs=set(), + ) + + def location_xform(loc: analyze.QName) -> str: + return str(loc) + + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + print() + app_output.print_group(result, location_xform, "A") + app_output.print_group(result, location_xform, "B") + assert ( + stdout.getvalue() + == """ += A =============================== == +anamethatisnttoolongbutisnttooshort 9 * 2 +short 8 +----------------------------------- -- +Total 26 +Maximum 9 +=================================== == += B (empty) = +""" + ) diff --git a/build-aux/measurestack/util.py b/build-aux/measurestack/util.py new file mode 100644 index 0000000..47b2617 --- /dev/null +++ b/build-aux/measurestack/util.py @@ -0,0 +1,125 @@ +# build-aux/measurestack/util.py - Analyze stack sizes for compiled objects +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import re +import typing + +from . import analyze, vcg +from .analyze import BaseName, Node, QName + +# pylint: disable=unused-variable +__all__ = [ + "synthetic_node", + "read_source", + "get_zero_or_one", + "re_call_other", + "Plugin", + "PluginApplication", +] + + +def synthetic_node( + name: str, nstatic: int, calls: typing.Collection[str] = frozenset() +) -> Node: + n = Node() + + n.funcname = QName(name) + + n.location = "<synthetic>" + n.usage_kind = "static" + n.nstatic = nstatic + n.ndynamic = 0 + + n.calls = dict((QName(c), False) for c in calls) + + return n + + +re_location = re.compile(r"(?P<filename>.+):(?P<row>[0-9]+):(?P<col>[0-9]+)") + + +def read_source(location: str) -> str: + m = re_location.fullmatch(location) + if not m: + raise ValueError(f"unexpected label value {location!r}") + filename = m.group("filename") + row = int(m.group("row")) - 1 + col = int(m.group("col")) - 1 + with open(filename, "r", encoding="utf-8") as fh: + return fh.readlines()[row][col:].rstrip() + + +def get_zero_or_one( + pred: typing.Callable[[str], bool], fnames: typing.Collection[str] +) -> str | None: + count = sum(1 for fname in fnames if pred(fname)) + assert count < 2 + if count: + return next(fname for fname in fnames if pred(fname)) + return None + + +re_call_other = re.compile(r"(?P<func>[^(]+)\(.*") + + +class Plugin(typing.Protocol): + def is_intrhandler(self, name: QName) -> bool: ... + + # init_array returns a list of functions that are placed in the + # `.init_array.*` section; AKA functions marked with + # `__attribute__((constructor))`. + def init_array(self) -> typing.Collection[QName]: ... + + # extra_includes returns a list of functions that are never + # called, but are included in the binary anyway. This may because + # it is an unused method in a used vtable. This may be because it + # is an atexit() callback (we never exit). + def extra_includes(self) -> typing.Collection[BaseName]: ... + + def extra_nodes(self) -> typing.Collection[Node]: ... + def indirect_callees( + self, loc: str, line: str + ) -> tuple[typing.Collection[QName], bool] | None: ... + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: ... + + +class PluginApplication: + _location_xform: typing.Callable[[str], str] + _plugins: list[Plugin] + + def __init__( + self, location_xform: typing.Callable[[str], str], plugins: list[Plugin] + ) -> None: + self._location_xform = location_xform + self._plugins = plugins + + def extra_nodes(self) -> typing.Collection[Node]: + ret: list[Node] = [] + for plugin in self._plugins: + ret.extend(plugin.extra_nodes()) + return ret + + def indirect_callees( + self, elem: vcg.VCGElem + ) -> tuple[typing.Collection[QName], bool]: + loc = elem.attrs.get("label", "") + line = read_source(loc) + + for plugin in self._plugins: + ret = plugin.indirect_callees(loc, line) + if ret is not None: + return ret + + placeholder = "__indirect_call" + if m := re_call_other.fullmatch(line): + placeholder += ":" + m.group("func") + placeholder += " at " + self._location_xform(elem.attrs.get("label", "")) + return [QName(placeholder)], False + + def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: + ret: dict[BaseName, analyze.SkipModel] = {} + for plugin in self._plugins: + ret.update(plugin.skipmodels()) + return ret diff --git a/build-aux/measurestack/vcg.py b/build-aux/measurestack/vcg.py new file mode 100644 index 0000000..39755e9 --- /dev/null +++ b/build-aux/measurestack/vcg.py @@ -0,0 +1,97 @@ +# build-aux/measurestack/vcg.py - Parse the "VCG" language +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import re +import typing + +# pylint: disable=unused-variable +__all__ = [ + "VCGElem", + "parse_vcg", +] + +# Parse the "VCG" language +# +# https://www.rw.cdl.uni-saarland.de/people/sander/private/html/gsvcg1.html +# +# The formal syntax is found at +# ftp://ftp.cs.uni-sb.de/pub/graphics/vcg/vcg.tgz `doc/grammar.txt`. + + +class VCGElem: + typ: str + lineno: int + attrs: dict[str, str] + + +re_beg = re.compile(r"(edge|node):\s*\{\s*") +_re_tok = r"[a-zA-Z_][a-zA-Z0-9_]*" +_re_str = r'"(?:[^\"]|\\.)*"' +re_attr = re.compile("(" + _re_tok + r")\s*:\s*(" + _re_tok + "|" + _re_str + r")\s*") +re_end = re.compile(r"\}\s*$") +re_skip = re.compile(r"(graph:\s*\{\s*title\s*:\s*" + _re_str + r"\s*|\})\s*") +re_esc = re.compile(r"\\.") + + +def parse_vcg(reader: typing.TextIO) -> typing.Iterator[VCGElem]: + + for lineno, line in enumerate(reader): + pos = 0 + + def _raise(msg: str) -> typing.NoReturn: + nonlocal lineno + nonlocal line + nonlocal pos + e = SyntaxError(msg) + e.lineno = lineno + e.offset = pos + e.text = line + raise e + + if re_skip.fullmatch(line): + continue + + elem = VCGElem() + elem.lineno = lineno + + m = re_beg.match(line, pos=pos) + if not m: + _raise("does not look like a VCG line") + elem.typ = m.group(1) + pos = m.end() + + elem.attrs = {} + while True: + if re_end.match(line, pos=pos): + break + m = re_attr.match(line, pos=pos) + if not m: + _raise("unexpected character") + k = m.group(1) + v = m.group(2) + if k in elem.attrs: + _raise(f"duplicate key: {k!r}") + if v.startswith('"'): + + def unesc(esc: re.Match[str]) -> str: + match esc.group(0)[1:]: + case "n": + return "\n" + case '"': + return '"' + case "\\": + return "\\" + case _: + _raise(f"invalid escape code {esc.group(0)!r}") + + v = re_esc.sub(unesc, v[1:-1]) + elem.attrs[k] = v + pos = m.end() + + del _raise + del pos + del line + del lineno + yield elem diff --git a/build-aux/requirements.txt b/build-aux/requirements.txt index 03c0b3d..e6044e0 100644 --- a/build-aux/requirements.txt +++ b/build-aux/requirements.txt @@ -1,9 +1,11 @@ # build-aux/requirements.txt - List of Python dev requirements # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later mypy -types-gdb +types-gdb>=15.0.0.20241204 # https://github.com/python/typeshed/pull/13169 black isort +pylint +pytest diff --git a/build-aux/stack.c.gen b/build-aux/stack.c.gen index 06612ac..713630a 100755 --- a/build-aux/stack.c.gen +++ b/build-aux/stack.c.gen @@ -1,273 +1,14 @@ #!/usr/bin/env python3 # build-aux/stack.c.gen - Analyze stack sizes for compiled objects # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later -import re +import os.path import sys -import typing - -################################################################################ -# -# Parse the "VCG" language -# -# https://www.rw.cdl.uni-saarland.de/people/sander/private/html/gsvcg1.html -# -# The formal syntax is found at -# ftp://ftp.cs.uni-sb.de/pub/graphics/vcg/vcg.tgz `doc/grammar.txt`. - - -class VCGElem: - typ: str - lineno: int - attrs: dict[str, str] - - -def parse_vcg(reader: typing.TextIO) -> typing.Iterator[VCGElem]: - re_beg = re.compile(r"(edge|node):\s*\{\s*") - _re_tok = r"[a-zA-Z_][a-zA-Z0-9_]*" - _re_str = r'"(?:[^\"]|\\.)*"' - re_attr = re.compile( - "(" + _re_tok + r")\s*:\s*(" + _re_tok + "|" + _re_str + r")\s*" - ) - re_end = re.compile(r"\}\s*$") - re_skip = re.compile(r"(graph:\s*\{\s*title\s*:\s*" + _re_str + r"\s*|\})\s*") - re_esc = re.compile(r"\\.") - - for lineno, line in enumerate(reader): - pos = 0 - - def _raise(msg: str) -> typing.NoReturn: - nonlocal lineno - nonlocal line - nonlocal pos - e = SyntaxError(msg) - e.lineno = lineno - e.offset = pos - e.text = line - raise e - - if re_skip.fullmatch(line): - continue - - elem = VCGElem() - elem.lineno = lineno - - m = re_beg.match(line, pos=pos) - if not m: - _raise("does not look like a VCG line") - elem.typ = m.group(1) - pos = m.end() - - elem.attrs = {} - while True: - if re_end.match(line, pos=pos): - break - m = re_attr.match(line, pos=pos) - if not m: - _raise("unexpected character") - k = m.group(1) - v = m.group(2) - if k in elem.attrs: - _raise(f"duplicate key: {repr(k)}") - if v.startswith('"'): - - def unesc(esc: re.Match[str]) -> str: - match esc.group(0)[1:]: - case "n": - return "\n" - case '"': - return '"' - case "\\": - return "\\" - case _: - _raise(f"invalid escape code {repr(esc.group(0))}") - - v = re_esc.sub(unesc, v[1:-1]) - elem.attrs[k] = v - pos = m.end() - - yield elem - - -################################################################################ -# Main application - - -class Node: - # from .title (`static` and `__weak` functions are prefixed with - # the compilation unit .c file. For static functions that's fine, - # but we'll have to handle it specially for __weak.). - funcname: str - # .label is "{funcname}\n{location}\n{nstatic} bytes (static}\n{ndynamic} dynamic objects" - location: str - nstatic: int - ndynamic: int - - # edges with .sourcename set to this node - calls: set[str] - - -re_location = re.compile(r"(?P<filename>.+):(?P<row>[0-9]+):(?P<col>[0-9]+)") - - -def read_source(location: str) -> str: - m = re_location.fullmatch(location) - if not m: - raise ValueError(f"unexpected label value {repr(location)}") - filename = m.group("filename") - row = int(m.group("row")) - 1 - col = int(m.group("col")) - 1 - with open(m.group("filename"), "r") as fh: - return fh.readlines()[row][col:].rstrip() - - -def main(ci_fnames: list[str]) -> None: - re_node_label = re.compile( - r"(?P<funcname>[^\n]+)\n" - + r"(?P<location>[^\n]+:[0-9]+:[0-9]+)\n" - + r"(?P<nstatic>[0-9]+) bytes \(static\)\n" - + r"(?P<ndynamic>[0-9]+) dynamic objects", - flags=re.MULTILINE, - ) - re_call_vcall = re.compile(r"VCALL\((?P<obj>[^,]+), (?P<meth>[^,)]+)[,)].*") - re_call_other = re.compile(r"(?P<func>[^(]+)\(.*") - - graph: dict[str, Node] = dict() - qualified: dict[str, set[str]] = dict() - - def handle_elem(elem: VCGElem) -> None: - match elem.typ: - case "node": - node = Node() - node.calls = set() - skip = False - for k, v in elem.attrs.items(): - match k: - case "title": - node.funcname = v - case "label": - if elem.attrs.get("shape", "") != "ellipse": - m = re_node_label.fullmatch(v) - if not m: - raise ValueError( - f"unexpected label value {repr(v)}" - ) - node.location = m.group("location") - node.nstatic = int(m.group("nstatic")) - node.ndynamic = int(m.group("ndynamic")) - case "shape": - if v != "ellipse": - raise ValueError(f"unexpected shape value {repr(v)}") - skip = True - case _: - raise ValueError(f"unknown edge key {repr(k)}") - if not skip: - if node.funcname in graph: - raise ValueError(f"duplicate node {repr(node.funcname)}") - graph[node.funcname] = node - if ":" in node.funcname: - _, shortname = node.funcname.rsplit(":", 1) - if shortname not in qualified: - qualified[shortname] = set() - qualified[shortname].add(node.funcname) - case "edge": - caller: str | None = None - callee: str | None = None - for k, v in elem.attrs.items(): - match k: - case "sourcename": - caller = v - case "targetname": - callee = v - case "label": - pass - case _: - raise ValueError(f"unknown edge key {repr(k)}") - if caller is None or callee is None: - raise ValueError(f"incomplete edge: {repr(elem.attrs)}") - if caller not in graph: - raise ValueError(f"unknown caller: {caller}") - if callee == "__indirect_call": - callstr = read_source(elem.attrs.get("label", "")) - if m := re_call_vcall.fullmatch(callstr): - callee += f":{m.group('obj')}->vtable->{m.group('meth')}" - elif m := re_call_other.fullmatch(callstr): - callee += f":{m.group('func')}" - else: - callee += f':{elem.attrs.get("label", "")}' - graph[caller].calls.add(callee) - case _: - raise ValueError(f"unknown elem type {repr(elem.typ)}") - - for ci_fname in ci_fnames: - with open(ci_fname, "r") as fh: - for elem in parse_vcg(fh): - handle_elem(elem) - - missing: set[str] = set() - cycles: set[str] = set() - - print("/*") - - dbg = False - - def nstatic(funcname: str, chain: list[str] = []) -> int: - nonlocal dbg - if funcname not in graph: - if f"__wrap_{funcname}" in graph: - # Handle `ld --wrap` functions - funcname = f"__wrap_{funcname}" - elif funcname in qualified and len(qualified[funcname]) == 1: - # Handle `__weak` functions - funcname = sorted(qualified[funcname])[0] - else: - missing.add(funcname) - return 0 - if funcname in chain: - if "__assert_msg_fail" in chain: - if funcname == "__wrap_printf": - return 0 - pass - else: - cycles.add(f"{chain[chain.index(funcname):] + [funcname]}") - return 9999999 - node = graph[funcname] - if dbg: - print(f"//dbg: {funcname}\t{node.nstatic}") - return node.nstatic + max( - [0, *[nstatic(call, chain + [funcname]) for call in node.calls]] - ) - - def thread_filter(name: str) -> bool: - return name.endswith("_cr") or name == "main" - - namelen = max(len(name) for name in graph if thread_filter(name)) - numlen = max(len(str(nstatic(name))) for name in graph if name.endswith("_cr")) - print(("=" * namelen) + " " + "=" * numlen) - - for funcname in graph: - if thread_filter(funcname): - # dbg = "dhcp" in funcname - print(f"{funcname.ljust(namelen)} {str(nstatic(funcname)).rjust(numlen)}") - - print(("=" * namelen) + " " + "=" * numlen) - - for funcname in sorted(missing): - print(f"warning: missing: {funcname}") - for cycle in sorted(cycles): - print(f"warning: cycle: {cycle}") - - print("*/") +sys.path.insert(0, os.path.normpath(os.path.join(__file__, ".."))) +import measurestack # pylint: disable=wrong-import-position if __name__ == "__main__": - re_suffix = re.compile(r"\.c\.o(bj)?$") - main( - [ - re_suffix.sub(".c.ci", fname) - for fname in sys.argv[1:] - if re_suffix.search(fname) - ] - ) + measurestack.main() diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt index 6199e0c..678af07 100644 --- a/cmd/sbc_harness/CMakeLists.txt +++ b/cmd/sbc_harness/CMakeLists.txt @@ -1,6 +1,6 @@ # cmd/sbc_harness/CMakeLists.txt - Build script for main sbc_harness.uf2 firmware file # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later if (PICO_PLATFORM STREQUAL "rp2040") @@ -10,23 +10,42 @@ if (PICO_PLATFORM STREQUAL "rp2040") add_library(sbc_harness_objs OBJECT main.c usb_keyboard.c + tusb_log.c + + fs_harness_flash_bin.c + fs_harness_uptime_txt.c ) target_include_directories(sbc_harness_objs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config) target_include_directories(sbc_harness_objs PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(sbc_harness_objs PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(sbc_harness_objs - pico_stdlib + pico_runtime + pico_stdio_uart + hardware_flash + hardware_watchdog libmisc + libfmt libusb libdhcp - libhw + libhw_cr + lib9p + lib9p_util +) +pico_minimize_runtime(sbc_harness_objs + INCLUDE PRINTF PRINTF_MINIMAL PRINTF_LONG_LONG PRINTF_PTRDIFF_T +) +target_compile_definitions(sbc_harness_objs PRIVATE + #PICO_USE_FASTEST_SUPPORTED_CLOCK=1 + + # Calculated by `./3rd-party/pico-sdk/src/rp2_common/hardware_clocks/scripts/vcocalc.py --cmake-only 170` + PLL_SYS_REFDIV=2 + PLL_SYS_VCO_FREQ_HZ=1530000000 + PLL_SYS_POSTDIV1=3 + PLL_SYS_POSTDIV2=3 + SYS_CLK_HZ=170000000 ) -pico_minimize_runtime(sbc_harness_objs) -pico_enable_stdio_usb(sbc_harness_objs 0) -pico_enable_stdio_uart(sbc_harness_objs 1) -pico_enable_stdio_semihosting(sbc_harness_objs 0) -pico_enable_stdio_rtt(sbc_harness_objs 0) suppress_tinyusb_warnings() @@ -37,10 +56,6 @@ add_stack_analysis(sbc_harness_stack.c sbc_harness_objs) # Link ######################################################################### add_executable(sbc_harness) -set_source_files_properties("$<TARGET_OBJECTS:sbc_harness_objs>" PROPERTIES - EXTERNAL_OBJECT true - GENERATED true -) target_sources(sbc_harness PRIVATE sbc_harness_stack.c "$<TARGET_OBJECTS:sbc_harness_objs>" @@ -48,7 +63,22 @@ target_sources(sbc_harness PRIVATE target_link_libraries(sbc_harness pico_standard_link ) +target_link_options(sbc_harness PRIVATE "$<TARGET_PROPERTY:sbc_harness_objs,LINK_OPTIONS>") pico_add_extra_outputs(sbc_harness) # create .map/.bin/.hex/.uf2 files in addition to .elf pico_set_program_url(sbc_harness "https://git.lukeshu.com/sbc-harness") +# Embed ######################################################################## + +target_embed_sources(sbc_harness_objs sbc_harness static.h + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/agpl-3.0.txt + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/pico-sdk.bsd3.txt + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/printf.mit.txt + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/tinyusb.mit.txt + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/newlib.txt + static/Documentation/harness_rom_bin.txt + static/Documentation/harness_flash_bin.txt + static/Documentation/harness_uptime_txt.txt +) + endif() diff --git a/cmd/sbc_harness/config/config.h b/cmd/sbc_harness/config/config.h index 85170cc..5e7bc06 100644 --- a/cmd/sbc_harness/config/config.h +++ b/cmd/sbc_harness/config/config.h @@ -1,18 +1,21 @@ /* config.h - Compile-time configuration for sbc_harness * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _CONFIG_H_ #define _CONFIG_H_ -/* W5500 **********************************************************************/ +#include <stddef.h> /* for size_t */ -/** - * How many W5500 chips we have. - */ -#define CONFIG_W5500_NUM 1 +#define CONFIG_FLASH_DEBUG 1 + +/* RP2040 *********************************************************************/ + +#define CONFIG_RP2040_SPI_DEBUG 1 /* bool */ + +/* W5500 **********************************************************************/ /** * When allocating an arbitrary local port, what range should it be @@ -25,9 +28,14 @@ #define CONFIG_W5500_LOCAL_PORT_MIN 32768 #define CONFIG_W5500_LOCAL_PORT_MAX 60999 +#define CONFIG_W5500_VALIDATE_SPI 1 /* bool */ +#define CONFIG_W5500_DEBUG 0 /* bool */ +#define CONFIG_W5500_LL_DEBUG 0 /* bool */ + /* 9P *************************************************************************/ -#define CONFIG_9P_PORT 564 +#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */ + /** * This max-msg-size is sized so that a Twrite message can return * 8KiB of data. @@ -45,46 +53,63 @@ * negotiated. In Plan 9 1e it was (8*1024)+128, and was bumped to * (8*1024)+160 in 2e and 3e. */ -#define CONFIG_9P_MAX_MSG_SIZE ((4*1024)+24) +#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24) /** * Maximum host-data-structure size. A message may be larger in * unmarshaled-host-structures than marshaled-net-bytes due to (1) - * struct padding, (2) nul-terminator byes for strings. + * struct padding, (2) array pointers. */ -#define CONFIG_9P_MAX_HOSTMSG_SIZE CONFIG_9P_MAX_MSG_SIZE+16 -#define CONFIG_9P_MAX_FIDS 16 -#define CONFIG_9P_MAX_REQS 2 -#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */ -#define CONFIG_9P_ENABLE_9P2000 1 /* bool */ -#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */ -#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */ +#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16 +#define CONFIG_9P_SRV_MAX_FIDS 16 +#define CONFIG_9P_SRV_MAX_REQS 2 +#define CONFIG_9P_SRV_MAX_DEPTH 3 + +#define CONFIG_9P_ENABLE_9P2000 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_L 0 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_p9p 0 /* bool */ /* DHCP ***********************************************************************/ #define CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP 0 /* bool */ -#define CONFIG_DHCP_DEBUG 0 /* bool */ +#define CONFIG_DHCP_DEBUG 1 /* bool */ #define CONFIG_DHCP_OPT_SIZE 312 /* minimum of 312 */ #define CONFIG_DHCP_SELECTING_NS (5*NS_PER_S) /* USB KEYBOARD ***************************************************************/ +/** + * Which USB port to use for the Root Hub. + * + * The RP2040 only has 1 port, so it's gotta be port #0. + */ +#define CONFIG_USB_COMMON_RHPORT 0 + #define CONFIG_USB_COMMON_DEBUG 1 /* bool */ /* COROUTINE ******************************************************************/ -#define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (2*1024) +extern const size_t CONFIG_COROUTINE_STACK_SIZE_dhcp_cr; +extern const size_t CONFIG_COROUTINE_STACK_SIZE_init_cr; +extern const size_t CONFIG_COROUTINE_STACK_SIZE_lib9p_srv_write_cr; +extern const size_t CONFIG_COROUTINE_STACK_SIZE_read9p_cr; +extern const size_t CONFIG_COROUTINE_STACK_SIZE_usb_common_cr; +extern const size_t CONFIG_COROUTINE_STACK_SIZE_usb_keyboard_cr; +extern const size_t CONFIG_COROUTINE_STACK_SIZE_w5500_irq_cr; #define CONFIG_COROUTINE_NAME_LEN 16 #define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */ #define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */ #define CONFIG_COROUTINE_DEBUG 0 /* bool */ #define CONFIG_COROUTINE_VALGRIND 0 /* bool */ +#define CONFIG_COROUTINE_GDB 1 /* bool */ -#define _CONFIG_9P_NUM_SOCKS 7 -#define CONFIG_COROUTINE_NUM ( \ - 1 /* usb_common */ + \ - 1 /* usb_keyboard */ + \ - CONFIG_W5500_NUM /* irq handler */ + \ - _CONFIG_9P_NUM_SOCKS /* 9P accept()+read() */ + \ - (CONFIG_9P_MAX_REQS*_CONFIG_9P_NUM_SOCKS) /* 9P work+write() */ ) +#define _CONFIG_9P_NUM_SOCKS 3 /* FIXME: bump this back up to 8 */ +#define CONFIG_COROUTINE_NUM ( \ + 1 /* usb_common */ + \ + 1 /* usb_keyboard */ + \ + 1 /* W5500 irq handler */ + \ + _CONFIG_9P_NUM_SOCKS /* 9P accept()+read() */ + \ + (CONFIG_9P_SRV_MAX_REQS*_CONFIG_9P_NUM_SOCKS) /* 9P work+write() */ ) #endif /* _CONFIG_H_ */ diff --git a/cmd/sbc_harness/config/tusb_config.h b/cmd/sbc_harness/config/tusb_config.h index fc963ac..0a6d3e4 100644 --- a/cmd/sbc_harness/config/tusb_config.h +++ b/cmd/sbc_harness/config/tusb_config.h @@ -36,16 +36,35 @@ extern "C" { #endif //-------------------------------------------------------------------- -// TinyUSB Device (TUD) initialization for rp2040-based boards +// Override the default definition of TU_ASSERT() to use our logging //-------------------------------------------------------------------- -// Which USB port to use for the RootHub. -// The rp2040 only has 1 port, so it's gotta be port #0. -#define BOARD_TUD_RHPORT 0 - -// RHPort max operational speed. -// Use OPT_MODE_DEFAULT_SPEED for max speed supported by MCU. -#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +// "magically" select between the 1-arg and 2-args variants, inject a +// stringified version of `_cond`. +// +// Note: Use GNU-C `, ##__VA_ARGS__`, not standard C `__VA_OPT__(,) +// __VA_ARGS__`; because __VA_OPT__ doesn't handle present-but-empty +// arguments the way that we need. +#define TU_ASSERT(_cond, ...) \ + _GET_3RD_ARG(_cond, ##__VA_ARGS__, \ + _LIBMISC_TU_ASSERT_2ARGS, _LIBMISC_TU_ASSERT_1ARGS, _dummy) \ + (_cond, #_cond, ##__VA_ARGS__) + +#define _LIBMISC_TU_ASSERT_1ARGS(_cond, _cond_str) \ + _LIBMISC_TU_ASSERT_2ARGS(_cond, _cond_str, false) + +#define _LIBMISC_TU_ASSERT_2ARGS(_cond, _cond_str, _ret) \ + do { \ + if ( !(_cond) ) { \ + _libmisc_tu_mess_failed(_cond_str, \ + __FILE__, __LINE__, __func__); \ + TU_BREAKPOINT(); \ + return _ret; \ + } \ + } while(0) + +void _libmisc_tu_mess_failed(const char *expr, + const char *file, unsigned int line, const char *func); //-------------------------------------------------------------------- // Configuration: common @@ -72,7 +91,7 @@ extern "C" { #define CFG_TUSB_MEM_ALIGN [[gnu::aligned(4)]] #define CFG_TUD_ENABLED 1 -#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED +#define CFG_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED //-------------------------------------------------------------------- // Configuration: TinyUSB Device (TUD) diff --git a/cmd/sbc_harness/fs_harness_flash_bin.c b/cmd/sbc_harness/fs_harness_flash_bin.c new file mode 100644 index 0000000..bcdf296 --- /dev/null +++ b/cmd/sbc_harness/fs_harness_flash_bin.c @@ -0,0 +1,312 @@ +/* sbc_harness/fs_harness_flash_bin.c - 9P access to flash storage + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> + +#include <hardware/flash.h> +#include <hardware/watchdog.h> + +#define LOG_NAME FLASH +#include <libmisc/log.h> + +#include <util9p/static.h> + +#define IMPLEMENTATION_FOR_FS_HARNESS_FLASH_BIN YES +#include "fs_harness_flash_bin.h" + +LO_IMPLEMENTATION_C(lib9p_srv_file, struct flash_file, flash_file, static); + +LO_IMPLEMENTATION_H(lib9p_srv_fio, struct flash_file, flash_file); +LO_IMPLEMENTATION_C(lib9p_srv_fio, struct flash_file, flash_file, static); + +#define DATA_START ((const char *)(XIP_NOALLOC_BASE)) +#define DATA_SIZE PICO_FLASH_SIZE_BYTES +#define DATA_HSIZE (DATA_SIZE/2) +static_assert(DATA_SIZE % FLASH_SECTOR_SIZE == 0); +static_assert(DATA_HSIZE % FLASH_SECTOR_SIZE == 0); + +/* There are some memcpy()s (and memcmp()s?) in here that can (and + * arguably should) be replaced with SSI DMA. */ + +/* ab_flash_* (mid-level utilities for our A/B write scheme) ******************/ + +/** + * Copy the upper half of flash to the lower half of flash, then reboot. + * + * @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE + */ +[[noreturn]] static void __no_inline_not_in_flash_func(ab_flash_finalize)(uint8_t *buf) { + assert(buf); + + infof("copying upper flash to lower flash..."); + + cr_save_and_disable_interrupts(); + + for (size_t off = 0; off < DATA_HSIZE; off += FLASH_SECTOR_SIZE) { + memcpy(buf, DATA_START+DATA_HSIZE+off, FLASH_SECTOR_SIZE); + if (memcmp(DATA_START+off, buf, FLASH_SECTOR_SIZE) == 0) + continue; + flash_range_erase(off, FLASH_SECTOR_SIZE); + flash_range_program(off, buf, FLASH_SECTOR_SIZE); + } + + infof("rebooting..."); + + watchdog_reboot(0, 0, 300); + + for (;;) + asm volatile ("nop"); +} + +/** + * Set the upper half of flash to all zero bytes. + * + * @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE + */ +static void ab_flash_initialize_zero(uint8_t *buf) { + assert(buf); + + memset(buf, 0, FLASH_SECTOR_SIZE); + + infof("zeroing upper flash..."); + for (size_t off = DATA_HSIZE; off < DATA_SIZE; off += FLASH_SECTOR_SIZE) { + if (memcmp(buf, DATA_START+off, FLASH_SECTOR_SIZE) == 0) + continue; + bool saved = cr_save_and_disable_interrupts(); + /* No need to `flash_range_erase()`; the way the flash + * works is that _erase() sets all bits to 1, and + * _program() sets some bits to 0. If we don't need + * any bits to be 1, then we can skip the + * _erase(). */ + flash_range_program(off, buf, FLASH_SECTOR_SIZE); + cr_restore_interrupts(saved); + } + debugf("... zeroed"); +} + +/** + * Copy the lower half of flash to the upper half of flash. + * + * @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE + */ +static void ab_flash_initialize(uint8_t *buf) { + assert(buf); + + infof("initializing upper flash..."); + for (size_t off = 0; off < DATA_HSIZE; off += FLASH_SECTOR_SIZE) { + memcpy(buf, DATA_START+off, FLASH_SECTOR_SIZE); + if (memcmp(buf, DATA_START+DATA_HSIZE+off, FLASH_SECTOR_SIZE) == 0) + continue; + bool saved = cr_save_and_disable_interrupts(); + flash_range_erase(DATA_HSIZE+off, FLASH_SECTOR_SIZE); + flash_range_program(DATA_HSIZE+off, buf, FLASH_SECTOR_SIZE); + cr_restore_interrupts(saved); + } + debugf("... initialized"); +} + +/** + * Write `dat` to flash sector `pos`+(DATA_SIZE/2) (i.e. `pos` is a + * sector in the lower half, but this function writes to the upper + * half). + * + * @param pos : start-position of the sector to write to, must be in the upper half of the flash + * @param dat : the FLASH_SECTOR_SIZE bytes to write + */ +static void ab_flash_write_sector(size_t pos, uint8_t *dat) { + assert(pos < DATA_HSIZE); + assert(pos % FLASH_SECTOR_SIZE == 0); + assert(dat); + + pos += DATA_HSIZE; + + infof("write flash sector @ %zu...", pos); + if (memcmp(dat, DATA_START+pos, FLASH_SECTOR_SIZE) != 0) { + bool saved = cr_save_and_disable_interrupts(); + flash_range_erase(pos, FLASH_SECTOR_SIZE); + flash_range_program(pos, dat, FLASH_SECTOR_SIZE); + cr_restore_interrupts(saved); + } + debugf("... written"); +} + +/* srv_file *******************************************************************/ + +static void flash_file_free(struct flash_file *self) { + assert(self); +} +static struct lib9p_qid flash_file_qid(struct flash_file *self) { + assert(self); + + return (struct lib9p_qid){ + .type = LIB9P_QT_FILE|LIB9P_QT_EXCL, + .vers = 1, + .path = self->pathnum, + }; +} + +static struct lib9p_stat flash_file_stat(struct flash_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + return (struct lib9p_stat){ + .kern_type = 0, + .kern_dev = 0, + .file_qid = flash_file_qid(self), + .file_mode = LIB9P_DM_EXCL|0666, + .file_atime = UTIL9P_ATIME, + .file_mtime = UTIL9P_MTIME, + .file_size = DATA_SIZE, + .file_name = lib9p_str(self->name), + .file_owner_uid = lib9p_str("root"), + .file_owner_gid = lib9p_str("root"), + .file_last_modified_uid = lib9p_str("root"), + .file_extension = lib9p_str(NULL), + .file_owner_n_uid = 0, + .file_owner_n_gid = 0, + .file_last_modified_n_uid = 0, + }; +} +static void flash_file_wstat(struct flash_file *self, struct lib9p_srv_ctx *ctx, + struct lib9p_stat) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); +} +static void flash_file_remove(struct flash_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); +} + +LIB9P_SRV_NOTDIR(struct flash_file, flash_file); + +static lo_interface lib9p_srv_fio flash_file_fopen(struct flash_file *self, struct lib9p_srv_ctx *ctx, + bool rd, bool wr, bool trunc) { + assert(self); + assert(ctx); + + if (rd) { + self->rbuf.ok = false; + } + + if (wr) { + if (trunc) { + ab_flash_initialize_zero(self->wbuf.dat); + self->written = true; + } else { + ab_flash_initialize(self->wbuf.dat); + self->written = false; + } + self->wbuf.ok = false; + } + + return lo_box_flash_file_as_lib9p_srv_fio(self); +} + +/* srv_fio ********************************************************************/ + +static uint32_t flash_file_iounit(struct flash_file *self) { + assert(self); + return FLASH_SECTOR_SIZE; +} + +static void flash_file_iofree(struct flash_file *self) { + assert(self); + + if (self->wbuf.ok) + ab_flash_write_sector(self->wbuf.pos, self->wbuf.dat); + + if (self->written) + ab_flash_finalize(self->wbuf.dat); +} + +static void flash_file_pread(struct flash_file *self, struct lib9p_srv_ctx *ctx, + uint32_t byte_count, uint64_t byte_offset, + struct iovec *ret) { + assert(self); + assert(ctx); + assert(ret); + + if (byte_offset > DATA_SIZE) { + lib9p_error(&ctx->basectx, + LINUX_EINVAL, "offset is past the chip size"); + return; + } + + /* Assume that somewhere down the line the iovec we return + * will be passed to DMA. We don't want the DMA engine to hit + * (slow) XIP (for instance, this can cause reads/writes to + * the SSP to get out of sync with eachother), so copy the + * data to a buffer in (fast) RAM first. It's lame that the + * DMA engine can only have a DREQ on one side of the channel. + */ + if (byte_offset == DATA_SIZE) { + *ret = (struct iovec){ + .iov_len = 0, + }; + return; + } + size_t sector_base = LM_ROUND_DOWN(byte_offset, FLASH_SECTOR_SIZE); + if (byte_offset + byte_count > sector_base + FLASH_SECTOR_SIZE) + byte_count = (sector_base + FLASH_SECTOR_SIZE) - byte_offset; + assert(byte_offset + byte_count <= DATA_SIZE); + + if (!self->rbuf.ok || self->rbuf.pos != sector_base) { + self->rbuf.ok = true; + self->rbuf.pos = sector_base; + memcpy(self->rbuf.dat, DATA_START+sector_base, FLASH_SECTOR_SIZE); + } + + *ret = (struct iovec){ + .iov_base = &self->rbuf.dat[byte_offset-sector_base], + .iov_len = byte_count, + }; +} + +/* TODO: Short/corrupt writes are dangerous. This should either (1) + * check a checksum, (2) use uf2 instead of verbatim data, or (3) use + * ihex instead of verbatim data. */ +static uint32_t flash_file_pwrite(struct flash_file *self, struct lib9p_srv_ctx *ctx, + void *buf, + uint32_t byte_count, + uint64_t byte_offset) { + assert(self); + assert(ctx); + + if (byte_offset > DATA_HSIZE) { + lib9p_error(&ctx->basectx, + LINUX_EINVAL, "offset is past half the chip size"); + return 0; + } + if (byte_count == 0) + return 0; + if (byte_offset == DATA_HSIZE) { + lib9p_error(&ctx->basectx, + LINUX_EINVAL, "offset is at half the chip size"); + return 0; + } + + size_t sector_base = LM_ROUND_DOWN(byte_offset, FLASH_SECTOR_SIZE); + if (byte_offset + byte_count > sector_base + FLASH_SECTOR_SIZE) + byte_count = (sector_base + FLASH_SECTOR_SIZE) - byte_offset; + assert(byte_offset + byte_count < DATA_HSIZE); + + if (self->wbuf.ok && self->wbuf.pos != sector_base) + ab_flash_write_sector(self->wbuf.pos, self->wbuf.dat); + if (!self->wbuf.ok || self->wbuf.pos != sector_base) { + self->wbuf.ok = true; + self->wbuf.pos = sector_base; + if (byte_count != FLASH_SECTOR_SIZE) + memcpy(self->wbuf.dat, DATA_START+DATA_HSIZE+sector_base, FLASH_SECTOR_SIZE); + } + memcpy(&self->wbuf.dat[byte_offset-sector_base], buf, byte_count); + + self->written = true; + return byte_count; +} diff --git a/cmd/sbc_harness/fs_harness_flash_bin.h b/cmd/sbc_harness/fs_harness_flash_bin.h new file mode 100644 index 0000000..36382be --- /dev/null +++ b/cmd/sbc_harness/fs_harness_flash_bin.h @@ -0,0 +1,30 @@ +/* sbc_harness/fs_harness_flash_bin.h - 9P access to flash storage + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _SBC_HARNESS_FS_HARNESS_FLASH_BIN_H_ +#define _SBC_HARNESS_FS_HARNESS_FLASH_BIN_H_ + +#include <hardware/flash.h> /* for FLASH_SECTOR_SIZE */ + +#include <lib9p/srv.h> + +struct flash_file { + char *name; + uint64_t pathnum; + + BEGIN_PRIVATE(FS_HARNESS_FLASH_BIN); + bool written; + struct { + bool ok; + size_t pos; + uint8_t dat[FLASH_SECTOR_SIZE]; + } wbuf, rbuf; + END_PRIVATE(FS_HARNESS_FLASH_BIN); +}; +LO_IMPLEMENTATION_H(lib9p_srv_file, struct flash_file, flash_file); +#define lo_box_flash_file_as_lib9p_srv_file(obj) util9p_box(flash_file, obj) + +#endif /* _SBC_HARNESS_FS_HARNESS_FLASH_BIN_H_ */ diff --git a/cmd/sbc_harness/fs_harness_uptime_txt.c b/cmd/sbc_harness/fs_harness_uptime_txt.c new file mode 100644 index 0000000..9216986 --- /dev/null +++ b/cmd/sbc_harness/fs_harness_uptime_txt.c @@ -0,0 +1,156 @@ +/* sbc_harness/fs_harness_uptime_txt.c - 9P access to harness uptime + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdio.h> /* for snprintf() */ +#include <stdlib.h> /* for malloc(), free() */ + +#include <libhw/generic/alarmclock.h> +#include <util9p/static.h> + +#include "fs_harness_uptime_txt.h" + +LO_IMPLEMENTATION_C(lib9p_srv_file, struct uptime_file, uptime_file, static); + +struct uptime_fio { + struct uptime_file *parent; + size_t buf_len; + char buf[24]; /* len(str(UINT64_MAX)+"ns\n\0") */ +}; + +LO_IMPLEMENTATION_H(lib9p_srv_fio, struct uptime_fio, uptime_fio); +LO_IMPLEMENTATION_C(lib9p_srv_fio, struct uptime_fio, uptime_fio, static); + +/* srv_file *******************************************************************/ + +static void uptime_file_free(struct uptime_file *self) { + assert(self); +} +static struct lib9p_qid uptime_file_qid(struct uptime_file *self) { + assert(self); + + return (struct lib9p_qid){ + .type = LIB9P_QT_FILE, + .vers = 1, + .path = self->pathnum, + }; +} + +static struct lib9p_stat uptime_file_stat(struct uptime_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + uint64_t now = LO_CALL(bootclock, get_time_ns); + uint64_t size = 0; + while (now) { + size++; + now /= 10; + } + if (!size) + size++; + size += 3; + + return (struct lib9p_stat){ + .kern_type = 0, + .kern_dev = 0, + .file_qid = uptime_file_qid(self), + .file_mode = 0444, + .file_atime = UTIL9P_ATIME, + .file_mtime = UTIL9P_MTIME, + .file_size = size, + .file_name = lib9p_str(self->name), + .file_owner_uid = lib9p_str("root"), + .file_owner_gid = lib9p_str("root"), + .file_last_modified_uid = lib9p_str("root"), + .file_extension = lib9p_str(NULL), + .file_owner_n_uid = 0, + .file_owner_n_gid = 0, + .file_last_modified_n_uid = 0, + }; +} +static void uptime_file_wstat(struct uptime_file *self, struct lib9p_srv_ctx *ctx, + struct lib9p_stat) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); +} +static void uptime_file_remove(struct uptime_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); +} + +LIB9P_SRV_NOTDIR(struct uptime_file, uptime_file); + +static lo_interface lib9p_srv_fio uptime_file_fopen(struct uptime_file *self, struct lib9p_srv_ctx *ctx, + bool LM_UNUSED(rd), bool LM_UNUSED(wr), bool LM_UNUSED(trunc)) { + assert(self); + assert(ctx); + + struct uptime_fio *ret = malloc(sizeof(struct uptime_fio)); + ret->parent = self; + ret->buf_len = 0; + + return lo_box_uptime_fio_as_lib9p_srv_fio(ret); +} + +/* srv_fio ********************************************************************/ + +static uint32_t uptime_fio_iounit(struct uptime_fio *self) { + assert(self); + return sizeof(self->buf)-1; +} + +static void uptime_fio_iofree(struct uptime_fio *self) { + assert(self); + free(self); +} + +static struct lib9p_qid uptime_fio_qid(struct uptime_fio *self) { + assert(self); + assert(self->parent); + return uptime_file_qid(self->parent); +} + +static void uptime_fio_pread(struct uptime_fio *self, struct lib9p_srv_ctx *ctx, + uint32_t byte_count, uint64_t byte_offset, + struct iovec *ret) { + assert(self); + assert(ctx); + assert(ret); + + if (byte_offset == 0 || self->buf_len == 0) { + uint64_t now = LO_CALL(bootclock, get_time_ns); + self->buf_len = snprintf(self->buf, sizeof(self->buf), "%"PRIu64"ns\n", now); + } + + if (byte_offset > (uint64_t)self->buf_len) { + lib9p_error(&ctx->basectx, + LINUX_EINVAL, "offset is past end-of-file length"); + return; + } + + size_t beg_off = (size_t)byte_offset; + size_t end_off = beg_off + (size_t)byte_count; + if (end_off > self->buf_len) + end_off = self->buf_len; + *ret = (struct iovec){ + .iov_base = &self->buf[beg_off], + .iov_len = end_off-beg_off, + }; +} + +static uint32_t uptime_fio_pwrite(struct uptime_fio *self, struct lib9p_srv_ctx *ctx, + void *LM_UNUSED(buf), + uint32_t LM_UNUSED(byte_count), + uint64_t LM_UNUSED(byte_offset)) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); + return 0; +} diff --git a/cmd/sbc_harness/fs_harness_uptime_txt.h b/cmd/sbc_harness/fs_harness_uptime_txt.h new file mode 100644 index 0000000..7bf2945 --- /dev/null +++ b/cmd/sbc_harness/fs_harness_uptime_txt.h @@ -0,0 +1,19 @@ +/* sbc_harness/fs_harness_uptime_txt.h - 9P access to harness uptime + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _SBC_HARNESS_FS_HARNESS_UPTIME_TXT_H_ +#define _SBC_HARNESS_FS_HARNESS_UPTIME_TXT_H_ + +#include <lib9p/srv.h> + +struct uptime_file { + char *name; + uint64_t pathnum; +}; +LO_IMPLEMENTATION_H(lib9p_srv_file, struct uptime_file, uptime_file); +#define lo_box_uptime_file_as_lib9p_srv_file(obj) util9p_box(uptime_file, obj) + +#endif /* _SBC_HARNESS_FS_HARNESS_UPTIME_TXT_H_ */ diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c index 4683c72..7765ca8 100644 --- a/cmd/sbc_harness/main.c +++ b/cmd/sbc_harness/main.c @@ -1,27 +1,120 @@ /* sbc_harness/main.c - Main entry point and event loop for sbc-harness * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <string.h> /* libc: for strlen() */ +/* libc */ +#include <string.h> /* libc: for strlen() */ -#include <pico/stdlib.h> /* pico-sdk:pico_stdlib: for stdio_uart_init() */ -#include <hardware/flash.h> /* pico-sdk:hardware_flash: for flash_get_unique_id() */ +/* pico-sdk */ +#include <pico/stdio_uart.h> /* pico-sdk:pico_stdio_uart: for stdio_uart_init() */ +#include <hardware/flash.h> /* pico-sdk:hardware_flash: for flash_get_unique_id() */ +/* our OS */ #include <libcr/coroutine.h> +#include <libhw/generic/alarmclock.h> /* so we can set `bootclock` */ #include <libhw/rp2040_hwspi.h> +#include <libhw/rp2040_hwtimer.h> #include <libhw/w5500.h> -#include <libmisc/hash.h> -#include <libusb/usb_common.h> + +/* our application libraries */ #include <libdhcp/client.h> +#include <libusb/usb_common.h> +#include <lib9p/srv.h> +#include <util9p/static.h> +/* our utility libraries */ +#include <libmisc/hash.h> #define LOG_NAME MAIN #include <libmisc/log.h> +/* local headers */ #include "usb_keyboard.h" +#include "static.h" +#include "fs_harness_flash_bin.h" +#include "fs_harness_uptime_txt.h" + +/* configuration **************************************************************/ + +#include "config.h" + +/* file tree ******************************************************************/ + +enum { PATH_BASE = __COUNTER__ }; +#define PATH_COUNTER __COUNTER__ - PATH_BASE + +#define STATIC_FILE(STRNAME, ...) UTIL9P_STATIC_FILE(PATH_COUNTER, STRNAME, __VA_ARGS__) +#define STATIC_DIR(STRNAME, ...) UTIL9P_STATIC_DIR(PATH_COUNTER, STRNAME, __VA_ARGS__) + +#define API_FILE(STRNAME, SYMNAME, ...) \ + lo_box_##SYMNAME##_file_as_lib9p_srv_file(&((struct SYMNAME##_file){ \ + .name = STRNAME, \ + .pathnum = PATH_COUNTER \ + __VA_OPT__(,) __VA_ARGS__ \ + })) + +struct lib9p_srv_file root = + STATIC_DIR("", + STATIC_DIR("Documentation", + STATIC_FILE("YOUR_RIGHTS_AND_OBLIGATIONS.md", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_md_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_md_end), + STATIC_DIR("YOUR_RIGHTS_AND_OBLIGATIONS", + STATIC_FILE("agpl-3.0.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_agpl_3_0_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_agpl_3_0_txt_end), + STATIC_FILE("pico-sdk.bsd3.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_pico_sdk_bsd3_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_pico_sdk_bsd3_txt_end), + STATIC_FILE("printf.mit.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_printf_mit_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_printf_mit_txt_end), + STATIC_FILE("tinyusb.mit.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_tinyusb_mit_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_tinyusb_mit_txt_end), + STATIC_FILE("newlib.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_newlib_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_newlib_txt_end), + ), + STATIC_FILE("harness_rom_bin.txt", + .data_start = _binary_static_Documentation_harness_rom_bin_txt_start, + .data_end = _binary_static_Documentation_harness_rom_bin_txt_end), + STATIC_FILE("harness_flash_bin.txt", + .data_start = _binary_static_Documentation_harness_flash_bin_txt_start, + .data_end = _binary_static_Documentation_harness_flash_bin_txt_end), + STATIC_FILE("harness_uptime_txt.txt", + .data_start = _binary_static_Documentation_harness_uptime_txt_txt_start, + .data_end = _binary_static_Documentation_harness_uptime_txt_txt_end), + ), + STATIC_DIR("harness", + STATIC_FILE("rom.bin", + .data_start = (void*)0x00000000, + .data_size = 16*1024), + API_FILE("flash.bin", + flash), + API_FILE("uptime.txt", + uptime), + // TODO: system.log + // TODO: proc.txt + // TODO: cpuinfo.txt + // TODO: ctl + ), + STATIC_DIR("dut", + // TODO: hdmi.nut + // TODO: uart.txt + // TODO: usb-keyboard.txt + ), + ); + +static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), struct lib9p_s LM_UNUSED(treename)) { + return root; +} + +/* Code ***********************************************************************/ -COROUTINE hello_world_cr(void *_chan) { +/* +static COROUTINE hello_world_cr(void *_chan) { const char *msg = "Hello world!\n"; usb_keyboard_rpc_t *chan = _chan; cr_begin(); @@ -36,21 +129,37 @@ COROUTINE hello_world_cr(void *_chan) { cr_end(); } +*/ -COROUTINE dhcp_cr(void *_chip) { - struct w5500 *chip = _chip; +struct { + struct rp2040_hwspi dev_spi; + struct w5500 dev_w5500; + usb_keyboard_rpc_t keyboard_chan; + uint16_t usb_serial[sizeof(uint64_t)*2]; /* UTF-16 */ + struct lib9p_srv srv; +} globals; + +static COROUTINE dhcp_cr(void *) { cr_begin(); - dhcp_client_main(chip, "harness"); + dhcp_client_main(lo_box_w5500_if_as_net_iface(&globals.dev_w5500), "harness"); cr_end(); } -struct { - struct rp2040_hwspi dev_spi; - struct w5500 dev_w5500; - usb_keyboard_rpc_t keyboard_chan; -} globals; +static COROUTINE read9p_cr(void *) { + cr_begin(); + + lo_interface net_iface iface = lo_box_w5500_if_as_net_iface(&globals.dev_w5500); + lo_interface net_stream_listener listener = LO_CALL(iface, tcp_listen, LIB9P_DEFAULT_PORT_9FS); + + lib9p_srv_read_cr(&globals.srv, listener); + + cr_end(); +} + +const char *const hexdig = "0123456789ABCDEF"; +static_assert(CONFIG_9P_SRV_MAX_REQS*_CONFIG_9P_NUM_SOCKS <= 16); COROUTINE init_cr(void *) { cr_begin(); @@ -71,12 +180,16 @@ COROUTINE init_cr(void *) { rp2040_hwspi_init(&globals.dev_spi, "W5500", RP2040_HWSPI_0, SPI_MODE_0, /* the W5500 supports mode 0 or mode 3 */ - 60*1000*1000, /* as close to the W5500's max rate of 80MHz as we can without hwspi borking */ - 16, /* PIN_MISO */ - 19, /* PIN_MOSI */ - 18, /* PIN_CLK */ - 17); /* PIN_CS */ - w5500_init(&globals.dev_w5500, "W5500", &globals.dev_spi, + 42500000, /* min(w5500, hwspi); w5500=80MHz; hwspi=42.5MHz, see rp2040_hwspi.h for a comment about why this is so low */ + 30, /* W5500 datasheet says min(T_CS = SCSn High Time) = 30ns */ + 0, /* bogus write write data when doing a read */ + 16, /* PIN_MISO */ + 19, /* PIN_MOSI */ + 18, /* PIN_CLK */ + 17, /* PIN_CS */ + 0, 1, 2, 3); /* DMA channels */ + w5500_init(&globals.dev_w5500, "W5500", + lo_box_rp2040_hwspi_as_spi(&globals.dev_spi), 21, /* PIN_INTR */ 20, /* PIN_RESET */ ((struct net_eth_addr){{ @@ -86,23 +199,40 @@ COROUTINE init_cr(void *) { flash_id24[0], flash_id24[1], flash_id24[2], }})); - usb_common_earlyinit(); + static_assert(sizeof(flash_id64)*2 == LM_ARRAY_LEN(globals.usb_serial)); + for (size_t i = 0; i < LM_ARRAY_LEN(globals.usb_serial); i++) + globals.usb_serial[i] = hexdig[(flash_id64 >> ((sizeof(flash_id64)*8)-((i+1)*4))) & 0xF]; + usb_common_earlyinit(globals.usb_serial, sizeof(globals.usb_serial)); usb_keyboard_init(); usb_common_lateinit(); globals.keyboard_chan = (usb_keyboard_rpc_t){0}; + globals.srv.rootdir = get_root; + /* set up coroutines **************************************************/ coroutine_add("usb_common", usb_common_cr, NULL); coroutine_add("usb_keyboard", usb_keyboard_cr, &globals.keyboard_chan); - //coroutine_add("hello_world", hello_world_cr, &keyboard_chan); - coroutine_add_with_stack_size(4*1024, "dhcp", dhcp_cr, &globals.dev_w5500); + //coroutine_add("hello_world", hello_world_cr, &globals.keyboard_chan); + coroutine_add("dhcp", dhcp_cr, NULL); + for (int i = 0; i < _CONFIG_9P_NUM_SOCKS; i++) { + char name[] = {'r', 'e', 'a', 'd', '-', hexdig[i], '\0'}; + coroutine_add(name, read9p_cr, NULL); + } + for (int i = 0; i < CONFIG_9P_SRV_MAX_REQS*_CONFIG_9P_NUM_SOCKS; i++) { + char name[] = {'w', 'r', 'i', 't', 'e', '-', hexdig[i], '\0'}; + coroutine_add(name, lib9p_srv_write_cr, &globals.srv); + } cr_exit(); } int main() { + bootclock = rp2040_hwtimer(0); stdio_uart_init(); + /* char *hdr = "=" * (80-strlen("info : MAIN: ")); */ + infof("==================================================================="); coroutine_add("init", init_cr, NULL); coroutine_main(); + assert_notreached("all coroutines exited"); } diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md new file mode 100644 index 0000000..b3fc12e --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md @@ -0,0 +1,29 @@ +<!-- + Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md - Overview of your + rights and obligations with regard to the SBC-Harness firmware + + Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later +--> + +The firmware running on the SBC-Harness is Free Software -- if you +have access to this file, then you have the freedom to use, study, +share, and improve the firmware; as long as you follow a few +conditions that basically amount to "paying it forward". + +The precise terms of your rights and obligations are given in the +files in the `YOUR_RIGHTS_AND_OBLIGATIONS/` directory. You must obey +each of the files in that directory when distributing +verbatim-or-modified copies of the firmware: + + - `agpl-3.0.txt`: The firmware is as-a-whole licensed to you under + the terms of the GNU Affero General Public License (GNU AGPL) as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. This is the main + document that protects your rights and defines your obligations. + + - other files: The firmware makes use of other code that is under + various other licenses. When taken with the AGPL, they amount to + requiring that you pass along the copyright and license text in + those files when you distribute verbatim-or-modified copies of the + firmware. diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/agpl-3.0.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/agpl-3.0.txt new file mode 120000 index 0000000..8a9b6f3 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/agpl-3.0.txt @@ -0,0 +1 @@ +../../../../../COPYING.txt
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/newlib.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/newlib.txt new file mode 120000 index 0000000..5c5939c --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/newlib.txt @@ -0,0 +1 @@ +../../../../../3rd-party/COPYING.newlib.txt
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/pico-sdk.bsd3.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/pico-sdk.bsd3.txt new file mode 120000 index 0000000..52c4374 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/pico-sdk.bsd3.txt @@ -0,0 +1 @@ +../../../../../3rd-party/pico-sdk/LICENSE.TXT
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/printf.mit.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/printf.mit.txt new file mode 120000 index 0000000..5a6e342 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/printf.mit.txt @@ -0,0 +1 @@ +../../../../../3rd-party/pico-sdk/src/rp2_common/pico_printf/LICENSE
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/tinyusb.mit.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/tinyusb.mit.txt new file mode 120000 index 0000000..22a67cf --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/tinyusb.mit.txt @@ -0,0 +1 @@ +../../../../../3rd-party/pico-sdk/lib/tinyusb/LICENSE
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt b/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt new file mode 100644 index 0000000..115f2ee --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt @@ -0,0 +1,33 @@ +NAME + /harness/flash.bin + +DESCRIPTION + Access to the flash storage chip (where the harness firmware + is stored). + + Any number of readers may read the flash contents. + + Only one writer can have the file open at a time; once the + file is closed, the harness reboots into the new firmware. + Writes to the top half of the chip will fail. + +BUGS + - The size of the chip is configured at compile-time. If the + firmware is loaded onto hardware with a larger flash chip + than it was compiled for, then the upper part of the chip + will not be accessible with this file. If the firmware is + loaded onto hardware with a smaller flash chip than it was + compiled for, then accessing the missing upper part of the + chip will crash. + + - When writing to the flash using this file, only half of the + chip capacity is usable; the top half and bottom half are + mirrors of each-other. This is to avoid the firmware + crashing as its program text is overwritten; the firmware is + executing out of the bottom half, and writing to the top + half; once the file is closed, a minimal in-RAM function + copies the top half to the bottom half and reboots. + +AUTHOR + Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/cmd/sbc_harness/static/Documentation/harness_rom_bin.txt b/cmd/sbc_harness/static/Documentation/harness_rom_bin.txt new file mode 100644 index 0000000..63fd0a3 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/harness_rom_bin.txt @@ -0,0 +1,41 @@ +NAME + /harness/rom.bin + +DESCRIPTION + Read access to the RP2040 CPU's ROM. This contains code that + initializes the chip to load the main firmware from the + external flash chip, provides a failsafe USB-programmable + mode, and provides a few functions that the main firmware can + call to. + +BUGS + This ROM is programmed into the chip at the factory; revising + it means issuing a new revison of the RP2040 CPU. So while + the source code to the ROM is freely available to be used, + studied, and shared; one cannot install modified versions onto + the CPU. + +HISTORY + - RP2040 B0 : chips manufactured before September 2020 or so + - RP2040 B1 : chips manufactured after September 2020 or so + - Released to the public January 2021; chance whether you get + a B0 or a B1 chip. + - RP2040 B2 : released September 2021 + + Printed on the physical CPU is a label that indicates which + revision it is. For example: + + RP2-B2 21/24 + + indicates that it is the "B2" revision (and was manufactured + the 21st week (late May) of 2024). + +SEE ALSO + - /harness/cpuinfo.txt can report which CPU version you have. + + - The source code to each ROM revision is published at + https://github.com/raspberrypi/pico-bootrom-rp2040 + +AUTHOR + Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt b/cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt new file mode 100644 index 0000000..1ab86f7 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt @@ -0,0 +1,17 @@ +NAME + /harness/uptime.txt + +DESCRIPTION + Reading this file gives a text string of the format + `sprintf("%uns\n", uptime_ns)` indicating the harness's uptime + in an integer number of nanoseconds. + +BUGS + Using nanoseconds gives the illusion of more precision than + there actually is; the harness' clock only has microsecond + resolution; the last 3 digits of the returned nanosecond count + will always be 0. + +AUTHOR + Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/cmd/sbc_harness/tusb_log.c b/cmd/sbc_harness/tusb_log.c new file mode 100644 index 0000000..4c6b7df --- /dev/null +++ b/cmd/sbc_harness/tusb_log.c @@ -0,0 +1,15 @@ +/* sbc_harness/tusb_log.c - Logger for tusb_config.h + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#define LOG_NAME TINY_USB +#include <libmisc/log.h> + +void _libmisc_tu_mess_failed(const char *expr, + const char *file, unsigned int line, const char *func) { + errorf("%s:%u:%s(): assertion \"%s\" failed", + file, line, func, + expr); +} diff --git a/cmd/sbc_harness/usb_keyboard.c b/cmd/sbc_harness/usb_keyboard.c index 637921e..f3cb42d 100644 --- a/cmd/sbc_harness/usb_keyboard.c +++ b/cmd/sbc_harness/usb_keyboard.c @@ -1,6 +1,6 @@ /* sbc_harness/usb_keyboard.c - Implementation of a USB keyboard device * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -8,11 +8,10 @@ #include <libusb/tusb_helpers.h> /* for TUD_ENDPOINT_IN */ #include <libusb/usb_common.h> +#include <libmisc/macro.h> #include "usb_keyboard.h" -#define UNUSED(name) - /** * A USB-HID "Report Descriptor" (see USB-HID 1.11 §6.2.2 "Report * Descriptor") describing a keyboard. @@ -24,7 +23,6 @@ static uint8_t kbd_ifc = 0; void usb_keyboard_init() { if (kbd_ifc) return; - usb_common_earlyinit(); kbd_ifc = usb_add_interface(cfgnum_std, TUD_HID_DESC_LEN, (uint8_t[]){ /* USB-HID input-only descriptor for inclusion in the config descriptor; consisting of 3 parts: @@ -97,7 +95,7 @@ uint8_t const *tud_hid_descriptor_report_cb(uint8_t index) { uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { - // TODO not Implemented + // TODO not implemented (void) instance; (void) report_id; (void) report_type; @@ -109,7 +107,11 @@ uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_t // Invoked when received SET_REPORT control request or // received data on OUT endpoint ( Report ID = 0, Type = 0 ) -void tud_hid_set_report_cb(uint8_t UNUSED(instance), uint8_t UNUSED(report_id), hid_report_type_t UNUSED(report_type), uint8_t const *UNUSED(buffer), uint16_t UNUSED(bufsize)) +void tud_hid_set_report_cb(uint8_t LM_UNUSED(instance), + uint8_t LM_UNUSED(report_id), + hid_report_type_t LM_UNUSED(report_type), + uint8_t const *LM_UNUSED(buffer), + uint16_t LM_UNUSED(bufsize)) { // TODO not implemented } diff --git a/cmd/sbc_harness/usb_keyboard.h b/cmd/sbc_harness/usb_keyboard.h index 210014d..cf8483b 100644 --- a/cmd/sbc_harness/usb_keyboard.h +++ b/cmd/sbc_harness/usb_keyboard.h @@ -1,6 +1,6 @@ /* sbc_harness/usb_keyboard.h - Implementation of a USB keyboard device * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -12,7 +12,7 @@ #include <libcr/coroutine.h> /* for COROUTINE */ #include <libcr_ipc/rpc.h> /* for CR_RPC_DECLARE */ -CR_RPC_DECLARE(usb_keyboard_rpc, uint32_t, int) +CR_RPC_DECLARE(usb_keyboard_rpc, uint32_t, int); void usb_keyboard_init(void); COROUTINE usb_keyboard_cr(void *arg); diff --git a/cmd/srv9p/CMakeLists.txt b/cmd/srv9p/CMakeLists.txt deleted file mode 100644 index b747882..0000000 --- a/cmd/srv9p/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -# cmd/srv9p/CMakeLists.txt - Build script for srv9p test/dev executable -# -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -if (PICO_PLATFORM STREQUAL "host") - -add_executable(srv9p - main.c - static9p.c -) -target_embed_sources(srv9p static.h - static/README.md - static/Documentation/x -) -target_include_directories(srv9p PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_include_directories(srv9p PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config) -target_link_libraries(srv9p - libcr - libcr_ipc - libmisc - lib9p -) - -endif() diff --git a/cmd/srv9p/main.c b/cmd/srv9p/main.c deleted file mode 100644 index 05368e5..0000000 --- a/cmd/srv9p/main.c +++ /dev/null @@ -1,130 +0,0 @@ -/* srv9p/main.c - Main entry point for test 9P server - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <error.h> - -#include <lib9p/srv.h> -#include <libcr/coroutine.h> -#include <libhw/generic/net.h> -#include <libhw/generic/alarmclock.h> -#include <libhw/host_net.h> - -#include "static9p.h" -#include "static.h" - -/* configuration **************************************************************/ - -#include "config.h" - -#ifndef CONFIG_SRV9P_NUM_CONNS - #error config.h must define CONFIG_SRV9P_NUM_CONNS -#endif - -/* implementation *************************************************************/ - -#define UNUSED(name) - -/* file tree ******************************************************************/ - -#define FILE_COMMON(NAME) { \ - .vtable = &static_file_vtable, \ - \ - .u_name = "root", .u_num = 0, /* owner user */ \ - .g_name = "root", .g_num = 0, /* owner group */ \ - .m_name = "root", .m_num = 0, /* last-modified-by user */ \ - \ - .pathnum = __COUNTER__, \ - .name = NAME, \ - .perm = 0444, \ - .atime = 1728337905, \ - .mtime = 1728337904, \ - } - -#define DIR_COMMON(NAME) { \ - .vtable = &static_dir_vtable, \ - \ - .u_name = "root", .u_num = 0, /* owner user */ \ - .g_name = "root", .g_num = 0, /* owner group */ \ - .m_name = "root", .m_num = 0, /* last-modified-by user */ \ - \ - .pathnum = __COUNTER__, \ - .name = NAME, \ - .perm = 0555, \ - .atime = 1728337905, \ - .mtime = 1728337904, \ - } - -#define STATIC_FILE(STRNAME, SYMNAME) \ - &((static struct static_file){ \ - ._static_common = FILE_COMMON(STRNAME), \ - .data_start = _binary_static_##SYMNAME##_start, \ - .data_end = _binary_static_##SYMNAME##_end, \ - }) - -static struct static_dir root = { - ._static_common = DIR_COMMON(""), - .members = { - &((static struct static_dir){ - ._static_common = DIR_COMMON("Documentation"), - .members = { - STATIC_FILE("x", Documentation_x), - NULL - }, - }), - STATIC_FILE("README.md", README_md), - NULL, - }, -}; - -static implements_lib9p_srv_file *get_root(struct lib9p_srv_ctx *UNUSED(ctx), char *UNUSED(treename)) { - return &root; -} - -/* main ***********************************************************************/ - -static COROUTINE read_cr(void *_srv) { - struct lib9p_srv *srv = _srv; - assert(srv); - - cr_begin(); - - struct hostnet_tcp_listener listener; - hostnet_tcp_listener_init(&listener, 9000); - - lib9p_srv_read_cr(srv, &listener); - - cr_end(); -} - -const char *hexdig = "0123456789abcdef"; - -struct lib9p_srv srv = { - .rootdir = get_root, -}; - -static COROUTINE init_cr(void *) { - cr_begin(); - - for (int i = 0; i < CONFIG_SRV9P_NUM_CONNS; i++) { - char name[] = {'r', 'e', 'a', 'd', '-', hexdig[i], '\0'}; - if (!coroutine_add(name, read_cr, &srv)) - error(1, 0, "coroutine_add(read_cr, &srv)"); - sleep_for_s(1); - } - for (int i = 0; i < 2*CONFIG_SRV9P_NUM_CONNS; i++) { - char name[] = {'w', 'r', 'i', 't', 'e', '-', hexdig[i], '\0'}; - if (!coroutine_add(name, lib9p_srv_write_cr, &srv)) - error(1, 0, "coroutine_add(lib9p_srv_write_cr, &srv)"); - sleep_for_s(1); - } - - cr_exit(); -} - -int main() { - coroutine_add("init", init_cr, NULL); - coroutine_main(); -} diff --git a/cmd/srv9p/static/Documentation/x b/cmd/srv9p/static/Documentation/x deleted file mode 100644 index 257cc56..0000000 --- a/cmd/srv9p/static/Documentation/x +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/cmd/srv9p/static/README.md b/cmd/srv9p/static/README.md deleted file mode 100644 index af5626b..0000000 --- a/cmd/srv9p/static/README.md +++ /dev/null @@ -1 +0,0 @@ -Hello, world! diff --git a/cmd/srv9p/static9p.c b/cmd/srv9p/static9p.c deleted file mode 100644 index b5b18e0..0000000 --- a/cmd/srv9p/static9p.c +++ /dev/null @@ -1,241 +0,0 @@ -/* srv9p/static9p.c - Serve static files over 9P - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <libmisc/assert.h> -#include <libmisc/vcall.h> - -#include "static9p.h" - -#define UNUSED(name) -#define p9_str(cstr) ((struct lib9p_s){ .len = strlen(cstr), .utf8 = cstr }) -#define p9_nulstr ((struct lib9p_s){ .len = 0, .utf8 = NULL }) - -/* common *********************************************************************/ - -static implements_lib9p_srv_file *static_common_clone(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - return self; -} - -static void static_common_free(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - /* do nothing */ -} - -static uint32_t static_common_io(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, lib9p_o_t UNUSED(flags)) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - return 0; -} - -static void static_common_wstat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - struct lib9p_stat UNUSED(new)) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); -} - -static void static_common_remove(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); -} - -/* dir ************************************************************************/ - -static struct lib9p_stat static_dir_stat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - struct static_dir *self = VCALL_SELF(struct static_dir, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - return (struct lib9p_stat){ - .kern_type = 0, - .kern_dev = 0, - .file_qid = { - .type = LIB9P_QT_DIR, - .vers = 1, - .path = self->pathnum, - }, - .file_mode = LIB9P_DM_DIR | (self->perm & 0555), - .file_atime = self->atime, - .file_mtime = self->mtime, - .file_size = 0, - .file_name = p9_str(self->name), - .file_owner_uid = p9_str(self->u_name), - .file_owner_gid = p9_str(self->g_name), - .file_last_modified_uid = p9_str(self->m_name), - .file_extension = p9_nulstr, - .file_owner_n_uid = self->u_num, - .file_owner_n_gid = self->g_num, - .file_last_modified_n_uid = self->m_num, - }; -} - -static implements_lib9p_srv_file *static_dir_dopen(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - char *childname) { - struct static_dir *self = VCALL_SELF(struct static_dir, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - for (size_t i = 0; self->members[i]; i++) { - implements_lib9p_srv_file *filep = self->members[i]; - struct lib9p_stat stat = VCALL(filep, stat, ctx); - if (lib9p_ctx_has_error(&ctx->basectx)) - break; - lib9p_assert_stat(stat); - if (strcmp(stat.file_name.utf8, childname) == 0) - return filep; - } - lib9p_error(&ctx->basectx, - LINUX_ENOENT, "no such file or directory"); - return NULL; -} - -static implements_lib9p_srv_file *static_dir_dcreate(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - char *UNUSED(childname), - lib9p_dm_t UNUSED(perm), lib9p_o_t UNUSED(flags)) { - struct static_dir *self = VCALL_SELF(struct static_dir, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); - return NULL; -} - -static size_t static_dir_dread(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - uint8_t *buf, - uint32_t byte_count, - size_t _obj_offset) { - struct static_dir *self = VCALL_SELF(struct static_dir, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - uint32_t byte_offset = 0; - size_t obj_offset = _obj_offset; - while (self->members[obj_offset]) { - implements_lib9p_srv_file *filep = self->members[obj_offset]; - struct lib9p_stat stat = VCALL(filep, stat, ctx); - if (lib9p_ctx_has_error(&ctx->basectx)) - break; - lib9p_assert_stat(stat); - uint32_t nbytes = lib9p_marshal_stat(&ctx->basectx, byte_count-byte_offset, &stat, - &buf[byte_offset]); - if (!nbytes) { - if (obj_offset == _obj_offset) - lib9p_error(&ctx->basectx, - LINUX_ERANGE, "stat object does not fit into negotiated max message size"); - break; - } - byte_offset += nbytes; - obj_offset++; - } - return obj_offset - _obj_offset; -} - -struct lib9p_srv_file_vtable static_dir_vtable = { - .clone = static_common_clone, - .free = static_common_free, - - .io = static_common_io, - .stat = static_dir_stat, - .wstat = static_common_wstat, - .remove = static_common_remove, - - .dopen = static_dir_dopen, - .dcreate = static_dir_dcreate, - - .dread = static_dir_dread, -}; - -/* file ***********************************************************************/ - -static inline size_t static_file_size(struct static_file *file) { - assert(file); - -#if __unix__ - assert(file->data_start); -#endif - if (!file->data_end) - return file->data_size; - return (size_t)((uintptr_t)file->data_end - (uintptr_t)file->data_start); -} - -static struct lib9p_stat static_file_stat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - struct static_file *self = VCALL_SELF(struct static_file, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - return (struct lib9p_stat){ - .kern_type = 0, - .kern_dev = 0, - .file_qid = { - .type = LIB9P_QT_FILE, - .vers = 1, - .path = self->pathnum, - }, - .file_mode = self->perm & 0444, - .file_atime = self->atime, - .file_mtime = self->mtime, - .file_size = (uint64_t)static_file_size(self), - .file_name = p9_str(self->name), - .file_owner_uid = p9_str(self->u_name), - .file_owner_gid = p9_str(self->g_name), - .file_last_modified_uid = p9_str(self->m_name), - .file_extension = p9_nulstr, - .file_owner_n_uid = self->u_num, - .file_owner_n_gid = self->g_num, - .file_last_modified_n_uid = self->m_num, - }; -} - -static uint32_t static_file_pread(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - void *buf, - uint32_t byte_count, - uint64_t byte_offset) { - struct static_file *self = VCALL_SELF(struct static_file, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - size_t data_size = static_file_size(self); - - if (byte_offset > (uint64_t)data_size) { - lib9p_error(&ctx->basectx, - LINUX_EINVAL, "offset is past end-of-file length"); - return 0; - } - - size_t beg_off = (size_t)byte_offset; - size_t end_off = beg_off + (size_t)byte_count; - if (end_off > data_size) - end_off = data_size; - memcpy(buf, &self->data_start[beg_off], end_off-beg_off); - return (uint32_t)(end_off-beg_off); -} - -struct lib9p_srv_file_vtable static_file_vtable = { - .clone = static_common_clone, - .free = static_common_free, - - .io = static_common_io, - .stat = static_file_stat, - .wstat = static_common_wstat, - .remove = static_common_remove, - - .pread = static_file_pread, - .pwrite = NULL, -}; diff --git a/cmd/srv9p/static9p.h b/cmd/srv9p/static9p.h deleted file mode 100644 index 7a3d476..0000000 --- a/cmd/srv9p/static9p.h +++ /dev/null @@ -1,47 +0,0 @@ -/* srv9p/static9p.h - Serve static files over 9P - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _SRV9P_STATIC9P_H_ -#define _SRV9P_STATIC9P_H_ - -#include <lib9p/srv.h> - -typedef struct { - implements_lib9p_srv_file; - - char *u_name; - uint32_t u_num; - char *g_name; - uint32_t g_num; - char *m_name; - uint32_t m_num; - - uint64_t pathnum; - char *name; - lib9p_dm_t perm; - uint32_t atime, mtime; -} _static_common; - -struct static_dir { - _static_common; - - /* NULL-terminated */ - implements_lib9p_srv_file *members[]; -}; - - -struct static_file { - _static_common; - - char *data_start; /* must not be NULL */ - char *data_end; /* may be NULL, in which case data_size is used */ - size_t data_size; /* only used if .data_end==NULL */ -}; - -extern struct lib9p_srv_file_vtable static_dir_vtable; -extern struct lib9p_srv_file_vtable static_file_vtable; - -#endif /* _SRV9P_STATIC9P_H_ */ diff --git a/gdb-helpers/libcr.py b/gdb-helpers/libcr.py new file mode 100644 index 0000000..fcfd86e --- /dev/null +++ b/gdb-helpers/libcr.py @@ -0,0 +1,471 @@ +# gdb-helpers/libcr.py - GDB helpers for libcr. +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import contextlib +import time +import typing + +import gdb # pylint: disable=import-error +import gdb.unwinder # pylint: disable=import-error + +# GDB helpers ################################################################## + +# https://sourceware.org/bugzilla/show_bug.cgi?id=32428 +gdb_bug_32428 = True + + +class _gdb_Locus(typing.Protocol): + @property + def frame_unwinders(self) -> list["gdb._Unwinder"]: ... + + +def gdb_unregister_unwinder( + locus: gdb.Objfile | gdb.Progspace | None, unwinder: "gdb._Unwinder" +) -> None: + _locus: _gdb_Locus = typing.cast(_gdb_Locus, gdb) if locus is None else locus + _locus.frame_unwinders.remove(unwinder) + gdb.invalidate_cached_frames() + + +def gdb_is_on_os() -> bool: + try: + gdb.execute("info proc", to_string=True) + return True + except gdb.error: + return False + + +class gdb_JmpBuf: + """Our own in-Python GDB-specific implementation of `jmp_buf`""" + + level: int + registers: dict[str, str] + + +def gdb_setjmp() -> gdb_JmpBuf: + """Our own in-Python GDB-specific implementation of `setjmp()`""" + buf = gdb_JmpBuf() + buf.level = gdb.selected_frame().level() + gdb.execute("select-frame level 0") + buf.registers = {} + for line in gdb.execute("info registers", to_string=True).split("\n"): + words = line.split(maxsplit=2) + if len(words) < 2: + continue + buf.registers[words[0]] = words[1] + gdb.execute(f"select-frame level {buf.level}") + return buf + + +def gdb_longjmp(buf: gdb_JmpBuf) -> None: + """Our own in-Python GDB-specific implementation of `longjmp()`""" + + gdb.execute("select-frame level 0") + + if ( + ("sp" in buf.registers) + and ("msp" in buf.registers) + and ("psp" in buf.registers) + and ("control" in buf.registers) + ): + # On ARM, 'sp' is an alias for either 'msp' or 'psp' + # (depending on 'control'&(1<<1)). We must set all 3 before + # fussing with 'xPSR' or frames, or GDB will get upset at us + # about "Invalid state". + gdb.execute(f"set $sp = {buf.registers['sp']}", to_string=True) + gdb.execute(f"set $msp = {buf.registers['msp']}") + gdb.execute(f"set $psp = {buf.registers['psp']}") + + for reg, val in buf.registers.items(): + gdb.execute(f"set ${reg} = {val}") + gdb.invalidate_cached_frames() + + gdb.execute(f"select-frame level {buf.level}") + + +# Core libcr functionality ##################################################### + + +class CrGlobals: + coroutines: list["CrCoroutine"] + _breakpoint: "CrBreakpoint" + _known_threads: set[gdb.InferiorThread] + + def __init__(self) -> None: + num = int( + gdb.parse_and_eval("sizeof(coroutine_table)/sizeof(coroutine_table[0])") + ) + + self.coroutines = [CrCoroutine(self, i + 1) for i in range(num)] + + self._breakpoint = CrBreakpoint() + self._breakpoint.enabled = False + + self._known_threads = set() + + gdb.events.cont.connect(self._on_cont) + + def delete(self) -> None: + self.coroutines = [] + self._breakpoint.delete() + gdb.events.cont.disconnect(self._on_cont) + + def readjmp(self, env_ptr_expr: str) -> gdb_JmpBuf: + self._breakpoint.enabled = True + gdb.execute(f"call (void)cr_gdb_readjmp({env_ptr_expr})") + self._breakpoint.enabled = False + if gdb_is_on_os(): + gdb.execute("queue-signal SIGWINCH") + return self._breakpoint.env + + def _on_cont(self, event: gdb.Event) -> None: + cur_threads = set(gdb.selected_inferior().threads()) + if cur_threads - self._known_threads: + # Ignore thread creation events. + self._known_threads = cur_threads + return + if self.coroutine_running: + if not self.coroutine_running.is_selected(): + if gdb_bug_32428: + print("Must return to running coroutine before continuing.") + print("Hit ^C twice then run:") + print(f" cr select {self.coroutine_running.id}") + while True: + time.sleep(1) + assert self.coroutine_running.cont_env + gdb_longjmp(self.coroutine_running.cont_env) + for cr in self.coroutines: + cr.cont_env = None + + def is_valid_cid(self, cid: int) -> bool: + return 0 < cid <= len(self.coroutines) + + @property + def coroutine_running(self) -> "CrCoroutine | None": + cid = int(gdb.parse_and_eval("coroutine_running")) + if not self.is_valid_cid(cid): + return None + return self.coroutines[cid - 1] + + @property + def coroutine_selected(self) -> "CrCoroutine | None": + for cr in self.coroutines: + if cr.is_selected(): + return cr + return None + + @property + def CR_NONE(self) -> gdb.Value: + return gdb.parse_and_eval("CR_NONE") + + @property + def CR_RUNNING(self) -> gdb.Value: + return gdb.parse_and_eval("CR_RUNNING") + + +class CrBreakpointUnwinder(gdb.unwinder.Unwinder): + """Used to temporarily disable unwinding so that + gdb/breakpoint.c:check_longjmp_breakpoint_for_call_dummy() doesn't + prematurely garbage collect the `call`-dummy-frame. + + """ + + def __init__(self) -> None: + super().__init__("cr_breakpoint_unwinder") + + # The .pyi is wrong; it says `Frame` instead of `PendingFrame`. + def __call__(self, pending_frame: gdb.PendingFrame) -> gdb.UnwindInfo | None: + # Stop unwinding with stop_reason=UNWIND_NO_SAVED_PC by + # returning an UnwindInfo that doesn't have + # `.add_saved_register("pc", ...)`. + return pending_frame.create_unwind_info( + gdb.unwinder.FrameId( + sp=pending_frame.read_register("sp"), + pc=pending_frame.read_register("pc"), + ) + ) + + +class CrBreakpoint(gdb.Breakpoint): + env: gdb_JmpBuf + _unwinder_locus: gdb.Objfile + _unwinder: CrBreakpointUnwinder + + def __init__(self) -> None: + self.env = gdb_JmpBuf() + + self._unwinder = CrBreakpointUnwinder() + readjmp_sym = gdb.lookup_global_symbol("cr_gdb_readjmp") + assert readjmp_sym + self._unwinder_locus = readjmp_sym.symtab.objfile + gdb.unwinder.register_unwinder(self._unwinder_locus, self._unwinder, True) + self._unwinder.enabled = False + + super().__init__( + function="cr_gdb_breakpoint", type=gdb.BP_BREAKPOINT, internal=True + ) + + @property + def enabled(self) -> bool: + return super().enabled + + @enabled.setter + def enabled(self, value: bool) -> None: + self._unwinder.enabled = value + # Use a dunder-call to avoid an infinite loop. + # pylint: disable=unnecessary-dunder-call + gdb.Breakpoint.enabled.__set__(self, value) # type: ignore + + def stop(self) -> bool: + assert self._unwinder.enabled + self._unwinder.enabled = False + self.env = gdb_setjmp() + self._unwinder.enabled = True + return False # don't stop + + def delete(self) -> None: + gdb_unregister_unwinder(self._unwinder_locus, self._unwinder) + super().delete() + + +def cr_select_top_frame() -> None: + gdb.execute("select-frame level 0") + base_frame = gdb.selected_frame() + while True: + fn = gdb.selected_frame().name() + if fn and (fn.startswith("cr_") or fn.startswith("_cr_")): + older = gdb.selected_frame().older() + if not older: + base_frame.select() + break + older.select() + else: + break + + +class CrCoroutine: + cr_globals: CrGlobals + cid: int + cont_env: gdb_JmpBuf | None + + def __init__(self, cr_globals: CrGlobals, cid: int) -> None: + self.cr_globals = cr_globals + self.cid = cid + self.cont_env = None + + @property + def id(self) -> int: + return self.cid + + @property + def state(self) -> gdb.Value: + return gdb.parse_and_eval(f"coroutine_table[{self.cid-1}].state") + + @property + def name(self) -> str: + bs: list[int] = [0] * int(gdb.parse_and_eval("sizeof(coroutine_table[0].name)")) + for i, _ in enumerate(bs): + bs[i] = int(gdb.parse_and_eval(f"coroutine_table[{self.cid-1}].name[{i}]")) + return bytes(bs).decode("UTF-8").split("\x00", maxsplit=1)[0] + + def is_selected(self) -> bool: + sp = int(gdb.parse_and_eval("$sp")) + lo = int(gdb.parse_and_eval(f"coroutine_table[{self.id-1}].stack")) + hi = lo + int(gdb.parse_and_eval(f"coroutine_table[{self.id-1}].stack_size")) + return lo <= sp < hi + + def select(self, level: int = -1) -> None: + if self.cr_globals.coroutine_selected: + self.cr_globals.coroutine_selected.cont_env = gdb_setjmp() + + if self.cont_env: + gdb_longjmp(self.cont_env) + else: + env: gdb_JmpBuf + if self == self.cr_globals.coroutine_running: + assert False # self.cont_env should have been set + elif self.state == self.cr_globals.CR_RUNNING: + env = self.cr_globals.readjmp("&coroutine_add_env") + else: + env = self.cr_globals.readjmp(f"&coroutine_table[{self.id-1}].env") + gdb_longjmp(env) + cr_select_top_frame() + + @contextlib.contextmanager + def with_selected(self) -> typing.Iterator[None]: + saved_env = gdb_setjmp() + self.select() + try: + yield + finally: + gdb_longjmp(saved_env) + + +# User-facing commands ######################################################### + + +class CrCommand(gdb.Command): + """Use this command for libcr coroutines.""" + + cr_globals: CrGlobals + + def __init__(self, cr_globals: CrGlobals) -> None: + self.cr_globals = cr_globals + gdb.Command.__init__(self, "cr", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE, True) + + def invoke(self, arg: str, from_tty: bool) -> None: + gdb.execute("help cr") + + +class CrListCommand(gdb.Command): + """List libcr coroutines. + Usage: cr list + + In the output: + - the 'R' marker indicates the currently-running coroutine + - the 'G' marker indicates the coroutine that GDB is viewing; this may be changed with `cr select` + """ + + cr_globals: CrGlobals + + def __init__(self, cr_globals: CrGlobals) -> None: + self.cr_globals = cr_globals + gdb.Command.__init__(self, "cr list", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE) + + def invoke(self, arg: str, from_tty: bool) -> None: + argv = gdb.string_to_argv(arg) + if len(argv) != 0: + raise gdb.GdbError("Usage: cr list") + + rows: list[tuple[str, str, str, str, str]] = [ + ("", "Id", "Name", "State", "Frame") + ] + for cr in self.cr_globals.coroutines: + if cr.state == self.cr_globals.CR_NONE: + continue + rows += [ + ( + "".join( + [ + "R" if cr == self.cr_globals.coroutine_running else " ", + "G" if cr.is_selected() else " ", + ] + ), + str(cr.id), + repr(cr.name), + str(cr.state), + self._pretty_frame(cr, from_tty), + ) + ] + + widths: list[int] = [ + max(len(row[col]) for row in rows) for col in range(len(rows[0])) + ] + + def line(row: tuple[str, str, str, str, str]) -> str: + + def cell(col: int) -> str: + return row[col].ljust(widths[col]) + + return f"{cell(0)} {cell(1)} {cell(2)} {cell(3)} {row[4]}" + + maxline = 0 + if screenwidth := gdb.parameter("width"): + assert isinstance(screenwidth, int) + maxline = max(screenwidth, len(line(rows[0]))) + + for row in rows: + l = line(row) + if maxline and len(l) > maxline: + l = l[:maxline] + print(l) + + def _pretty_frame(self, cr: CrCoroutine, from_tty: bool) -> str: + try: + with cr.with_selected(): + saved_level = gdb.selected_frame().level() + cr_select_top_frame() + full = gdb.execute("frame", from_tty=from_tty, to_string=True) + gdb.execute(f"select-frame level {saved_level}") + except Exception as e: # pylint: disable=broad-exception-caught + full = "#0 err: " + str(e) + line = full.split("\n", maxsplit=1)[0] + return line.split(maxsplit=1)[1] + + +class CrSelectCommand(gdb.Command): + """Select the coroutine that GDB is viewing + Usage: cr select COROUTINE + COROUTINE is either a coroutine ID or coroutine name.""" + + cr_globals: CrGlobals + + def __init__(self, cr_globals: CrGlobals) -> None: + self.cr_globals = cr_globals + gdb.Command.__init__(self, "cr select", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE) + + def invoke(self, arg: str, from_tty: bool) -> None: + argv = gdb.string_to_argv(arg) + if len(argv) != 1: + raise gdb.GdbError("Usage: cr select COROUTINE") + cr = self._find(argv[0]) + cr.select() + gdb.execute("frame") + + def _find(self, name: str) -> CrCoroutine: + if name.isnumeric(): + cid = int(name) + if ( + self.cr_globals.is_valid_cid(cid) + and self.cr_globals.coroutines[cid - 1].state != self.cr_globals.CR_NONE + ): + return self.cr_globals.coroutines[cid - 1] + crs: list[CrCoroutine] = [] + for cr in self.cr_globals.coroutines: + if cr.state != self.cr_globals.CR_NONE and cr.name == name: + crs += [cr] + match len(crs): + case 0: + raise gdb.GdbError(f"No such coroutine: {name!r}") + case 1: + return crs[0] + case _: + raise gdb.GdbError(f"Ambiguous name, must use Id: {name!r}") + + +# Wire it all in ############################################################### + +_cr_globals: CrGlobals | None = None + + +def cr_initialize() -> None: + global _cr_globals + if _cr_globals: + old = _cr_globals + new = CrGlobals() + for i in range(min(len(old.coroutines), len(new.coroutines))): + new.coroutines[i].cont_env = old.coroutines[i].cont_env + old.delete() + _cr_globals = new + else: + _cr_globals = CrGlobals() + CrCommand(_cr_globals) + CrListCommand(_cr_globals) + CrSelectCommand(_cr_globals) + + +def cr_on_new_objfile(event: gdb.Event) -> None: + if any( + objfile.lookup_global_symbol("cr_gdb_readjmp") for objfile in gdb.objfiles() + ): + print("Initializing libcr integration...") + cr_initialize() + gdb.events.new_objfile.disconnect(cr_on_new_objfile) + + +if _cr_globals: + cr_initialize() +else: + gdb.events.new_objfile.connect(cr_on_new_objfile) diff --git a/gdb-helpers/rp2040.py b/gdb-helpers/rp2040.py index 30a936a..f019299 100644 --- a/gdb-helpers/rp2040.py +++ b/gdb-helpers/rp2040.py @@ -1,9 +1,11 @@ # gdb-helpers/rp2040.py - GDB helpers for the RP2040 CPU. # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later -import gdb +import typing + +import gdb # pylint: disable=import-error def read_mmreg(addr: int) -> int: @@ -18,6 +20,23 @@ def fmt32(x: int) -> str: return "0b" + bin(x)[2:].rjust(32, "0") +def box(title: str, content: str) -> str: + width = 80 + + lines = content.split("\n") + while len(lines) and lines[0] == "": + lines = lines[1:] + while len(lines) and lines[-1] == "": + lines = lines[:-1] + lines = ["", *lines, ""] + + ret = "┏━[" + title + "]" + ("━" * (width - len(title) - 5)) + "┓\n" + for line in content.split("\n"): + ret += f"┃ {line:<{width-4}} ┃\n" + ret += "┗" + ("━" * (width - 2)) + "┛" + return ret + + def read_prio(addr: int) -> str: prios: list[int] = 32 * [0] for regnum in range(0, 8): @@ -91,27 +110,23 @@ class RP2040ShowInterrupts(gdb.Command): """Show the RP2040's interrupt state.""" def __init__(self) -> None: - super(RP2040ShowInterrupts, self).__init__( - "rp2040-show-interrupts", gdb.COMMAND_USER - ) + super().__init__("rp2040-show-interrupts", gdb.COMMAND_USER) def invoke(self, arg: str, from_tty: bool) -> None: + self.arm_cortex_m0plus_registers() + self.arm_cortex_m0plus_mmregisters() + self.rp2040_dma_mmregisters() + + def arm_cortex_m0plus_mmregisters(self) -> None: base: int = 0xE0000000 icsr = read_mmreg(base + 0xED04) - psr = read_reg("xPSR") - # ║├┤║├┤├┤║├┤║║├┤├──┤║║║├──┤ - # 10987654321098765432109876543210 (dec bitnum) - # 3 2 1 0 - # ║├┤║├┤├┤║├┤║║├┤├──┤║║║├──┤ - # fedcba9876543210fedcba9876543210 (hex bitnum) - # 1 0 print( - f""" -ARM Cortex-M0+ memory-mapped registers: - + box( + "ARM Cortex-M0+ memory-mapped registers", + f""" clocks╖ ┌SIO SPI┐ ║ │╓QSPI - UART┐│ ║ │║╓bank0 ╓XIP + UART┐│ ║ │║╓GPIO ╓XIP ADC╖ ││ ║ │║║┌DMA ║╓USB I2C┐ ║ ││ ║ │║║│ ┌PIO║║╓PWM RTC╖├┐║┌┤├┐║┌┤║║├┐├──┐║║║┌──┬timers @@ -137,27 +152,144 @@ ICSR : {fmt32(icsr) } Control and State ╓endiannes (0=little, 1=big) vect_key┐ ║ ╓sys_reset_req ┌───────┴──────┐║ ║╓vect_clr_active -AIRCR : {fmt32(read_mmreg(base+0xed0c)) } Application Interrupt and Reset Control +AIRCR : {fmt32(read_mmreg(base+0xed0c)) } Application {{Interrupt+Reset}} Control ╓sleep_deep s_ev_on_pend╖ ║╓sleep_on_exit SCR : {fmt32(read_mmreg(base+0xed10)) } System Control +""", + ) + ) -ARM Cortex-M0+ processor core registers: - + def arm_cortex_m0plus_registers(self) -> None: + psr = read_reg("xPSR") + print( + box( + "ARM Cortex-M0+ processor-core registers", + f""" ╓pm (0=normal, 1=top priority) PRIMASK : {fmt32(read_reg('primask')) } Priority Mask - [C]arry╖╓o[V]erflow - [Z]ero╖║║ ╓[T]humb ╓require [a]lignment - [N]egative╖║║║ ║ exec ║ ┌interrupt - app╫╫╫╢ ╟───────┴──────╢┌────┴──┐ -xPSR : {fmt32(psr) } {{Application,Execution,Interrupt}} Program Status - └────┬──┘ - └{psr&0x1FF} ({exception_names[psr&0x1FF]}) + app exec intr + ┌┴─┐ ┌───────┴──────┐┌───┴───┐ +xPSR : {fmt32(psr) } {{App,Exec,Intr}} Program Status + ║║║║ ║ ║└───┬───┘ + [N]egative╜║║║ ║ ║ └{psr&0x1FF} ({exception_names[psr&0x1FF]}) + [Z]ero╜║║ ╙[T]humb ╙require [a]lignment + [C]arry╜║ + o[V]erflow╜ +""", + ) + ) -""" + def rp2040_dma_mmregisters(self) -> None: + base: int = 0x50000000 + + def fmt12(x: int) -> str: + s = fmt32(x) + return s[:-12] + "_" + s[-12:] + + print( + box( + "RP2040 DMA memory-mapped registers", + f""" + + 8 4 0 + ┌──┴───┴───┤ +INTR : {fmt12(read_mmreg(base + 0x400))} Raw + │ │ │ │ +INTE0: {fmt12(read_mmreg(base + 0x404))} IRQ_DMA_0 Enable +INTF0: {fmt12(read_mmreg(base + 0x408))} IRQ_DMA_0 Force +INTS0: {fmt12(read_mmreg(base + 0x40c))} IRQ_DMA_0 Status + │ │ │ │ +INTE1: {fmt12(read_mmreg(base + 0x414))} IRQ_DMA_1 Enable +INTF1: {fmt12(read_mmreg(base + 0x418))} IRQ_DMA_1 Force +INTS1: {fmt12(read_mmreg(base + 0x41c))} IRQ_DMA_1 Status +""", + ) ) RP2040ShowInterrupts() + + +class RP2040ShowDMA(gdb.Command): + """Show the RP2040's DMA control registers.""" + + def __init__(self) -> None: + super().__init__("rp2040-show-dma", gdb.COMMAND_USER) + + def invoke(self, arg: str, from_tty: bool) -> None: + base: int = 0x50000000 + u32_size: int = 4 + + nchan = read_mmreg(base + 0x448) + + def chreg( + ch: int, + name: typing.Literal[ + "read_addr", + "write_addr", + "trans_count", + "ctrl", + "dbg_ctdreq", + "dbg_tcr", + ], + ) -> int: + fieldcnt: int = 4 * 4 + fieldnum: int + debug = False + match name: + case "read_addr": + fieldnum = 0 + case "write_addr": + fieldnum = 1 + case "trans_count": + fieldnum = 2 + case "ctrl": + fieldnum = 4 + case "dbg_ctdreq": + fieldnum = 0 + debug = True + case "dbg_tcr": + fieldnum = 1 + debug = True + return read_mmreg( + base + + (0x800 if debug else 0) + + (ch * u32_size * fieldcnt) + + (u32_size * fieldnum) + ) + + def ctrl(ch: int) -> str: + s = fmt32(chreg(ch, "ctrl")) + return s[:10] + "_" + s[10:] + + def chaddr(ch: int, name: typing.Literal["read", "write"]) -> str: + val = chreg(ch, name + "_addr") # type: ignore + if val == 0: + return "NULL " + return f"0x{val:08x}" + + ret = """ + ╓sniff_enable + ║╓bswap + ║║╓irq_quiet + ║║║ ┌treq_sel + ║║║ │ ┌chain_to + ║║║ │ │ ╓ring_sel + ║║║ │ │ ║ ┌ring_size + ║║║ │ │ ║ │ ╓incr_write + busy╖ ║║║ │ │ ║ │ ║╓incr_read +write_err╖ ║ ║║║ │ │ ║ │ ║║┌data_size +read_err╖║ ║ ║║║ │ │ ║ │ ║║│ ╓high_priority +ahb_err╖║║ ║ ║║║ │ │ ║ │ ║║│ ║╓enable + ║║║ ║ ║║║ │ │ ║ │ ║║│ ║║ trans_cnt + ║║║ ║ ║║║┌─┴──┐┌┴─┐║┌┴─┐║║├┐║║ read_addr write_addr cur/reload +""" + for ch in range(0, nchan): + ret += f"{ch: 3}: {ctrl(ch)} {chaddr(ch, 'read')} {chaddr(ch, 'write')} {chreg(ch, 'trans_count')}/{chreg(ch, 'dbg_tcr')}\n" + print(box("RP2040 DMA channels", ret)) + + +RP2040ShowDMA() @@ -1,17 +1,47 @@ /* lib9p/9p.c - Base 9P protocol utilities for both clients and servers * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #include <inttypes.h> /* for PRIu{n} */ #include <stdarg.h> /* for va_* */ -#include <stdio.h> /* for vsnprintf() */ #include <string.h> /* for strncpy() */ +#include <libfmt/fmt.h> /* for fmt_vsnprintf() */ + #include <lib9p/9p.h> -#include "internal.h" +/* strings ********************************************************************/ + +struct lib9p_s lib9p_str(char *s) { + if (!s) + return (struct lib9p_s){0}; + return (struct lib9p_s){ + .len = strlen(s), + .utf8 = s, + }; +} +struct lib9p_s lib9p_strn(char *s, size_t maxlen) { + if (maxlen == 0 || !s) + return (struct lib9p_s){0}; + return (struct lib9p_s){ + .len = strnlen(s, maxlen), + .utf8 = s, + }; +} +struct lib9p_s lib9p_str_slice(struct lib9p_s s, uint16_t beg, uint16_t end) { + assert(s.len == 0 || s.utf8); + assert(beg <= end && end <= s.len); + return (struct lib9p_s){ + .len = end - beg, + .utf8 = &s.utf8[beg], + }; +} +bool lib9p_str_eq(struct lib9p_s a, struct lib9p_s b) { + return a.len == b.len && + (a.len == 0 || memcmp(a.utf8, b.utf8, a.len) == 0); +} /* ctx ************************************************************************/ @@ -28,7 +58,7 @@ bool lib9p_ctx_has_error(struct lib9p_ctx *ctx) { return ctx->err_msg[0]; } -int lib9p_error(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *msg) { +int lib9p_error(struct lib9p_ctx *ctx, lib9p_errno_t linux_errno, char const *msg) { if (lib9p_ctx_has_error(ctx)) return -1; strncpy(ctx->err_msg, msg, sizeof(ctx->err_msg)); @@ -43,14 +73,14 @@ int lib9p_error(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *msg) { return -1; } -int lib9p_errorf(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *fmt, ...) { +int lib9p_errorf(struct lib9p_ctx *ctx, lib9p_errno_t linux_errno, char const *fmt, ...) { int n; va_list args; if (lib9p_ctx_has_error(ctx)) return -1; va_start(args, fmt); - n = vsnprintf(ctx->err_msg, sizeof(ctx->err_msg), fmt, args); + n = fmt_vsnprintf(ctx->err_msg, sizeof(ctx->err_msg), fmt, args); va_end(args); if ((size_t)(n+1) < sizeof(ctx->err_msg)) memset(&ctx->err_msg[n+1], 0, sizeof(ctx->err_msg)-(n+1)); @@ -63,119 +93,3 @@ int lib9p_errorf(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *fmt, . return -1; } - -const char *lib9p_msg_type_str(struct lib9p_ctx *ctx, enum lib9p_msg_type typ) { - assert(0 <= typ && typ <= 0xFF); - return _lib9p_versions[ctx->version].msgs[typ].name; -} - -/* main message functions *****************************************************/ - -ssize_t lib9p_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) { - /* Inspect the first 5 bytes ourselves. */ - struct _validate_ctx subctx = { - .ctx = ctx, - .net_size = decode_u32le(net_bytes), - .net_bytes = net_bytes, - - .net_offset = 0, - .host_extra = 0, - }; - if (subctx.net_size < 5) - return lib9p_error(ctx, LINUX_EBADMSG, "message is impossibly short"); - uint8_t typ = net_bytes[4]; - struct _table_msg table = _lib9p_versions[ctx->version].msgs[typ]; - if (!table.validate) - return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "unknown message type: %s (protocol_version=%s)", - lib9p_msg_type_str(ctx, typ), lib9p_version_str(ctx->version)); - - /* Now use the message-type-specific table to process the whole thing. */ - if (table.validate(&subctx)) - return -1; - assert(subctx.net_offset <= subctx.net_size); - if (subctx.net_offset < subctx.net_size) - return lib9p_errorf(ctx, LINUX_EBADMSG, "message has %"PRIu32" extra bytes", - subctx.net_size - subctx.net_offset); - - /* Return. */ - ssize_t ret; - if (__builtin_add_overflow(table.basesize, subctx.host_extra, &ret)) - return lib9p_error(ctx, LINUX_EMSGSIZE, "unmarshalled payload overflows SSIZE_MAX"); - return ret; -} - -void lib9p_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, - enum lib9p_msg_type *ret_typ, void *ret_body) { - struct _unmarshal_ctx subctx = { - .ctx = ctx, - .net_bytes = net_bytes, - - .net_offset = 0, - }; - - *ret_typ = net_bytes[4]; - struct _table_msg table = _lib9p_versions[ctx->version].msgs[*ret_typ]; - subctx.extra = ret_body + table.basesize; - table.unmarshal(&subctx, ret_body); -} - -bool lib9p_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, - uint8_t *ret_bytes) { - struct _marshal_ctx subctx = { - .ctx = ctx, - .net_bytes = ret_bytes, - .net_offset = 0, - }; - - struct _table_msg table = _lib9p_versions[ctx->version].msgs[typ]; - return table.marshal(&subctx, body); -} - -/* `struct lib9p_stat` helpers ************************************************/ - -bool lib9p_validate_stat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, - uint32_t *ret_net_size, ssize_t *ret_host_size) { - struct _validate_ctx subctx = { - .ctx = ctx, - .net_size = net_size, - .net_bytes = net_bytes, - - .net_offset = 0, - .host_extra = 0, - }; - if (_lib9p_validate_stat(&subctx)) - return true; - if (ret_net_size) - *ret_net_size = subctx.net_offset; - if (ret_host_size) - if (__builtin_add_overflow(sizeof(struct lib9p_stat), subctx.host_extra, ret_host_size)) - return lib9p_error(ctx, LINUX_EMSGSIZE, "unmarshalled stat object overflows SSIZE_MAX"); - return false; -} - -uint32_t lib9p_unmarshal_stat(struct lib9p_ctx *ctx, uint8_t *net_bytes, - struct lib9p_stat *ret_obj, void *ret_extra) { - struct _unmarshal_ctx subctx = { - .ctx = ctx, - .net_bytes = net_bytes, - .net_offset = 0, - - .extra = ret_extra, - }; - _lib9p_unmarshal_stat(&subctx, ret_obj); - return subctx.net_offset; -} - -uint32_t lib9p_marshal_stat(struct lib9p_ctx *ctx, uint32_t max_net_size, struct lib9p_stat *obj, - uint8_t *ret_bytes) { - struct lib9p_ctx _ctx = *ctx; - _ctx.max_msg_size = max_net_size; - struct _marshal_ctx subctx = { - .ctx = &_ctx, - .net_bytes = ret_bytes, - .net_offset = 0, - }; - if (_lib9p_marshal_stat(&subctx, obj)) - return 0; - return subctx.net_offset; -} diff --git a/lib9p/9p.generated.c b/lib9p/9p.generated.c index f3a907b..b58a485 100644 --- a/lib9p/9p.generated.c +++ b/lib9p/9p.generated.c @@ -1,4 +1,4 @@ -/* Generated by `lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */ +/* Generated by `lib9p/proto.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2003-9P2000.p9p.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2010-9P2000.L.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */ #include <stdbool.h> #include <stddef.h> /* for size_t */ @@ -6,2160 +6,7376 @@ #include <string.h> /* for memset() */ #include <libmisc/assert.h> +#include <libmisc/endian.h> #include <lib9p/9p.h> -#include "internal.h" - -/* strings ********************************************************************/ +#include "tables.h" +#include "utf8.h" + +/* libobj vtables *************************************************************/ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_tag_t, lib9p_tag, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_fid_t, lib9p_fid, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_s, lib9p_s, static); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_dm_t, lib9p_dm, static); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_qt_t, lib9p_qt, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_qid, lib9p_qid, static); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_stat, lib9p_stat, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_o_t, lib9p_o, static); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tversion, lib9p_msg_Tversion, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rversion, lib9p_msg_Rversion, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tauth, lib9p_msg_Tauth, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rauth, lib9p_msg_Rauth, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tattach, lib9p_msg_Tattach, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rattach, lib9p_msg_Rattach, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rerror, lib9p_msg_Rerror, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tflush, lib9p_msg_Tflush, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rflush, lib9p_msg_Rflush, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Twalk, lib9p_msg_Twalk, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rwalk, lib9p_msg_Rwalk, static); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Topen, lib9p_msg_Topen, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Ropen, lib9p_msg_Ropen, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tcreate, lib9p_msg_Tcreate, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rcreate, lib9p_msg_Rcreate, static); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tread, lib9p_msg_Tread, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rread, lib9p_msg_Rread, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Twrite, lib9p_msg_Twrite, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rwrite, lib9p_msg_Rwrite, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tclunk, lib9p_msg_Tclunk, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rclunk, lib9p_msg_Rclunk, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tremove, lib9p_msg_Tremove, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rremove, lib9p_msg_Rremove, static); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tstat, lib9p_msg_Tstat, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rstat, lib9p_msg_Rstat, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Twstat, lib9p_msg_Twstat, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rwstat, lib9p_msg_Rwstat, static); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_p9p +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Topenfd, lib9p_msg_Topenfd, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Ropenfd, lib9p_msg_Ropenfd, static); +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_nuid_t, lib9p_nuid, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_errno_t, lib9p_errno, static); +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_super_magic_t, lib9p_super_magic, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_lo_t, lib9p_lo, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_dt_t, lib9p_dt, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_mode_t, lib9p_mode, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_b4_t, lib9p_b4, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_getattr_t, lib9p_getattr, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_setattr_t, lib9p_setattr, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_lock_type_t, lib9p_lock_type, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_lock_flags_t, lib9p_lock_flags, static); +LO_IMPLEMENTATION_C(fmt_formatter, lib9p_lock_status_t, lib9p_lock_status, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rlerror, lib9p_msg_Rlerror, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tstatfs, lib9p_msg_Tstatfs, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rstatfs, lib9p_msg_Rstatfs, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tlopen, lib9p_msg_Tlopen, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rlopen, lib9p_msg_Rlopen, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tlcreate, lib9p_msg_Tlcreate, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rlcreate, lib9p_msg_Rlcreate, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tsymlink, lib9p_msg_Tsymlink, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rsymlink, lib9p_msg_Rsymlink, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tmknod, lib9p_msg_Tmknod, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rmknod, lib9p_msg_Rmknod, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Trename, lib9p_msg_Trename, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rrename, lib9p_msg_Rrename, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Treadlink, lib9p_msg_Treadlink, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rreadlink, lib9p_msg_Rreadlink, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tgetattr, lib9p_msg_Tgetattr, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rgetattr, lib9p_msg_Rgetattr, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tsetattr, lib9p_msg_Tsetattr, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rsetattr, lib9p_msg_Rsetattr, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Txattrwalk, lib9p_msg_Txattrwalk, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rxattrwalk, lib9p_msg_Rxattrwalk, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Txattrcreate, lib9p_msg_Txattrcreate, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rxattrcreate, lib9p_msg_Rxattrcreate, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Treaddir, lib9p_msg_Treaddir, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rreaddir, lib9p_msg_Rreaddir, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tfsync, lib9p_msg_Tfsync, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rfsync, lib9p_msg_Rfsync, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tlock, lib9p_msg_Tlock, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rlock, lib9p_msg_Rlock, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tgetlock, lib9p_msg_Tgetlock, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rgetlock, lib9p_msg_Rgetlock, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tlink, lib9p_msg_Tlink, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rlink, lib9p_msg_Rlink, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tmkdir, lib9p_msg_Tmkdir, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rmkdir, lib9p_msg_Rmkdir, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Trenameat, lib9p_msg_Trenameat, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rrenameat, lib9p_msg_Rrenameat, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tunlinkat, lib9p_msg_Tunlinkat, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Runlinkat, lib9p_msg_Runlinkat, static); +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tsession, lib9p_msg_Tsession, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rsession, lib9p_msg_Rsession, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tsread, lib9p_msg_Tsread, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rsread, lib9p_msg_Rsread, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tswrite, lib9p_msg_Tswrite, static); +LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rswrite, lib9p_msg_Rswrite, static); +#endif /* CONFIG_9P_ENABLE_9P2000_e */ -static const char *version_strs[LIB9P_VER_NUM] = { - [LIB9P_VER_unknown] = "unknown", +/* utilities ******************************************************************/ +#if CONFIG_9P_ENABLE_9P2000 + #define _is_ver_9P2000(v) (v == LIB9P_VER_9P2000) +#else + #define _is_ver_9P2000(v) false +#endif +#if CONFIG_9P_ENABLE_9P2000_L + #define _is_ver_9P2000_L(v) (v == LIB9P_VER_9P2000_L) +#else + #define _is_ver_9P2000_L(v) false +#endif +#if CONFIG_9P_ENABLE_9P2000_e + #define _is_ver_9P2000_e(v) (v == LIB9P_VER_9P2000_e) +#else + #define _is_ver_9P2000_e(v) false +#endif +#if CONFIG_9P_ENABLE_9P2000_p9p + #define _is_ver_9P2000_p9p(v) (v == LIB9P_VER_9P2000_p9p) +#else + #define _is_ver_9P2000_p9p(v) false +#endif +#if CONFIG_9P_ENABLE_9P2000_u + #define _is_ver_9P2000_u(v) (v == LIB9P_VER_9P2000_u) +#else + #define _is_ver_9P2000_u(v) false +#endif + +/** + * is_ver(ctx, ver) is essentially `(ctx->version == LIB9P_VER_##ver)`, but + * compiles correctly (to `false`) even if `LIB9P_VER_##ver` isn't defined + * (because `!CONFIG_9P_ENABLE_##ver`). This is useful when `||`ing + * several version checks together. + */ +#define is_ver(ctx, ver) _is_ver_##ver((ctx)->version) + +/* bitmasks *******************************************************************/ + +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static const lib9p_dm_t dm_masks[LIB9P_VER_NUM] = { #if CONFIG_9P_ENABLE_9P2000 - [LIB9P_VER_9P2000] = "9P2000", + [LIB9P_VER_9P2000] = 0b11111100000000000000000111111111, #endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ #if CONFIG_9P_ENABLE_9P2000_e - [LIB9P_VER_9P2000_e] = "9P2000.e", + [LIB9P_VER_9P2000_e] = 0b11111100000000000000000111111111, #endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = 0b11111100000000000000000111111111, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ #if CONFIG_9P_ENABLE_9P2000_u - [LIB9P_VER_9P2000_u] = "9P2000.u", + [LIB9P_VER_9P2000_u] = 0b11111100101111000000000111111111, #endif /* CONFIG_9P_ENABLE_9P2000_u */ }; -const char *lib9p_version_str(enum lib9p_version ver) { - assert(0 <= ver && ver < LIB9P_VER_NUM); - return version_strs[ver]; -} - -/* validate_* *****************************************************************/ - -ALWAYS_INLINE static bool _validate_size_net(struct _validate_ctx *ctx, uint32_t n) { - if (__builtin_add_overflow(ctx->net_offset, n, &ctx->net_offset)) - /* If needed-net-size overflowed uint32_t, then - * there's no way that actual-net-size will live up to - * that. */ - return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content"); - if (ctx->net_offset > ctx->net_size) - return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content"); - return false; -} - -ALWAYS_INLINE static bool _validate_size_host(struct _validate_ctx *ctx, size_t n) { - if (__builtin_add_overflow(ctx->host_extra, n, &ctx->host_extra)) - /* If needed-host-size overflowed size_t, then there's - * no way that actual-net-size will live up to - * that. */ - return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content"); - return false; -} - -ALWAYS_INLINE static bool _validate_list(struct _validate_ctx *ctx, - size_t cnt, - _validate_fn_t item_fn, size_t item_host_size) { - for (size_t i = 0; i < cnt; i++) - if (_validate_size_host(ctx, item_host_size) || item_fn(ctx)) - return true; - return false; -} - -#define validate_1(ctx) _validate_size_net(ctx, 1) -#define validate_2(ctx) _validate_size_net(ctx, 2) -#define validate_4(ctx) _validate_size_net(ctx, 4) -#define validate_8(ctx) _validate_size_net(ctx, 8) - -#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u -ALWAYS_INLINE static bool validate_tag(struct _validate_ctx *ctx) { - return validate_2(ctx); -} - -ALWAYS_INLINE static bool validate_fid(struct _validate_ctx *ctx) { - return validate_4(ctx); -} +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static const lib9p_qt_t qt_masks[LIB9P_VER_NUM] = { +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = 0b11111100, +#endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = 0b11111100, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = 0b11111100, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = 0b11111100, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_u + [LIB9P_VER_9P2000_u] = 0b11111110, +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; -ALWAYS_INLINE static bool validate_d(struct _validate_ctx *ctx) { - uint32_t base_offset = ctx->net_offset; - if (validate_4(ctx)) - return true; - uint32_t len = decode_u32le(&ctx->net_bytes[base_offset]); - return _validate_size_net(ctx, len) || _validate_size_host(ctx, len); -} +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static const lib9p_o_t o_masks[LIB9P_VER_NUM] = { +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = 0b01010011, +#endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = 0b00000000, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = 0b01010011, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = 0b01010011, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_u + [LIB9P_VER_9P2000_u] = 0b01010011, +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; -ALWAYS_INLINE static bool validate_s(struct _validate_ctx *ctx) { - uint32_t base_offset = ctx->net_offset; - if (validate_2(ctx)) - return true; - uint16_t len = decode_u16le(&ctx->net_bytes[base_offset]); - if (_validate_size_net(ctx, len) || _validate_size_host(ctx, ((size_t)len)+1)) - return true; - if (!is_valid_utf8_without_nul(&ctx->net_bytes[base_offset+2], len)) - return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message contains invalid UTF-8"); - return false; -} +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +static const lib9p_lo_t lo_masks[LIB9P_VER_NUM] = { +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000 */ + [LIB9P_VER_9P2000_L] = 0b00000000000111111111111111000011, +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_u + [LIB9P_VER_9P2000_u] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; -static const lib9p_dm_t dm_masks[LIB9P_VER_NUM] = { +static const lib9p_mode_t mode_masks[LIB9P_VER_NUM] = { #if CONFIG_9P_ENABLE_9P2000 - [LIB9P_VER_9P2000] = 0b11101100000000000000000111111111, + [LIB9P_VER_9P2000] = 0b00000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000 */ + [LIB9P_VER_9P2000_L] = 0b00000000000000001111111111111111, #if CONFIG_9P_ENABLE_9P2000_e - [LIB9P_VER_9P2000_e] = 0b11101100000000000000000111111111, + [LIB9P_VER_9P2000_e] = 0b00000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ #if CONFIG_9P_ENABLE_9P2000_u - [LIB9P_VER_9P2000_u] = 0b11101100101111000000000111111111, + [LIB9P_VER_9P2000_u] = 0b00000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000_u */ }; -ALWAYS_INLINE static bool validate_dm(struct _validate_ctx *ctx) { - if (validate_4(ctx)) - return true; - lib9p_dm_t mask = dm_masks[ctx->ctx->version]; - lib9p_dm_t val = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); - if (val & ~mask) - return lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "unknown bits in dm bitfield: %#04"PRIx32, val & ~mask); - return false; -} -static const lib9p_qt_t qt_masks[LIB9P_VER_NUM] = { +static const lib9p_getattr_t getattr_masks[LIB9P_VER_NUM] = { #if CONFIG_9P_ENABLE_9P2000 - [LIB9P_VER_9P2000] = 0b11101100, + [LIB9P_VER_9P2000] = 0b0000000000000000000000000000000000000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000 */ + [LIB9P_VER_9P2000_L] = 0b0000000000000000000000000000000000000000000000000011111111111111, #if CONFIG_9P_ENABLE_9P2000_e - [LIB9P_VER_9P2000_e] = 0b11101100, + [LIB9P_VER_9P2000_e] = 0b0000000000000000000000000000000000000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = 0b0000000000000000000000000000000000000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ #if CONFIG_9P_ENABLE_9P2000_u - [LIB9P_VER_9P2000_u] = 0b11101110, + [LIB9P_VER_9P2000_u] = 0b0000000000000000000000000000000000000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000_u */ }; -ALWAYS_INLINE static bool validate_qt(struct _validate_ctx *ctx) { - if (validate_1(ctx)) - return true; - lib9p_qt_t mask = qt_masks[ctx->ctx->version]; - lib9p_qt_t val = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); - if (val & ~mask) - return lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#01"PRIx8, val & ~mask); - return false; -} - -ALWAYS_INLINE static bool validate_qid(struct _validate_ctx *ctx) { - return false - || validate_qt(ctx) - || validate_4(ctx) - || validate_8(ctx) - ; -} - -ALWAYS_INLINE static bool validate_stat(struct _validate_ctx *ctx) { - uint16_t stat_size; - uint32_t _kern_type_offset; - return false - || (validate_2(ctx) || ({ stat_size = decode_u16le(&ctx->net_bytes[ctx->net_offset-2]); false; })) - || ({ _kern_type_offset = ctx->net_offset; validate_2(ctx); }) - || validate_4(ctx) - || validate_qid(ctx) - || validate_dm(ctx) - || validate_4(ctx) - || validate_4(ctx) - || validate_8(ctx) - || validate_s(ctx) - || validate_s(ctx) - || validate_s(ctx) - || validate_s(ctx) + +static const lib9p_setattr_t setattr_masks[LIB9P_VER_NUM] = { +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000 */ + [LIB9P_VER_9P2000_L] = 0b00000000000000000000000111111111, +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ #if CONFIG_9P_ENABLE_9P2000_u - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && validate_s(ctx) ) - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && validate_4(ctx) ) - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && validate_4(ctx) ) - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && validate_4(ctx) ) + [LIB9P_VER_9P2000_u] = 0b00000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000_u */ - || ({ uint32_t exp = ctx->net_offset - _kern_type_offset; (((uint32_t)stat_size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "stat_size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)stat_size, exp); }) - ; -} +}; -static const lib9p_o_t o_masks[LIB9P_VER_NUM] = { +static const lib9p_lock_flags_t lock_flags_masks[LIB9P_VER_NUM] = { #if CONFIG_9P_ENABLE_9P2000 - [LIB9P_VER_9P2000] = 0b01010011, + [LIB9P_VER_9P2000] = 0b00000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000 */ + [LIB9P_VER_9P2000_L] = 0b00000000000000000000000000000011, #if CONFIG_9P_ENABLE_9P2000_e - [LIB9P_VER_9P2000_e] = 0b01010011, + [LIB9P_VER_9P2000_e] = 0b00000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = 0b00000000000000000000000000000000, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ #if CONFIG_9P_ENABLE_9P2000_u - [LIB9P_VER_9P2000_u] = 0b01010011, + [LIB9P_VER_9P2000_u] = 0b00000000000000000000000000000000, #endif /* CONFIG_9P_ENABLE_9P2000_u */ }; -ALWAYS_INLINE static bool validate_o(struct _validate_ctx *ctx) { - if (validate_1(ctx)) - return true; - lib9p_o_t mask = o_masks[ctx->ctx->version]; - lib9p_o_t val = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); - if (val & ~mask) - return lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "unknown bits in o bitfield: %#01"PRIx8, val & ~mask); - return false; -} - -FLATTEN static bool validate_Tversion(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_4(ctx) - || validate_s(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 100; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rversion(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_4(ctx) - || validate_s(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 101; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tauth(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || validate_s(ctx) - || validate_s(ctx) +#endif /* CONFIG_9P_ENABLE_9P2000_L */ + +/* validate_* *****************************************************************/ + +#define VALIDATE_NET_BYTES(n) \ + if (__builtin_add_overflow(net_offset, n, &net_offset)) \ + /* If needed-net-size overflowed uint32_t, then \ + * there's no way that actual-net-size will live up to \ + * that. */ \ + return lib9p_error(ctx, LINUX_EBADMSG, "message is too short for content"); \ + if (net_offset > net_size) \ + return lib9p_errorf(ctx, LINUX_EBADMSG, "message is too short for content (%"PRIu32" > %"PRIu32") @ %d", net_offset, net_size, __LINE__); +#define VALIDATE_NET_UTF8(n) \ + { \ + size_t len = n; \ + VALIDATE_NET_BYTES(len); \ + if (!is_valid_utf8_without_nul(&net_bytes[net_offset-len], len)) \ + return lib9p_error(ctx, LINUX_EBADMSG, "message contains invalid UTF-8"); \ + } +#define RESERVE_HOST_BYTES(n) \ + if (__builtin_add_overflow(host_size, n, &host_size)) \ + /* If needed-host-size overflowed ssize_t, then there's \ + * no way that actual-net-size will live up to \ + * that. */ \ + return lib9p_error(ctx, LINUX_EBADMSG, "message is too short for content"); +#define GET_U8LE(off) (net_bytes[off]) +#define GET_U16LE(off) uint16le_decode(&net_bytes[off]) +#define GET_U32LE(off) uint32le_decode(&net_bytes[off]) +#define GET_U64LE(off) uint64le_decode(&net_bytes[off]) +#define LAST_U8LE() GET_U8LE(net_offset-1) +#define LAST_U16LE() GET_U16LE(net_offset-2) +#define LAST_U32LE() GET_U32LE(net_offset-4) +#define LAST_U64LE() GET_U64LE(net_offset-8) + +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static ssize_t validate_stat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_stat); + uint32_t offsetof_stat_size = net_offset + 0; + uint32_t offsetof_kern_type = net_offset + 2; + uint32_t offsetof_file_qid_type = net_offset + 8; + VALIDATE_NET_BYTES(21); + if (GET_U8LE(offsetof_file_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_file_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_file_mode = net_offset + 0; + VALIDATE_NET_BYTES(22); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); #if CONFIG_9P_ENABLE_9P2000_u - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && validate_4(ctx) ) + if (is_ver(ctx, 9P2000_u)) { + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(12); + } #endif /* CONFIG_9P_ENABLE_9P2000_u */ - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 102; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rauth(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_qid(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 103; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tattach(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || validate_fid(ctx) - || validate_s(ctx) - || validate_s(ctx) + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_stat_size) != (uint32_t)(offsetof_end - offsetof_kern_type)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "stat->stat_size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_stat_size), (uint32_t)(offsetof_end - offsetof_kern_type)); + if (GET_U32LE(offsetof_file_mode) & ~dm_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in dm bitfield: %#08"PRIx32, + GET_U32LE(offsetof_file_mode) & ~dm_masks[ctx->version]); + if (ret_net_size) + *ret_net_size = net_offset; + return (ssize_t)host_size; +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static ssize_t validate_Tversion(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tversion); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tversion->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(100)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tversion->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(100)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rversion(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rversion); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rversion->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(101)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rversion->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(101)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tauth(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tauth); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + if (( is_ver(ctx, 9P2000_L) || is_ver(ctx, 9P2000_u) )) { + VALIDATE_NET_BYTES(4); + } +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tauth->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(102)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tauth->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(102)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rauth(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rauth); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_aqid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_aqid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_aqid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rauth->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(103)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rauth->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(103)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tattach(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tattach); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(17); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + if (( is_ver(ctx, 9P2000_L) || is_ver(ctx, 9P2000_u) )) { + VALIDATE_NET_BYTES(4); + } +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tattach->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(104)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tattach->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(104)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rattach(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rattach); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rattach->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(105)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rattach->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(105)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rerror(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rerror); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(9); + VALIDATE_NET_UTF8(LAST_U16LE()); +#if CONFIG_9P_ENABLE_9P2000_u + if (is_ver(ctx, 9P2000_u)) { + VALIDATE_NET_BYTES(4); + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rerror->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(107)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rerror->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(107)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tflush(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tflush); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 9; + VALIDATE_NET_BYTES(9); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tflush->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(108)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tflush->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(108)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rflush(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rflush); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rflush->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(109)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rflush->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(109)); + return (ssize_t)host_size; +} + +static ssize_t validate_Twalk(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Twalk); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_nwname = net_offset + 15; + VALIDATE_NET_BYTES(17); + for (uint16_t i = 0, cnt = LAST_U16LE(); i < cnt; i++) { + RESERVE_HOST_BYTES(sizeof(struct lib9p_s)); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + } + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twalk->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(110)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twalk->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(110)); + if ((uint16_t)GET_U16LE(offsetof_nwname) > (uint16_t)(16)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twalk->nwname value is too large: %"PRIu16" > %"PRIu16, + (uint16_t)GET_U16LE(offsetof_nwname), (uint16_t)(16)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rwalk(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rwalk); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_nwqid = net_offset + 7; + VALIDATE_NET_BYTES(9); + for (uint16_t i = 0, cnt = LAST_U16LE(); i < cnt; i++) { + RESERVE_HOST_BYTES(sizeof(struct lib9p_qid)); + uint32_t offsetof_wqid_type = net_offset + 0; + VALIDATE_NET_BYTES(13); + if (GET_U8LE(offsetof_wqid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_wqid_type) & ~qt_masks[ctx->version]); + } + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rwalk->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(111)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rwalk->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(111)); + if ((uint16_t)GET_U16LE(offsetof_nwqid) > (uint16_t)(16)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rwalk->nwqid value is too large: %"PRIu16" > %"PRIu16, + (uint16_t)GET_U16LE(offsetof_nwqid), (uint16_t)(16)); + return (ssize_t)host_size; +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static ssize_t validate_Topen(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Topen); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_mode = net_offset + 11; + uint32_t offsetof_end = net_offset + 12; + VALIDATE_NET_BYTES(12); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Topen->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(112)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Topen->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(112)); + if (GET_U8LE(offsetof_mode) & ~o_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in o bitfield: %#02"PRIx8, + GET_U8LE(offsetof_mode) & ~o_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Ropen(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Ropen); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 4; + VALIDATE_NET_BYTES(4); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Ropen->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(113)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Ropen->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(113)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tcreate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tcreate); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_perm = net_offset + 0; + uint32_t offsetof_mode = net_offset + 4; + uint32_t offsetof_end = net_offset + 5; + VALIDATE_NET_BYTES(5); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tcreate->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(114)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tcreate->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(114)); + if (GET_U32LE(offsetof_perm) & ~dm_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in dm bitfield: %#08"PRIx32, + GET_U32LE(offsetof_perm) & ~dm_masks[ctx->version]); + if (GET_U8LE(offsetof_mode) & ~o_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in o bitfield: %#02"PRIx8, + GET_U8LE(offsetof_mode) & ~o_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Rcreate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rcreate); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 4; + VALIDATE_NET_BYTES(4); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rcreate->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(115)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rcreate->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(115)); + return (ssize_t)host_size; +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static ssize_t validate_Tread(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tread); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_offset = net_offset + 11; + uint32_t offsetof_count = net_offset + 19; + uint32_t offsetof_end = net_offset + 23; + VALIDATE_NET_BYTES(23); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tread->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(116)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tread->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(116)); + if ((uint64_t)GET_U64LE(offsetof_offset) > (uint64_t)(INT64_MAX)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tread->offset value is too large: %"PRIu64" > %"PRIu64, + (uint64_t)GET_U64LE(offsetof_offset), (uint64_t)(INT64_MAX)); + if ((uint32_t)GET_U32LE(offsetof_count) > (uint32_t)(INT32_MAX)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tread->count value is too large: %"PRIu32" > %"PRIu32, + (uint32_t)GET_U32LE(offsetof_count), (uint32_t)(INT32_MAX)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rread(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rread); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_count = net_offset + 7; + VALIDATE_NET_BYTES(11); + VALIDATE_NET_BYTES(LAST_U32LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rread->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(117)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rread->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(117)); + if ((uint32_t)GET_U32LE(offsetof_count) > (uint32_t)(INT32_MAX)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rread->count value is too large: %"PRIu32" > %"PRIu32, + (uint32_t)GET_U32LE(offsetof_count), (uint32_t)(INT32_MAX)); + return (ssize_t)host_size; +} + +static ssize_t validate_Twrite(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Twrite); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_offset = net_offset + 11; + uint32_t offsetof_count = net_offset + 19; + VALIDATE_NET_BYTES(23); + VALIDATE_NET_BYTES(LAST_U32LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twrite->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(118)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twrite->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(118)); + if ((uint64_t)GET_U64LE(offsetof_offset) > (uint64_t)(INT64_MAX)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twrite->offset value is too large: %"PRIu64" > %"PRIu64, + (uint64_t)GET_U64LE(offsetof_offset), (uint64_t)(INT64_MAX)); + if ((uint32_t)GET_U32LE(offsetof_count) > (uint32_t)(INT32_MAX)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twrite->count value is too large: %"PRIu32" > %"PRIu32, + (uint32_t)GET_U32LE(offsetof_count), (uint32_t)(INT32_MAX)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rwrite(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rwrite); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_count = net_offset + 7; + uint32_t offsetof_end = net_offset + 11; + VALIDATE_NET_BYTES(11); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rwrite->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(119)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rwrite->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(119)); + if ((uint32_t)GET_U32LE(offsetof_count) > (uint32_t)(INT32_MAX)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rwrite->count value is too large: %"PRIu32" > %"PRIu32, + (uint32_t)GET_U32LE(offsetof_count), (uint32_t)(INT32_MAX)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tclunk(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tclunk); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 11; + VALIDATE_NET_BYTES(11); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tclunk->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(120)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tclunk->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(120)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rclunk(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rclunk); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rclunk->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(121)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rclunk->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(121)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tremove(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tremove); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 11; + VALIDATE_NET_BYTES(11); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tremove->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(122)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tremove->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(122)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rremove(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rremove); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rremove->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(123)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rremove->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(123)); + return (ssize_t)host_size; +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static ssize_t validate_Tstat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tstat); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 11; + VALIDATE_NET_BYTES(11); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tstat->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(124)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tstat->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(124)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rstat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rstat); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_nstat = net_offset + 7; + uint32_t offsetof_stat = net_offset + 9; + uint32_t offsetof_stat_stat_size = net_offset + 9; + uint32_t offsetof_stat_kern_type = net_offset + 11; + uint32_t offsetof_stat_file_qid_type = net_offset + 17; + VALIDATE_NET_BYTES(30); + if (GET_U8LE(offsetof_stat_file_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_stat_file_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_stat_file_mode = net_offset + 0; + VALIDATE_NET_BYTES(22); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); #if CONFIG_9P_ENABLE_9P2000_u - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && validate_4(ctx) ) + if (is_ver(ctx, 9P2000_u)) { + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(12); + } #endif /* CONFIG_9P_ENABLE_9P2000_u */ - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 104; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rattach(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_qid(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 105; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rerror(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_s(ctx) + uint32_t offsetof_stat_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_stat_stat_size) != (uint32_t)(offsetof_stat_end - offsetof_stat_kern_type)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rstat->stat.stat_size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_stat_stat_size), (uint32_t)(offsetof_stat_end - offsetof_stat_kern_type)); + if (GET_U32LE(offsetof_stat_file_mode) & ~dm_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in dm bitfield: %#08"PRIx32, + GET_U32LE(offsetof_stat_file_mode) & ~dm_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rstat->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(125)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rstat->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(125)); + if ((uint32_t)GET_U32LE(offsetof_nstat) != (uint32_t)(offsetof_end - offsetof_stat)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rstat->nstat value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_nstat), (uint32_t)(offsetof_end - offsetof_stat)); + return (ssize_t)host_size; +} + +static ssize_t validate_Twstat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Twstat); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_nstat = net_offset + 11; + uint32_t offsetof_stat = net_offset + 13; + uint32_t offsetof_stat_stat_size = net_offset + 13; + uint32_t offsetof_stat_kern_type = net_offset + 15; + uint32_t offsetof_stat_file_qid_type = net_offset + 21; + VALIDATE_NET_BYTES(34); + if (GET_U8LE(offsetof_stat_file_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_stat_file_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_stat_file_mode = net_offset + 0; + VALIDATE_NET_BYTES(22); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); #if CONFIG_9P_ENABLE_9P2000_u - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && validate_4(ctx) ) + if (is_ver(ctx, 9P2000_u)) { + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(12); + } #endif /* CONFIG_9P_ENABLE_9P2000_u */ - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 107; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tflush(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_2(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 108; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rflush(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 109; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Twalk(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint16_t nwname; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || validate_fid(ctx) - || (validate_2(ctx) || ({ nwname = decode_u16le(&ctx->net_bytes[ctx->net_offset-2]); false; })) - || _validate_list(ctx, decode_u16le(&ctx->net_bytes[ctx->net_offset-2]), validate_s, sizeof(struct lib9p_s)) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 110; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - || ({ uint32_t max = 16; (((uint32_t)nwname) > max) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "nwname value is too large (%"PRIu32" > %"PRIu32")", nwname, max); }) - ; -} - -FLATTEN static bool validate_Rwalk(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint16_t nwqid; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || (validate_2(ctx) || ({ nwqid = decode_u16le(&ctx->net_bytes[ctx->net_offset-2]); false; })) - || _validate_list(ctx, decode_u16le(&ctx->net_bytes[ctx->net_offset-2]), validate_qid, sizeof(struct lib9p_qid)) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 111; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - || ({ uint32_t max = 16; (((uint32_t)nwqid) > max) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "nwqid value is too large (%"PRIu32" > %"PRIu32")", nwqid, max); }) - ; -} - -FLATTEN static bool validate_Topen(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || validate_o(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 112; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Ropen(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_qid(ctx) - || validate_4(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 113; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tcreate(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || validate_s(ctx) - || validate_dm(ctx) - || validate_o(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 114; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rcreate(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_qid(ctx) - || validate_4(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 115; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tread(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || validate_8(ctx) - || validate_4(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 116; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rread(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_d(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 117; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Twrite(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || validate_8(ctx) - || validate_d(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 118; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rwrite(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_4(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 119; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tclunk(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 120; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rclunk(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 121; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tremove(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 122; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rremove(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 123; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tstat(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 124; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rstat(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint16_t nstat; - uint32_t _size_offset; - uint32_t _stat_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || (validate_2(ctx) || ({ nstat = decode_u16le(&ctx->net_bytes[ctx->net_offset-2]); false; })) - || ({ _stat_offset = ctx->net_offset; validate_stat(ctx); }) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 125; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - || ({ uint32_t exp = ctx->net_offset - _stat_offset; (((uint32_t)nstat) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "nstat value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)nstat, exp); }) - ; -} - -FLATTEN static bool validate_Twstat(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint16_t nstat; - uint32_t _size_offset; - uint32_t _stat_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_fid(ctx) - || (validate_2(ctx) || ({ nstat = decode_u16le(&ctx->net_bytes[ctx->net_offset-2]); false; })) - || ({ _stat_offset = ctx->net_offset; validate_stat(ctx); }) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 126; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - || ({ uint32_t exp = ctx->net_offset - _stat_offset; (((uint32_t)nstat) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "nstat value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)nstat, exp); }) - ; -} - -FLATTEN static bool validate_Rwstat(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 127; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */ + uint32_t offsetof_stat_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_stat_stat_size) != (uint32_t)(offsetof_stat_end - offsetof_stat_kern_type)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twstat->stat.stat_size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_stat_stat_size), (uint32_t)(offsetof_stat_end - offsetof_stat_kern_type)); + if (GET_U32LE(offsetof_stat_file_mode) & ~dm_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in dm bitfield: %#08"PRIx32, + GET_U32LE(offsetof_stat_file_mode) & ~dm_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twstat->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(126)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twstat->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(126)); + if ((uint32_t)GET_U32LE(offsetof_nstat) != (uint32_t)(offsetof_end - offsetof_stat)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Twstat->nstat value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_nstat), (uint32_t)(offsetof_end - offsetof_stat)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rwstat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rwstat); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rwstat->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(127)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rwstat->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(127)); + return (ssize_t)host_size; +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_p9p +static ssize_t validate_Topenfd(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Topenfd); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_mode = net_offset + 11; + uint32_t offsetof_end = net_offset + 12; + VALIDATE_NET_BYTES(12); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Topenfd->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(98)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Topenfd->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(98)); + if (GET_U8LE(offsetof_mode) & ~o_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in o bitfield: %#02"PRIx8, + GET_U8LE(offsetof_mode) & ~o_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Ropenfd(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Ropenfd); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 8; + VALIDATE_NET_BYTES(8); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Ropenfd->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(99)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Ropenfd->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(99)); + return (ssize_t)host_size; +} + +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_L +static ssize_t validate_Rlerror(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rlerror); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 11; + VALIDATE_NET_BYTES(11); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlerror->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(7)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlerror->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(7)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tstatfs(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tstatfs); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 11; + VALIDATE_NET_BYTES(11); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tstatfs->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(8)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tstatfs->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(8)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rstatfs(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rstatfs); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 67; + VALIDATE_NET_BYTES(67); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rstatfs->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(9)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rstatfs->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(9)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tlopen(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tlopen); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_flags = net_offset + 11; + uint32_t offsetof_end = net_offset + 15; + VALIDATE_NET_BYTES(15); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tlopen->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(12)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tlopen->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(12)); + if (GET_U32LE(offsetof_flags) & ~lo_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in lo bitfield: %#08"PRIx32, + GET_U32LE(offsetof_flags) & ~lo_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Rlopen(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rlopen); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 4; + VALIDATE_NET_BYTES(4); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlopen->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(13)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlopen->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(13)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tlcreate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tlcreate); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_flags = net_offset + 0; + uint32_t offsetof_mode = net_offset + 4; + uint32_t offsetof_end = net_offset + 12; + VALIDATE_NET_BYTES(12); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tlcreate->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(14)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tlcreate->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(14)); + if (GET_U32LE(offsetof_flags) & ~lo_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in lo bitfield: %#08"PRIx32, + GET_U32LE(offsetof_flags) & ~lo_masks[ctx->version]); + if (GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in mode bitfield: %#08"PRIx32, + GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Rlcreate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rlcreate); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 4; + VALIDATE_NET_BYTES(4); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlcreate->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(15)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlcreate->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(15)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tsymlink(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tsymlink); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 4; + VALIDATE_NET_BYTES(4); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tsymlink->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(16)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tsymlink->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(16)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rsymlink(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rsymlink); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rsymlink->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(17)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rsymlink->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(17)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tmknod(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tmknod); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_mode = net_offset + 0; + uint32_t offsetof_end = net_offset + 16; + VALIDATE_NET_BYTES(16); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tmknod->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(18)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tmknod->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(18)); + if (GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in mode bitfield: %#08"PRIx32, + GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Rmknod(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rmknod); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rmknod->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(19)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rmknod->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(19)); + return (ssize_t)host_size; +} + +static ssize_t validate_Trename(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Trename); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(17); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Trename->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(20)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Trename->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(20)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rrename(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rrename); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rrename->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(21)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rrename->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(21)); + return (ssize_t)host_size; +} + +static ssize_t validate_Treadlink(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Treadlink); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 11; + VALIDATE_NET_BYTES(11); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Treadlink->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(22)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Treadlink->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(22)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rreadlink(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rreadlink); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(9); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rreadlink->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(23)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rreadlink->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(23)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tgetattr(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tgetattr); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_request_mask = net_offset + 11; + uint32_t offsetof_end = net_offset + 19; + VALIDATE_NET_BYTES(19); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tgetattr->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(24)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tgetattr->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(24)); + if (GET_U64LE(offsetof_request_mask) & ~getattr_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in getattr bitfield: %#016"PRIx64, + GET_U64LE(offsetof_request_mask) & ~getattr_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Rgetattr(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rgetattr); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_valid = net_offset + 7; + uint32_t offsetof_qid_type = net_offset + 15; + VALIDATE_NET_BYTES(28); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_mode = net_offset + 0; + uint32_t offsetof_end = net_offset + 132; + VALIDATE_NET_BYTES(132); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rgetattr->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(25)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rgetattr->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(25)); + if (GET_U64LE(offsetof_valid) & ~getattr_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in getattr bitfield: %#016"PRIx64, + GET_U64LE(offsetof_valid) & ~getattr_masks[ctx->version]); + if (GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in mode bitfield: %#08"PRIx32, + GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Tsetattr(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tsetattr); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_valid = net_offset + 11; + uint32_t offsetof_mode = net_offset + 15; + uint32_t offsetof_end = net_offset + 67; + VALIDATE_NET_BYTES(67); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tsetattr->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(26)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tsetattr->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(26)); + if (GET_U32LE(offsetof_valid) & ~setattr_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in setattr bitfield: %#08"PRIx32, + GET_U32LE(offsetof_valid) & ~setattr_masks[ctx->version]); + if (GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in mode bitfield: %#08"PRIx32, + GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Rsetattr(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rsetattr); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rsetattr->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(27)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rsetattr->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(27)); + return (ssize_t)host_size; +} + +static ssize_t validate_Txattrwalk(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Txattrwalk); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(17); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Txattrwalk->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(30)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Txattrwalk->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(30)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rxattrwalk(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rxattrwalk); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 15; + VALIDATE_NET_BYTES(15); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rxattrwalk->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(31)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rxattrwalk->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(31)); + return (ssize_t)host_size; +} + +static ssize_t validate_Txattrcreate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Txattrcreate); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 12; + VALIDATE_NET_BYTES(12); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Txattrcreate->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(32)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Txattrcreate->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(32)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rxattrcreate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rxattrcreate); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rxattrcreate->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(33)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rxattrcreate->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(33)); + return (ssize_t)host_size; +} + +static ssize_t validate_Treaddir(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Treaddir); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 23; + VALIDATE_NET_BYTES(23); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Treaddir->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(40)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Treaddir->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(40)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rreaddir(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rreaddir); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(11); + VALIDATE_NET_BYTES(LAST_U32LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rreaddir->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(41)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rreaddir->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(41)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tfsync(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tfsync); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 15; + VALIDATE_NET_BYTES(15); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tfsync->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(50)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tfsync->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(50)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rfsync(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rfsync); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rfsync->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(51)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rfsync->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(51)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tlock(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tlock); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_flags = net_offset + 12; + VALIDATE_NET_BYTES(38); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tlock->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(52)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tlock->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(52)); + if (GET_U32LE(offsetof_flags) & ~lock_flags_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in lock_flags bitfield: %#08"PRIx32, + GET_U32LE(offsetof_flags) & ~lock_flags_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Rlock(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rlock); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 8; + VALIDATE_NET_BYTES(8); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlock->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(53)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlock->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(53)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tgetlock(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tgetlock); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(34); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tgetlock->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(54)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tgetlock->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(54)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rgetlock(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rgetlock); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(30); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rgetlock->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(55)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rgetlock->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(55)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tlink(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tlink); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(17); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tlink->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(70)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tlink->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(70)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rlink(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rlink); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlink->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(71)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rlink->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(71)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tmkdir(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tmkdir); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_mode = net_offset + 0; + uint32_t offsetof_end = net_offset + 8; + VALIDATE_NET_BYTES(8); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tmkdir->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(72)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tmkdir->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(72)); + if (GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in mode bitfield: %#08"PRIx32, + GET_U32LE(offsetof_mode) & ~mode_masks[ctx->version]); + return (ssize_t)host_size; +} + +static ssize_t validate_Rmkdir(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rmkdir); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_qid_type = net_offset + 7; + VALIDATE_NET_BYTES(20); + if (GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]) + return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in qt bitfield: %#02"PRIx8, + GET_U8LE(offsetof_qid_type) & ~qt_masks[ctx->version]); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rmkdir->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(73)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rmkdir->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(73)); + return (ssize_t)host_size; +} + +static ssize_t validate_Trenameat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Trenameat); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + VALIDATE_NET_BYTES(6); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Trenameat->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(74)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Trenameat->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(74)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rrenameat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rrenameat); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rrenameat->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(75)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rrenameat->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(75)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tunlinkat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tunlinkat); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + VALIDATE_NET_UTF8(LAST_U16LE()); + uint32_t offsetof_end = net_offset + 4; + VALIDATE_NET_BYTES(4); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tunlinkat->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(76)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tunlinkat->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(76)); + return (ssize_t)host_size; +} + +static ssize_t validate_Runlinkat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Runlinkat); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Runlinkat->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(77)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Runlinkat->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(77)); + return (ssize_t)host_size; +} + +#endif /* CONFIG_9P_ENABLE_9P2000_L */ #if CONFIG_9P_ENABLE_9P2000_e -FLATTEN static bool validate_Tsession(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_8(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 150; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rsession(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 151; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tsread(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_4(ctx) - || validate_2(ctx) - || _validate_list(ctx, decode_u16le(&ctx->net_bytes[ctx->net_offset-2]), validate_s, sizeof(struct lib9p_s)) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 152; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rsread(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_d(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 153; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Tswrite(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_4(ctx) - || validate_2(ctx) - || _validate_list(ctx, decode_u16le(&ctx->net_bytes[ctx->net_offset-2]), validate_s, sizeof(struct lib9p_s)) - || validate_d(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 154; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; -} - -FLATTEN static bool validate_Rswrite(struct _validate_ctx *ctx) { - uint32_t size; - uint8_t typ; - uint32_t _size_offset; - return false - || (({ _size_offset = ctx->net_offset; validate_4(ctx); }) || ({ size = decode_u32le(&ctx->net_bytes[ctx->net_offset-4]); false; })) - || (validate_1(ctx) || ({ typ = decode_u8le(&ctx->net_bytes[ctx->net_offset-1]); false; })) - || validate_tag(ctx) - || validate_4(ctx) - || ({ uint32_t exp = ctx->net_offset - _size_offset; (((uint32_t)size) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "size value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)size, exp); }) - || ({ uint32_t exp = 155; (((uint32_t)typ) != exp) && - lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "typ value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t)typ, exp); }) - ; +static ssize_t validate_Tsession(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tsession); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 15; + VALIDATE_NET_BYTES(15); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tsession->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(150)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tsession->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(150)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rsession(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rsession); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 7; + VALIDATE_NET_BYTES(7); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rsession->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(151)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rsession->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(151)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tsread(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tsread); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + for (uint16_t i = 0, cnt = LAST_U16LE(); i < cnt; i++) { + RESERVE_HOST_BYTES(sizeof(struct lib9p_s)); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + } + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tsread->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(152)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tsread->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(152)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rsread(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rsread); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(11); + VALIDATE_NET_BYTES(LAST_U32LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rsread->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(153)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rsread->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(153)); + return (ssize_t)host_size; +} + +static ssize_t validate_Tswrite(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Tswrite); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + VALIDATE_NET_BYTES(13); + for (uint16_t i = 0, cnt = LAST_U16LE(); i < cnt; i++) { + RESERVE_HOST_BYTES(sizeof(struct lib9p_s)); + VALIDATE_NET_BYTES(2); + VALIDATE_NET_UTF8(LAST_U16LE()); + } + VALIDATE_NET_BYTES(4); + VALIDATE_NET_BYTES(LAST_U32LE()); + uint32_t offsetof_end = net_offset + 0; + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tswrite->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(154)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Tswrite->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(154)); + return (ssize_t)host_size; +} + +static ssize_t validate_Rswrite(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) { + uint32_t net_offset = 0; + ssize_t host_size = sizeof(struct lib9p_msg_Rswrite); + uint32_t offsetof_size = net_offset + 0; + uint32_t offsetof_typ = net_offset + 4; + uint32_t offsetof_end = net_offset + 11; + VALIDATE_NET_BYTES(11); + if ((uint32_t)GET_U32LE(offsetof_size) != (uint32_t)(offsetof_end - offsetof_size)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rswrite->size value is wrong: actual: %"PRIu32" != correct:%"PRIu32, + (uint32_t)GET_U32LE(offsetof_size), (uint32_t)(offsetof_end - offsetof_size)); + if ((uint8_t)GET_U8LE(offsetof_typ) != (uint8_t)(155)) + return lib9p_errorf(ctx, LINUX_EBADMSG, "Rswrite->typ value is wrong: actual: %"PRIu8" != correct:%"PRIu8, + (uint8_t)GET_U8LE(offsetof_typ), (uint8_t)(155)); + return (ssize_t)host_size; } #endif /* CONFIG_9P_ENABLE_9P2000_e */ /* unmarshal_* ****************************************************************/ -ALWAYS_INLINE static void unmarshal_1(struct _unmarshal_ctx *ctx, uint8_t *out) { - *out = decode_u8le(&ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 1; -} - -ALWAYS_INLINE static void unmarshal_2(struct _unmarshal_ctx *ctx, uint16_t *out) { - *out = decode_u16le(&ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 2; +#define UNMARSHAL_BYTES(ctx, data_lvalue, len) \ + data_lvalue = (char *)&net_bytes[net_offset]; \ + net_offset += len; +#define UNMARSHAL_U8LE(ctx, val_lvalue) \ + val_lvalue = net_bytes[net_offset]; \ + net_offset += 1; +#define UNMARSHAL_U16LE(ctx, val_lvalue) \ + val_lvalue = uint16le_decode(&net_bytes[net_offset]); \ + net_offset += 2; +#define UNMARSHAL_U32LE(ctx, val_lvalue) \ + val_lvalue = uint32le_decode(&net_bytes[net_offset]); \ + net_offset += 4; +#define UNMARSHAL_U64LE(ctx, val_lvalue) \ + val_lvalue = uint64le_decode(&net_bytes[net_offset]); \ + net_offset += 8; + +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void unmarshal_stat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_stat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 2; + UNMARSHAL_U16LE(ctx, out->kern_type); + UNMARSHAL_U32LE(ctx, out->kern_dev); + UNMARSHAL_U8LE(ctx, out->file_qid.type); + UNMARSHAL_U32LE(ctx, out->file_qid.vers); + UNMARSHAL_U64LE(ctx, out->file_qid.path); + UNMARSHAL_U32LE(ctx, out->file_mode); + UNMARSHAL_U32LE(ctx, out->file_atime); + UNMARSHAL_U32LE(ctx, out->file_mtime); + UNMARSHAL_U64LE(ctx, out->file_size); + UNMARSHAL_U16LE(ctx, out->file_name.len); + UNMARSHAL_BYTES(ctx, out->file_name.utf8, out->file_name.len); + UNMARSHAL_U16LE(ctx, out->file_owner_uid.len); + UNMARSHAL_BYTES(ctx, out->file_owner_uid.utf8, out->file_owner_uid.len); + UNMARSHAL_U16LE(ctx, out->file_owner_gid.len); + UNMARSHAL_BYTES(ctx, out->file_owner_gid.utf8, out->file_owner_gid.len); + UNMARSHAL_U16LE(ctx, out->file_last_modified_uid.len); + UNMARSHAL_BYTES(ctx, out->file_last_modified_uid.utf8, out->file_last_modified_uid.len); +#if CONFIG_9P_ENABLE_9P2000_u + if (is_ver(ctx, 9P2000_u)) { + UNMARSHAL_U16LE(ctx, out->file_extension.len); + UNMARSHAL_BYTES(ctx, out->file_extension.utf8, out->file_extension.len); + UNMARSHAL_U32LE(ctx, out->file_owner_n_uid); + UNMARSHAL_U32LE(ctx, out->file_owner_n_gid); + UNMARSHAL_U32LE(ctx, out->file_last_modified_n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ } -ALWAYS_INLINE static void unmarshal_4(struct _unmarshal_ctx *ctx, uint32_t *out) { - *out = decode_u32le(&ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 4; +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void unmarshal_Tversion([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tversion *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->max_msg_size); + UNMARSHAL_U16LE(ctx, out->version.len); + UNMARSHAL_BYTES(ctx, out->version.utf8, out->version.len); +} + +static void unmarshal_Rversion([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rversion *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->max_msg_size); + UNMARSHAL_U16LE(ctx, out->version.len); + UNMARSHAL_BYTES(ctx, out->version.utf8, out->version.len); +} + +static void unmarshal_Tauth([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tauth *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->afid); + UNMARSHAL_U16LE(ctx, out->uname.len); + UNMARSHAL_BYTES(ctx, out->uname.utf8, out->uname.len); + UNMARSHAL_U16LE(ctx, out->aname.len); + UNMARSHAL_BYTES(ctx, out->aname.utf8, out->aname.len); +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + if (( is_ver(ctx, 9P2000_L) || is_ver(ctx, 9P2000_u) )) { + UNMARSHAL_U32LE(ctx, out->n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ +} + +static void unmarshal_Rauth([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rauth *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->aqid.type); + UNMARSHAL_U32LE(ctx, out->aqid.vers); + UNMARSHAL_U64LE(ctx, out->aqid.path); +} + +static void unmarshal_Tattach([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tattach *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U32LE(ctx, out->afid); + UNMARSHAL_U16LE(ctx, out->uname.len); + UNMARSHAL_BYTES(ctx, out->uname.utf8, out->uname.len); + UNMARSHAL_U16LE(ctx, out->aname.len); + UNMARSHAL_BYTES(ctx, out->aname.utf8, out->aname.len); +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + if (( is_ver(ctx, 9P2000_L) || is_ver(ctx, 9P2000_u) )) { + UNMARSHAL_U32LE(ctx, out->n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ +} + +static void unmarshal_Rattach([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rattach *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); +} + +static void unmarshal_Rerror([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rerror *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U16LE(ctx, out->ename.len); + UNMARSHAL_BYTES(ctx, out->ename.utf8, out->ename.len); +#if CONFIG_9P_ENABLE_9P2000_u + if (is_ver(ctx, 9P2000_u)) { + UNMARSHAL_U32LE(ctx, out->errno); + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ } -ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *out) { - *out = decode_u64le(&ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 8; +static void unmarshal_Tflush([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tflush *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U16LE(ctx, out->oldtag); +} + +static void unmarshal_Rflush([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rflush *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Twalk([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Twalk *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U32LE(ctx, out->newfid); + UNMARSHAL_U16LE(ctx, out->nwname); + out->wname = extra; + extra += sizeof(out->wname[0]) * out->nwname; + for (uint16_t i = 0; i < out->nwname; i++) { + UNMARSHAL_U16LE(ctx, out->wname[i].len); + UNMARSHAL_BYTES(ctx, out->wname[i].utf8, out->wname[i].len); + } } -#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u -ALWAYS_INLINE static void unmarshal_tag(struct _unmarshal_ctx *ctx, lib9p_tag_t *out) { - unmarshal_2(ctx, (uint16_t *)out); +static void unmarshal_Rwalk([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rwalk *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U16LE(ctx, out->nwqid); + out->wqid = extra; + extra += sizeof(out->wqid[0]) * out->nwqid; + for (uint16_t i = 0; i < out->nwqid; i++) { + UNMARSHAL_U8LE(ctx, out->wqid[i].type); + UNMARSHAL_U32LE(ctx, out->wqid[i].vers); + UNMARSHAL_U64LE(ctx, out->wqid[i].path); + } } -ALWAYS_INLINE static void unmarshal_fid(struct _unmarshal_ctx *ctx, lib9p_fid_t *out) { - unmarshal_4(ctx, (uint32_t *)out); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void unmarshal_Topen([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Topen *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U8LE(ctx, out->mode); +} + +static void unmarshal_Ropen([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Ropen *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); + UNMARSHAL_U32LE(ctx, out->iounit); +} + +static void unmarshal_Tcreate([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tcreate *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); + UNMARSHAL_U32LE(ctx, out->perm); + UNMARSHAL_U8LE(ctx, out->mode); +} + +static void unmarshal_Rcreate([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rcreate *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); + UNMARSHAL_U32LE(ctx, out->iounit); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void unmarshal_Tread([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tread *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U64LE(ctx, out->offset); + UNMARSHAL_U32LE(ctx, out->count); +} + +static void unmarshal_Rread([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rread *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->count); + UNMARSHAL_BYTES(ctx, out->data, out->count); +} + +static void unmarshal_Twrite([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Twrite *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U64LE(ctx, out->offset); + UNMARSHAL_U32LE(ctx, out->count); + UNMARSHAL_BYTES(ctx, out->data, out->count); +} + +static void unmarshal_Rwrite([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rwrite *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->count); +} + +static void unmarshal_Tclunk([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tclunk *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); +} + +static void unmarshal_Rclunk([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rclunk *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Tremove([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tremove *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); +} + +static void unmarshal_Rremove([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rremove *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void unmarshal_Tstat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tstat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); +} + +static void unmarshal_Rstat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rstat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + net_offset += 2; + net_offset += 2; + UNMARSHAL_U16LE(ctx, out->stat.kern_type); + UNMARSHAL_U32LE(ctx, out->stat.kern_dev); + UNMARSHAL_U8LE(ctx, out->stat.file_qid.type); + UNMARSHAL_U32LE(ctx, out->stat.file_qid.vers); + UNMARSHAL_U64LE(ctx, out->stat.file_qid.path); + UNMARSHAL_U32LE(ctx, out->stat.file_mode); + UNMARSHAL_U32LE(ctx, out->stat.file_atime); + UNMARSHAL_U32LE(ctx, out->stat.file_mtime); + UNMARSHAL_U64LE(ctx, out->stat.file_size); + UNMARSHAL_U16LE(ctx, out->stat.file_name.len); + UNMARSHAL_BYTES(ctx, out->stat.file_name.utf8, out->stat.file_name.len); + UNMARSHAL_U16LE(ctx, out->stat.file_owner_uid.len); + UNMARSHAL_BYTES(ctx, out->stat.file_owner_uid.utf8, out->stat.file_owner_uid.len); + UNMARSHAL_U16LE(ctx, out->stat.file_owner_gid.len); + UNMARSHAL_BYTES(ctx, out->stat.file_owner_gid.utf8, out->stat.file_owner_gid.len); + UNMARSHAL_U16LE(ctx, out->stat.file_last_modified_uid.len); + UNMARSHAL_BYTES(ctx, out->stat.file_last_modified_uid.utf8, out->stat.file_last_modified_uid.len); +#if CONFIG_9P_ENABLE_9P2000_u + if (is_ver(ctx, 9P2000_u)) { + UNMARSHAL_U16LE(ctx, out->stat.file_extension.len); + UNMARSHAL_BYTES(ctx, out->stat.file_extension.utf8, out->stat.file_extension.len); + UNMARSHAL_U32LE(ctx, out->stat.file_owner_n_uid); + UNMARSHAL_U32LE(ctx, out->stat.file_owner_n_gid); + UNMARSHAL_U32LE(ctx, out->stat.file_last_modified_n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ } -ALWAYS_INLINE static void unmarshal_d(struct _unmarshal_ctx *ctx, struct lib9p_d *out) { - memset(out, 0, sizeof(*out)); - unmarshal_4(ctx, &out->len); - out->dat = ctx->extra; - ctx->extra += sizeof(out->dat[0]) * out->len; - for (typeof(out->len) i = 0; i < out->len; i++) - unmarshal_1(ctx, (uint8_t *)&out->dat[i]); +static void unmarshal_Twstat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Twstat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + net_offset += 2; + net_offset += 2; + UNMARSHAL_U16LE(ctx, out->stat.kern_type); + UNMARSHAL_U32LE(ctx, out->stat.kern_dev); + UNMARSHAL_U8LE(ctx, out->stat.file_qid.type); + UNMARSHAL_U32LE(ctx, out->stat.file_qid.vers); + UNMARSHAL_U64LE(ctx, out->stat.file_qid.path); + UNMARSHAL_U32LE(ctx, out->stat.file_mode); + UNMARSHAL_U32LE(ctx, out->stat.file_atime); + UNMARSHAL_U32LE(ctx, out->stat.file_mtime); + UNMARSHAL_U64LE(ctx, out->stat.file_size); + UNMARSHAL_U16LE(ctx, out->stat.file_name.len); + UNMARSHAL_BYTES(ctx, out->stat.file_name.utf8, out->stat.file_name.len); + UNMARSHAL_U16LE(ctx, out->stat.file_owner_uid.len); + UNMARSHAL_BYTES(ctx, out->stat.file_owner_uid.utf8, out->stat.file_owner_uid.len); + UNMARSHAL_U16LE(ctx, out->stat.file_owner_gid.len); + UNMARSHAL_BYTES(ctx, out->stat.file_owner_gid.utf8, out->stat.file_owner_gid.len); + UNMARSHAL_U16LE(ctx, out->stat.file_last_modified_uid.len); + UNMARSHAL_BYTES(ctx, out->stat.file_last_modified_uid.utf8, out->stat.file_last_modified_uid.len); +#if CONFIG_9P_ENABLE_9P2000_u + if (is_ver(ctx, 9P2000_u)) { + UNMARSHAL_U16LE(ctx, out->stat.file_extension.len); + UNMARSHAL_BYTES(ctx, out->stat.file_extension.utf8, out->stat.file_extension.len); + UNMARSHAL_U32LE(ctx, out->stat.file_owner_n_uid); + UNMARSHAL_U32LE(ctx, out->stat.file_owner_n_gid); + UNMARSHAL_U32LE(ctx, out->stat.file_last_modified_n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ } -ALWAYS_INLINE static void unmarshal_s(struct _unmarshal_ctx *ctx, struct lib9p_s *out) { - memset(out, 0, sizeof(*out)); - unmarshal_2(ctx, &out->len); - out->utf8 = ctx->extra; - ctx->extra += sizeof(out->utf8[0]) * out->len; - for (typeof(out->len) i = 0; i < out->len; i++) - unmarshal_1(ctx, (uint8_t *)&out->utf8[i]); - ctx->extra++; - out->utf8[out->len] = '\0'; +static void unmarshal_Rwstat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rwstat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_p9p +static void unmarshal_Topenfd([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Topenfd *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U8LE(ctx, out->mode); +} + +static void unmarshal_Ropenfd([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Ropenfd *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); + UNMARSHAL_U32LE(ctx, out->iounit); + UNMARSHAL_U32LE(ctx, out->unixfd); +} + +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_L +static void unmarshal_Rlerror([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rlerror *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->ecode); +} + +static void unmarshal_Tstatfs([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tstatfs *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); +} + +static void unmarshal_Rstatfs([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rstatfs *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->type); + UNMARSHAL_U32LE(ctx, out->bsize); + UNMARSHAL_U64LE(ctx, out->blocks); + UNMARSHAL_U64LE(ctx, out->bfree); + UNMARSHAL_U64LE(ctx, out->bavail); + UNMARSHAL_U64LE(ctx, out->files); + UNMARSHAL_U64LE(ctx, out->ffree); + UNMARSHAL_U64LE(ctx, out->fsid); + UNMARSHAL_U32LE(ctx, out->namelen); +} + +static void unmarshal_Tlopen([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tlopen *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U32LE(ctx, out->flags); +} + +static void unmarshal_Rlopen([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rlopen *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); + UNMARSHAL_U32LE(ctx, out->iounit); +} + +static void unmarshal_Tlcreate([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tlcreate *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); + UNMARSHAL_U32LE(ctx, out->flags); + UNMARSHAL_U32LE(ctx, out->mode); + UNMARSHAL_U32LE(ctx, out->gid); +} + +static void unmarshal_Rlcreate([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rlcreate *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); + UNMARSHAL_U32LE(ctx, out->iounit); +} + +static void unmarshal_Tsymlink([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tsymlink *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); + UNMARSHAL_U16LE(ctx, out->symtgt.len); + UNMARSHAL_BYTES(ctx, out->symtgt.utf8, out->symtgt.len); + UNMARSHAL_U32LE(ctx, out->gid); +} + +static void unmarshal_Rsymlink([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rsymlink *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); +} + +static void unmarshal_Tmknod([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tmknod *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->dfid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); + UNMARSHAL_U32LE(ctx, out->mode); + UNMARSHAL_U32LE(ctx, out->major); + UNMARSHAL_U32LE(ctx, out->minor); + UNMARSHAL_U32LE(ctx, out->gid); +} + +static void unmarshal_Rmknod([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rmknod *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); +} + +static void unmarshal_Trename([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Trename *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U32LE(ctx, out->dfid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); +} + +static void unmarshal_Rrename([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rrename *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Treadlink([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Treadlink *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); +} + +static void unmarshal_Rreadlink([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rreadlink *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U16LE(ctx, out->target.len); + UNMARSHAL_BYTES(ctx, out->target.utf8, out->target.len); +} + +static void unmarshal_Tgetattr([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tgetattr *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U64LE(ctx, out->request_mask); +} + +static void unmarshal_Rgetattr([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rgetattr *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U64LE(ctx, out->valid); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); + UNMARSHAL_U32LE(ctx, out->mode); + UNMARSHAL_U32LE(ctx, out->uid); + UNMARSHAL_U32LE(ctx, out->gid); + UNMARSHAL_U64LE(ctx, out->nlink); + UNMARSHAL_U64LE(ctx, out->rdev); + UNMARSHAL_U64LE(ctx, out->filesize); + UNMARSHAL_U64LE(ctx, out->blksize); + UNMARSHAL_U64LE(ctx, out->blocks); + UNMARSHAL_U64LE(ctx, out->atime_sec); + UNMARSHAL_U64LE(ctx, out->atime_nsec); + UNMARSHAL_U64LE(ctx, out->mtime_sec); + UNMARSHAL_U64LE(ctx, out->mtime_nsec); + UNMARSHAL_U64LE(ctx, out->ctime_sec); + UNMARSHAL_U64LE(ctx, out->ctime_nsec); + UNMARSHAL_U64LE(ctx, out->btime_sec); + UNMARSHAL_U64LE(ctx, out->btime_nsec); + UNMARSHAL_U64LE(ctx, out->gen); + UNMARSHAL_U64LE(ctx, out->data_version); +} + +static void unmarshal_Tsetattr([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tsetattr *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U32LE(ctx, out->valid); + UNMARSHAL_U32LE(ctx, out->mode); + UNMARSHAL_U32LE(ctx, out->uid); + UNMARSHAL_U32LE(ctx, out->gid); + UNMARSHAL_U64LE(ctx, out->filesize); + UNMARSHAL_U64LE(ctx, out->atime_sec); + UNMARSHAL_U64LE(ctx, out->atime_nsec); + UNMARSHAL_U64LE(ctx, out->mtime_sec); + UNMARSHAL_U64LE(ctx, out->mtime_nsec); +} + +static void unmarshal_Rsetattr([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rsetattr *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Txattrwalk([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Txattrwalk *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U32LE(ctx, out->newfid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); +} + +static void unmarshal_Rxattrwalk([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rxattrwalk *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U64LE(ctx, out->attr_size); +} + +static void unmarshal_Txattrcreate([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Txattrcreate *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); + UNMARSHAL_U64LE(ctx, out->attr_size); + UNMARSHAL_U32LE(ctx, out->flags); +} + +static void unmarshal_Rxattrcreate([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rxattrcreate *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Treaddir([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Treaddir *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U64LE(ctx, out->offset); + UNMARSHAL_U32LE(ctx, out->count); +} + +static void unmarshal_Rreaddir([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rreaddir *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->count); + UNMARSHAL_BYTES(ctx, out->data, out->count); +} + +static void unmarshal_Tfsync([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tfsync *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U32LE(ctx, out->datasync); +} + +static void unmarshal_Rfsync([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rfsync *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Tlock([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tlock *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U8LE(ctx, out->type); + UNMARSHAL_U32LE(ctx, out->flags); + UNMARSHAL_U64LE(ctx, out->start); + UNMARSHAL_U64LE(ctx, out->length); + UNMARSHAL_U32LE(ctx, out->proc_id); + UNMARSHAL_U16LE(ctx, out->client_id.len); + UNMARSHAL_BYTES(ctx, out->client_id.utf8, out->client_id.len); +} + +static void unmarshal_Rlock([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rlock *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->status); +} + +static void unmarshal_Tgetlock([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tgetlock *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U8LE(ctx, out->type); + UNMARSHAL_U64LE(ctx, out->start); + UNMARSHAL_U64LE(ctx, out->length); + UNMARSHAL_U32LE(ctx, out->proc_id); + UNMARSHAL_U16LE(ctx, out->client_id.len); + UNMARSHAL_BYTES(ctx, out->client_id.utf8, out->client_id.len); +} + +static void unmarshal_Rgetlock([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rgetlock *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->type); + UNMARSHAL_U64LE(ctx, out->start); + UNMARSHAL_U64LE(ctx, out->length); + UNMARSHAL_U32LE(ctx, out->proc_id); + UNMARSHAL_U16LE(ctx, out->client_id.len); + UNMARSHAL_BYTES(ctx, out->client_id.utf8, out->client_id.len); +} + +static void unmarshal_Tlink([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tlink *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->dfid); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); +} + +static void unmarshal_Rlink([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rlink *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Tmkdir([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tmkdir *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->dfid); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); + UNMARSHAL_U32LE(ctx, out->mode); + UNMARSHAL_U32LE(ctx, out->gid); +} + +static void unmarshal_Rmkdir([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rmkdir *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U8LE(ctx, out->qid.type); + UNMARSHAL_U32LE(ctx, out->qid.vers); + UNMARSHAL_U64LE(ctx, out->qid.path); +} + +static void unmarshal_Trenameat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Trenameat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->olddirfid); + UNMARSHAL_U16LE(ctx, out->oldname.len); + UNMARSHAL_BYTES(ctx, out->oldname.utf8, out->oldname.len); + UNMARSHAL_U32LE(ctx, out->newdirfid); + UNMARSHAL_U16LE(ctx, out->newname.len); + UNMARSHAL_BYTES(ctx, out->newname.utf8, out->newname.len); +} + +static void unmarshal_Rrenameat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rrenameat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Tunlinkat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tunlinkat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->dirfd); + UNMARSHAL_U16LE(ctx, out->name.len); + UNMARSHAL_BYTES(ctx, out->name.utf8, out->name.len); + UNMARSHAL_U32LE(ctx, out->flags); +} + +static void unmarshal_Runlinkat([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Runlinkat *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e +static void unmarshal_Tsession([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tsession *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U64LE(ctx, out->key); +} + +static void unmarshal_Rsession([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rsession *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); +} + +static void unmarshal_Tsread([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tsread *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U16LE(ctx, out->nwname); + out->wname = extra; + extra += sizeof(out->wname[0]) * out->nwname; + for (uint16_t i = 0; i < out->nwname; i++) { + UNMARSHAL_U16LE(ctx, out->wname[i].len); + UNMARSHAL_BYTES(ctx, out->wname[i].utf8, out->wname[i].len); + } } -ALWAYS_INLINE static void unmarshal_dm(struct _unmarshal_ctx *ctx, lib9p_dm_t *out) { - unmarshal_4(ctx, (uint32_t *)out); +static void unmarshal_Rsread([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rsread *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->count); + UNMARSHAL_BYTES(ctx, out->data, out->count); +} + +static void unmarshal_Tswrite([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Tswrite *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->fid); + UNMARSHAL_U16LE(ctx, out->nwname); + out->wname = extra; + extra += sizeof(out->wname[0]) * out->nwname; + for (uint16_t i = 0; i < out->nwname; i++) { + UNMARSHAL_U16LE(ctx, out->wname[i].len); + UNMARSHAL_BYTES(ctx, out->wname[i].utf8, out->wname[i].len); + } + UNMARSHAL_U32LE(ctx, out->count); + UNMARSHAL_BYTES(ctx, out->data, out->count); } -ALWAYS_INLINE static void unmarshal_qt(struct _unmarshal_ctx *ctx, lib9p_qt_t *out) { - unmarshal_1(ctx, (uint8_t *)out); +static void unmarshal_Rswrite([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) { + struct lib9p_msg_Rswrite *out = out_buf; + [[gnu::unused]] void *extra = &out[1]; + uint32_t net_offset = 0; + net_offset += 4; + net_offset += 1; + UNMARSHAL_U16LE(ctx, out->tag); + UNMARSHAL_U32LE(ctx, out->count); } +#endif /* CONFIG_9P_ENABLE_9P2000_e */ -ALWAYS_INLINE static void unmarshal_qid(struct _unmarshal_ctx *ctx, struct lib9p_qid *out) { - memset(out, 0, sizeof(*out)); - unmarshal_qt(ctx, &out->type); - unmarshal_4(ctx, &out->vers); - unmarshal_8(ctx, &out->path); -} +/* marshal_* ******************************************************************/ -ALWAYS_INLINE static void unmarshal_stat(struct _unmarshal_ctx *ctx, struct lib9p_stat *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 2; - unmarshal_2(ctx, &out->kern_type); - unmarshal_4(ctx, &out->kern_dev); - unmarshal_qid(ctx, &out->file_qid); - unmarshal_dm(ctx, &out->file_mode); - unmarshal_4(ctx, &out->file_atime); - unmarshal_4(ctx, &out->file_mtime); - unmarshal_8(ctx, &out->file_size); - unmarshal_s(ctx, &out->file_name); - unmarshal_s(ctx, &out->file_owner_uid); - unmarshal_s(ctx, &out->file_owner_gid); - unmarshal_s(ctx, &out->file_last_modified_uid); +#define MARSHAL_BYTES_ZEROCOPY(ctx, data, len) \ + if (ret->net_iov[ret->net_iov_cnt-1].iov_len) \ + ret->net_iov_cnt++; \ + ret->net_iov[ret->net_iov_cnt-1].iov_base = data; \ + ret->net_iov[ret->net_iov_cnt-1].iov_len = len; \ + ret->net_iov_cnt++; +#define MARSHAL_BYTES(ctx, data, len) \ + if (!ret->net_iov[ret->net_iov_cnt-1].iov_base) \ + ret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size]; \ + memcpy(&ret->net_copied[ret->net_copied_size], data, len); \ + ret->net_copied_size += len; \ + ret->net_iov[ret->net_iov_cnt-1].iov_len += len; +#define MARSHAL_U8LE(ctx, val) \ + if (!ret->net_iov[ret->net_iov_cnt-1].iov_base) \ + ret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size]; \ + ret->net_copied[ret->net_copied_size] = val; \ + ret->net_copied_size += 1; \ + ret->net_iov[ret->net_iov_cnt-1].iov_len += 1; +#define MARSHAL_U16LE(ctx, val) \ + if (!ret->net_iov[ret->net_iov_cnt-1].iov_base) \ + ret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size]; \ + uint16le_encode(&ret->net_copied[ret->net_copied_size], val); \ + ret->net_copied_size += 2; \ + ret->net_iov[ret->net_iov_cnt-1].iov_len += 2; +#define MARSHAL_U32LE(ctx, val) \ + if (!ret->net_iov[ret->net_iov_cnt-1].iov_base) \ + ret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size]; \ + uint32le_encode(&ret->net_copied[ret->net_copied_size], val); \ + ret->net_copied_size += 4; \ + ret->net_iov[ret->net_iov_cnt-1].iov_len += 4; +#define MARSHAL_U64LE(ctx, val) \ + if (!ret->net_iov[ret->net_iov_cnt-1].iov_base) \ + ret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size]; \ + uint64le_encode(&ret->net_copied[ret->net_copied_size], val); \ + ret->net_copied_size += 8; \ + ret->net_iov[ret->net_iov_cnt-1].iov_len += 8; + +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static bool marshal_stat(struct lib9p_ctx *ctx, struct lib9p_stat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 49 + val->file_name.len + val->file_owner_uid.len + val->file_owner_gid.len + val->file_last_modified_uid.len; #if CONFIG_9P_ENABLE_9P2000_u - if ( (ctx->ctx->version==LIB9P_VER_9P2000_u) ) unmarshal_s(ctx, &out->file_extension); - if ( (ctx->ctx->version==LIB9P_VER_9P2000_u) ) unmarshal_4(ctx, &out->file_owner_n_uid); - if ( (ctx->ctx->version==LIB9P_VER_9P2000_u) ) unmarshal_4(ctx, &out->file_owner_n_gid); - if ( (ctx->ctx->version==LIB9P_VER_9P2000_u) ) unmarshal_4(ctx, &out->file_last_modified_n_uid); + if is_ver(ctx, 9P2000_u) { + needed_size += 14 + val->file_extension.len; + } #endif /* CONFIG_9P_ENABLE_9P2000_u */ + if (needed_size > ctx->max_msg_size) { + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_kern_type = 2; + MARSHAL_U16LE(ctx, offsetof_end - offsetof_kern_type); + MARSHAL_U16LE(ctx, val->kern_type); + MARSHAL_U32LE(ctx, val->kern_dev); + MARSHAL_U8LE(ctx, val->file_qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->file_qid.vers); + MARSHAL_U64LE(ctx, val->file_qid.path); + MARSHAL_U32LE(ctx, val->file_mode & dm_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->file_atime); + MARSHAL_U32LE(ctx, val->file_mtime); + MARSHAL_U64LE(ctx, val->file_size); + MARSHAL_U16LE(ctx, val->file_name.len); + MARSHAL_BYTES(ctx, val->file_name.utf8, val->file_name.len); + MARSHAL_U16LE(ctx, val->file_owner_uid.len); + MARSHAL_BYTES(ctx, val->file_owner_uid.utf8, val->file_owner_uid.len); + MARSHAL_U16LE(ctx, val->file_owner_gid.len); + MARSHAL_BYTES(ctx, val->file_owner_gid.utf8, val->file_owner_gid.len); + MARSHAL_U16LE(ctx, val->file_last_modified_uid.len); + MARSHAL_BYTES(ctx, val->file_last_modified_uid.utf8, val->file_last_modified_uid.len); +#if CONFIG_9P_ENABLE_9P2000_u + if (is_ver(ctx, 9P2000_u)) { + MARSHAL_U16LE(ctx, val->file_extension.len); + MARSHAL_BYTES(ctx, val->file_extension.utf8, val->file_extension.len); + MARSHAL_U32LE(ctx, val->file_owner_n_uid); + MARSHAL_U32LE(ctx, val->file_owner_n_gid); + MARSHAL_U32LE(ctx, val->file_last_modified_n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ + return false; } -ALWAYS_INLINE static void unmarshal_o(struct _unmarshal_ctx *ctx, lib9p_o_t *out) { - unmarshal_1(ctx, (uint8_t *)out); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static bool marshal_Tversion(struct lib9p_ctx *ctx, struct lib9p_msg_Tversion *val, struct _marshal_ret *ret) { + uint32_t needed_size = 13 + val->version.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tversion", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 100); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->max_msg_size); + MARSHAL_U16LE(ctx, val->version.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->version.utf8, val->version.len); + return false; } -FLATTEN static void unmarshal_Tversion(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tversion *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_4(ctx, &out->max_msg_size); - unmarshal_s(ctx, &out->version); +static bool marshal_Rversion(struct lib9p_ctx *ctx, struct lib9p_msg_Rversion *val, struct _marshal_ret *ret) { + uint32_t needed_size = 13 + val->version.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rversion", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 101); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->max_msg_size); + MARSHAL_U16LE(ctx, val->version.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->version.utf8, val->version.len); + return false; } -FLATTEN static void unmarshal_Rversion(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rversion *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_4(ctx, &out->max_msg_size); - unmarshal_s(ctx, &out->version); +static bool marshal_Tauth(struct lib9p_ctx *ctx, struct lib9p_msg_Tauth *val, struct _marshal_ret *ret) { + uint32_t needed_size = 15 + val->uname.len + val->aname.len; +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + if ( is_ver(ctx, 9P2000_L) || is_ver(ctx, 9P2000_u) ) { + needed_size += 4; + } +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tauth", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 102); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->afid); + MARSHAL_U16LE(ctx, val->uname.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->uname.utf8, val->uname.len); + MARSHAL_U16LE(ctx, val->aname.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->aname.utf8, val->aname.len); +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + if (( is_ver(ctx, 9P2000_L) || is_ver(ctx, 9P2000_u) )) { + MARSHAL_U32LE(ctx, val->n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ + return false; } -FLATTEN static void unmarshal_Tauth(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tauth *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->afid); - unmarshal_s(ctx, &out->uname); - unmarshal_s(ctx, &out->aname); -#if CONFIG_9P_ENABLE_9P2000_u - if ( (ctx->ctx->version==LIB9P_VER_9P2000_u) ) unmarshal_4(ctx, &out->n_uname); -#endif /* CONFIG_9P_ENABLE_9P2000_u */ +static bool marshal_Rauth(struct lib9p_ctx *ctx, struct lib9p_msg_Rauth *val, struct _marshal_ret *ret) { + uint32_t needed_size = 20; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rauth", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 103); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->aqid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->aqid.vers); + MARSHAL_U64LE(ctx, val->aqid.path); + return false; +} + +static bool marshal_Tattach(struct lib9p_ctx *ctx, struct lib9p_msg_Tattach *val, struct _marshal_ret *ret) { + uint32_t needed_size = 19 + val->uname.len + val->aname.len; +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + if ( is_ver(ctx, 9P2000_L) || is_ver(ctx, 9P2000_u) ) { + needed_size += 4; + } +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tattach", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 104); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U32LE(ctx, val->afid); + MARSHAL_U16LE(ctx, val->uname.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->uname.utf8, val->uname.len); + MARSHAL_U16LE(ctx, val->aname.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->aname.utf8, val->aname.len); +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + if (( is_ver(ctx, 9P2000_L) || is_ver(ctx, 9P2000_u) )) { + MARSHAL_U32LE(ctx, val->n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ + return false; } -FLATTEN static void unmarshal_Rauth(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rauth *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_qid(ctx, &out->aqid); +static bool marshal_Rattach(struct lib9p_ctx *ctx, struct lib9p_msg_Rattach *val, struct _marshal_ret *ret) { + uint32_t needed_size = 20; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rattach", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 105); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + return false; } -FLATTEN static void unmarshal_Tattach(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tattach *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); - unmarshal_fid(ctx, &out->afid); - unmarshal_s(ctx, &out->uname); - unmarshal_s(ctx, &out->aname); +static bool marshal_Rerror(struct lib9p_ctx *ctx, struct lib9p_msg_Rerror *val, struct _marshal_ret *ret) { + uint32_t needed_size = 9 + val->ename.len; +#if CONFIG_9P_ENABLE_9P2000_u + if is_ver(ctx, 9P2000_u) { + needed_size += 4; + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rerror", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 107); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U16LE(ctx, val->ename.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->ename.utf8, val->ename.len); #if CONFIG_9P_ENABLE_9P2000_u - if ( (ctx->ctx->version==LIB9P_VER_9P2000_u) ) unmarshal_4(ctx, &out->n_uname); + if (is_ver(ctx, 9P2000_u)) { + MARSHAL_U32LE(ctx, val->errno); + } #endif /* CONFIG_9P_ENABLE_9P2000_u */ + return false; } -FLATTEN static void unmarshal_Rattach(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rattach *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_qid(ctx, &out->qid); +static bool marshal_Tflush(struct lib9p_ctx *ctx, struct lib9p_msg_Tflush *val, struct _marshal_ret *ret) { + uint32_t needed_size = 9; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tflush", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 108); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U16LE(ctx, val->oldtag); + return false; } -FLATTEN static void unmarshal_Rerror(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rerror *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_s(ctx, &out->ename); -#if CONFIG_9P_ENABLE_9P2000_u - if ( (ctx->ctx->version==LIB9P_VER_9P2000_u) ) unmarshal_4(ctx, &out->errno); -#endif /* CONFIG_9P_ENABLE_9P2000_u */ +static bool marshal_Rflush(struct lib9p_ctx *ctx, struct lib9p_msg_Rflush *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rflush", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 109); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +static bool marshal_Twalk(struct lib9p_ctx *ctx, struct lib9p_msg_Twalk *val, struct _marshal_ret *ret) { + uint32_t needed_size = 17; + for (uint16_t i = 0; i < val->nwname; i++) { + needed_size += 2 + val->wname[i].len; + } + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Twalk", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 110); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U32LE(ctx, val->newfid); + MARSHAL_U16LE(ctx, val->nwname); + for (uint16_t i = 0; i < val->nwname; i++) { + MARSHAL_U16LE(ctx, val->wname[i].len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->wname[i].utf8, val->wname[i].len); + } + return false; } -FLATTEN static void unmarshal_Tflush(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tflush *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_2(ctx, &out->oldtag); -} - -FLATTEN static void unmarshal_Rflush(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rflush *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); -} - -FLATTEN static void unmarshal_Twalk(struct _unmarshal_ctx *ctx, struct lib9p_msg_Twalk *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); - unmarshal_fid(ctx, &out->newfid); - unmarshal_2(ctx, &out->nwname); - out->wname = ctx->extra; - ctx->extra += sizeof(out->wname[0]) * out->nwname; - for (typeof(out->nwname) i = 0; i < out->nwname; i++) - unmarshal_s(ctx, &out->wname[i]); -} - -FLATTEN static void unmarshal_Rwalk(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rwalk *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_2(ctx, &out->nwqid); - out->wqid = ctx->extra; - ctx->extra += sizeof(out->wqid[0]) * out->nwqid; - for (typeof(out->nwqid) i = 0; i < out->nwqid; i++) - unmarshal_qid(ctx, &out->wqid[i]); -} - -FLATTEN static void unmarshal_Topen(struct _unmarshal_ctx *ctx, struct lib9p_msg_Topen *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); - unmarshal_o(ctx, &out->mode); -} - -FLATTEN static void unmarshal_Ropen(struct _unmarshal_ctx *ctx, struct lib9p_msg_Ropen *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_qid(ctx, &out->qid); - unmarshal_4(ctx, &out->iounit); -} - -FLATTEN static void unmarshal_Tcreate(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tcreate *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); - unmarshal_s(ctx, &out->name); - unmarshal_dm(ctx, &out->perm); - unmarshal_o(ctx, &out->mode); -} - -FLATTEN static void unmarshal_Rcreate(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rcreate *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_qid(ctx, &out->qid); - unmarshal_4(ctx, &out->iounit); -} - -FLATTEN static void unmarshal_Tread(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tread *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); - unmarshal_8(ctx, &out->offset); - unmarshal_4(ctx, &out->count); -} - -FLATTEN static void unmarshal_Rread(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rread *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_d(ctx, &out->data); -} - -FLATTEN static void unmarshal_Twrite(struct _unmarshal_ctx *ctx, struct lib9p_msg_Twrite *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); - unmarshal_8(ctx, &out->offset); - unmarshal_d(ctx, &out->data); -} - -FLATTEN static void unmarshal_Rwrite(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rwrite *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_4(ctx, &out->count); -} - -FLATTEN static void unmarshal_Tclunk(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tclunk *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); -} - -FLATTEN static void unmarshal_Rclunk(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rclunk *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); -} - -FLATTEN static void unmarshal_Tremove(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tremove *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); -} - -FLATTEN static void unmarshal_Rremove(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rremove *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); -} - -FLATTEN static void unmarshal_Tstat(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tstat *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); -} - -FLATTEN static void unmarshal_Rstat(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rstat *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - ctx->net_offset += 2; - unmarshal_stat(ctx, &out->stat); -} - -FLATTEN static void unmarshal_Twstat(struct _unmarshal_ctx *ctx, struct lib9p_msg_Twstat *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_fid(ctx, &out->fid); - ctx->net_offset += 2; - unmarshal_stat(ctx, &out->stat); -} - -FLATTEN static void unmarshal_Rwstat(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rwstat *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); +static bool marshal_Rwalk(struct lib9p_ctx *ctx, struct lib9p_msg_Rwalk *val, struct _marshal_ret *ret) { + uint32_t needed_size = 9 + (val->nwqid)*13; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rwalk", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 111); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U16LE(ctx, val->nwqid); + for (uint16_t i = 0; i < val->nwqid; i++) { + MARSHAL_U8LE(ctx, val->wqid[i].type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->wqid[i].vers); + MARSHAL_U64LE(ctx, val->wqid[i].path); + } + return false; } -#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */ -#if CONFIG_9P_ENABLE_9P2000_e -FLATTEN static void unmarshal_Tsession(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tsession *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_8(ctx, &out->key); -} - -FLATTEN static void unmarshal_Rsession(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rsession *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); -} - -FLATTEN static void unmarshal_Tsread(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tsread *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_4(ctx, &out->fid); - unmarshal_2(ctx, &out->nwname); - out->wname = ctx->extra; - ctx->extra += sizeof(out->wname[0]) * out->nwname; - for (typeof(out->nwname) i = 0; i < out->nwname; i++) - unmarshal_s(ctx, &out->wname[i]); -} - -FLATTEN static void unmarshal_Rsread(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rsread *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_d(ctx, &out->data); -} - -FLATTEN static void unmarshal_Tswrite(struct _unmarshal_ctx *ctx, struct lib9p_msg_Tswrite *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_4(ctx, &out->fid); - unmarshal_2(ctx, &out->nwname); - out->wname = ctx->extra; - ctx->extra += sizeof(out->wname[0]) * out->nwname; - for (typeof(out->nwname) i = 0; i < out->nwname; i++) - unmarshal_s(ctx, &out->wname[i]); - unmarshal_d(ctx, &out->data); -} - -FLATTEN static void unmarshal_Rswrite(struct _unmarshal_ctx *ctx, struct lib9p_msg_Rswrite *out) { - memset(out, 0, sizeof(*out)); - ctx->net_offset += 4; - ctx->net_offset += 1; - unmarshal_tag(ctx, &out->tag); - unmarshal_4(ctx, &out->count); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static bool marshal_Topen(struct lib9p_ctx *ctx, struct lib9p_msg_Topen *val, struct _marshal_ret *ret) { + uint32_t needed_size = 12; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Topen", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 112); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U8LE(ctx, val->mode & o_masks[ctx->version]); + return false; } -#endif /* CONFIG_9P_ENABLE_9P2000_e */ -/* marshal_* ******************************************************************/ +static bool marshal_Ropen(struct lib9p_ctx *ctx, struct lib9p_msg_Ropen *val, struct _marshal_ret *ret) { + uint32_t needed_size = 24; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Ropen", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 113); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + MARSHAL_U32LE(ctx, val->iounit); + return false; +} -ALWAYS_INLINE static bool _marshal_too_large(struct _marshal_ctx *ctx) { - lib9p_errorf(ctx->ctx, LINUX_ERANGE, "%s too large to marshal into %s limit (limit=%"PRIu32")", - (ctx->net_bytes[4] % 2 == 0) ? "T-message" : "R-message", - ctx->ctx->version ? "negotiated" : ((ctx->net_bytes[4] % 2 == 0) ? "client" : "server"), - ctx->ctx->max_msg_size); - return true; +static bool marshal_Tcreate(struct lib9p_ctx *ctx, struct lib9p_msg_Tcreate *val, struct _marshal_ret *ret) { + uint32_t needed_size = 18 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tcreate", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 114); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + MARSHAL_U32LE(ctx, val->perm & dm_masks[ctx->version]); + MARSHAL_U8LE(ctx, val->mode & o_masks[ctx->version]); + return false; } -ALWAYS_INLINE static bool marshal_1(struct _marshal_ctx *ctx, uint8_t *val) { - if (ctx->net_offset + 1 > ctx->ctx->max_msg_size) - return _marshal_too_large(ctx); - ctx->net_bytes[ctx->net_offset] = *val; - ctx->net_offset += 1; +static bool marshal_Rcreate(struct lib9p_ctx *ctx, struct lib9p_msg_Rcreate *val, struct _marshal_ret *ret) { + uint32_t needed_size = 24; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rcreate", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 115); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + MARSHAL_U32LE(ctx, val->iounit); return false; } -ALWAYS_INLINE static bool marshal_2(struct _marshal_ctx *ctx, uint16_t *val) { - if (ctx->net_offset + 2 > ctx->ctx->max_msg_size) - return _marshal_too_large(ctx); - encode_u16le(*val, &ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 2; +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static bool marshal_Tread(struct lib9p_ctx *ctx, struct lib9p_msg_Tread *val, struct _marshal_ret *ret) { + uint32_t needed_size = 23; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tread", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 116); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U64LE(ctx, val->offset); + MARSHAL_U32LE(ctx, val->count); return false; } -ALWAYS_INLINE static bool marshal_4(struct _marshal_ctx *ctx, uint32_t *val) { - if (ctx->net_offset + 4 > ctx->ctx->max_msg_size) +static bool marshal_Rread(struct lib9p_ctx *ctx, struct lib9p_msg_Rread *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11 + val->count; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rread", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); return true; - encode_u32le(*val, &ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 4; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 117); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->count); + MARSHAL_BYTES_ZEROCOPY(ctx, val->data, val->count); return false; } -ALWAYS_INLINE static bool marshal_8(struct _marshal_ctx *ctx, uint64_t *val) { - if (ctx->net_offset + 8 > ctx->ctx->max_msg_size) +static bool marshal_Twrite(struct lib9p_ctx *ctx, struct lib9p_msg_Twrite *val, struct _marshal_ret *ret) { + uint32_t needed_size = 23 + val->count; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Twrite", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); return true; - encode_u64le(*val, &ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 8; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 118); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U64LE(ctx, val->offset); + MARSHAL_U32LE(ctx, val->count); + MARSHAL_BYTES_ZEROCOPY(ctx, val->data, val->count); return false; } -#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u -ALWAYS_INLINE static bool marshal_tag(struct _marshal_ctx *ctx, lib9p_tag_t *val) { - return marshal_2(ctx, (uint16_t *)val); +static bool marshal_Rwrite(struct lib9p_ctx *ctx, struct lib9p_msg_Rwrite *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rwrite", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 119); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->count); + return false; } -ALWAYS_INLINE static bool marshal_fid(struct _marshal_ctx *ctx, lib9p_fid_t *val) { - return marshal_4(ctx, (uint32_t *)val); +static bool marshal_Tclunk(struct lib9p_ctx *ctx, struct lib9p_msg_Tclunk *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tclunk", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 120); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + return false; } -ALWAYS_INLINE static bool marshal_d(struct _marshal_ctx *ctx, struct lib9p_d *val) { - return false - || marshal_4(ctx, &val->len) - || ({ bool err = false; - for (typeof(val->len) i = 0; i < val->len && !err; i++) - err = marshal_1(ctx, (uint8_t *)&val->dat[i]); - err; }) - ; +static bool marshal_Rclunk(struct lib9p_ctx *ctx, struct lib9p_msg_Rclunk *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rclunk", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 121); + MARSHAL_U16LE(ctx, val->tag); + return false; } -ALWAYS_INLINE static bool marshal_s(struct _marshal_ctx *ctx, struct lib9p_s *val) { - return false - || marshal_2(ctx, &val->len) - || ({ bool err = false; - for (typeof(val->len) i = 0; i < val->len && !err; i++) - err = marshal_1(ctx, (uint8_t *)&val->utf8[i]); - err; }) - ; +static bool marshal_Tremove(struct lib9p_ctx *ctx, struct lib9p_msg_Tremove *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tremove", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 122); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + return false; } -ALWAYS_INLINE static bool marshal_dm(struct _marshal_ctx *ctx, lib9p_dm_t *val) { - lib9p_dm_t masked_val = *val & dm_masks[ctx->ctx->version]; - return marshal_4(ctx, (uint32_t *)&masked_val); +static bool marshal_Rremove(struct lib9p_ctx *ctx, struct lib9p_msg_Rremove *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rremove", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 123); + MARSHAL_U16LE(ctx, val->tag); + return false; } -ALWAYS_INLINE static bool marshal_qt(struct _marshal_ctx *ctx, lib9p_qt_t *val) { - lib9p_qt_t masked_val = *val & qt_masks[ctx->ctx->version]; - return marshal_1(ctx, (uint8_t *)&masked_val); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static bool marshal_Tstat(struct lib9p_ctx *ctx, struct lib9p_msg_Tstat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tstat", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 124); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + return false; } -ALWAYS_INLINE static bool marshal_qid(struct _marshal_ctx *ctx, struct lib9p_qid *val) { - return false - || marshal_qt(ctx, &val->type) - || marshal_4(ctx, &val->vers) - || marshal_8(ctx, &val->path) - ; +static bool marshal_Rstat(struct lib9p_ctx *ctx, struct lib9p_msg_Rstat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 58 + val->stat.file_name.len + val->stat.file_owner_uid.len + val->stat.file_owner_gid.len + val->stat.file_last_modified_uid.len; +#if CONFIG_9P_ENABLE_9P2000_u + if is_ver(ctx, 9P2000_u) { + needed_size += 14 + val->stat.file_extension.len; + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rstat", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + uint32_t offsetof_stat = 9; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 125); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U16LE(ctx, offsetof_end - offsetof_stat); + uint32_t offsetof_stat_end = 49 + val->stat.file_name.len + val->stat.file_owner_uid.len + val->stat.file_owner_gid.len + val->stat.file_last_modified_uid.len; +#if CONFIG_9P_ENABLE_9P2000_u + if is_ver(ctx, 9P2000_u) { + offsetof_stat_end += 14 + val->stat.file_extension.len; + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ + uint32_t offsetof_stat_kern_type = 2; + MARSHAL_U16LE(ctx, offsetof_stat_end - offsetof_stat_kern_type); + MARSHAL_U16LE(ctx, val->stat.kern_type); + MARSHAL_U32LE(ctx, val->stat.kern_dev); + MARSHAL_U8LE(ctx, val->stat.file_qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->stat.file_qid.vers); + MARSHAL_U64LE(ctx, val->stat.file_qid.path); + MARSHAL_U32LE(ctx, val->stat.file_mode & dm_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->stat.file_atime); + MARSHAL_U32LE(ctx, val->stat.file_mtime); + MARSHAL_U64LE(ctx, val->stat.file_size); + MARSHAL_U16LE(ctx, val->stat.file_name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_name.utf8, val->stat.file_name.len); + MARSHAL_U16LE(ctx, val->stat.file_owner_uid.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_owner_uid.utf8, val->stat.file_owner_uid.len); + MARSHAL_U16LE(ctx, val->stat.file_owner_gid.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_owner_gid.utf8, val->stat.file_owner_gid.len); + MARSHAL_U16LE(ctx, val->stat.file_last_modified_uid.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_last_modified_uid.utf8, val->stat.file_last_modified_uid.len); +#if CONFIG_9P_ENABLE_9P2000_u + if (is_ver(ctx, 9P2000_u)) { + MARSHAL_U16LE(ctx, val->stat.file_extension.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_extension.utf8, val->stat.file_extension.len); + MARSHAL_U32LE(ctx, val->stat.file_owner_n_uid); + MARSHAL_U32LE(ctx, val->stat.file_owner_n_gid); + MARSHAL_U32LE(ctx, val->stat.file_last_modified_n_uid); + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ + return false; } -ALWAYS_INLINE static bool marshal_stat(struct _marshal_ctx *ctx, struct lib9p_stat *val) { - uint32_t _stat_size_offset; - uint32_t _kern_type_offset; - return false - || ({ _stat_size_offset = ctx->net_offset; ({ ctx->net_offset += 2; false; }); }) - || ({ _kern_type_offset = ctx->net_offset; marshal_2(ctx, &val->kern_type); }) - || marshal_4(ctx, &val->kern_dev) - || marshal_qid(ctx, &val->file_qid) - || marshal_dm(ctx, &val->file_mode) - || marshal_4(ctx, &val->file_atime) - || marshal_4(ctx, &val->file_mtime) - || marshal_8(ctx, &val->file_size) - || marshal_s(ctx, &val->file_name) - || marshal_s(ctx, &val->file_owner_uid) - || marshal_s(ctx, &val->file_owner_gid) - || marshal_s(ctx, &val->file_last_modified_uid) +static bool marshal_Twstat(struct lib9p_ctx *ctx, struct lib9p_msg_Twstat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 62 + val->stat.file_name.len + val->stat.file_owner_uid.len + val->stat.file_owner_gid.len + val->stat.file_last_modified_uid.len; #if CONFIG_9P_ENABLE_9P2000_u - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && marshal_s(ctx, &val->file_extension) ) - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && marshal_4(ctx, &val->file_owner_n_uid) ) - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && marshal_4(ctx, &val->file_owner_n_gid) ) - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && marshal_4(ctx, &val->file_last_modified_n_uid) ) + if is_ver(ctx, 9P2000_u) { + needed_size += 14 + val->stat.file_extension.len; + } #endif /* CONFIG_9P_ENABLE_9P2000_u */ - || ({ encode_u16le(ctx->net_offset - _kern_type_offset, &ctx->net_bytes[_stat_size_offset]); false; }) - ; -} - -ALWAYS_INLINE static bool marshal_o(struct _marshal_ctx *ctx, lib9p_o_t *val) { - lib9p_o_t masked_val = *val & o_masks[ctx->ctx->version]; - return marshal_1(ctx, (uint8_t *)&masked_val); -} - -FLATTEN static bool marshal_Tversion(struct _marshal_ctx *ctx, struct lib9p_msg_Tversion *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_4(ctx, &val->max_msg_size) - || marshal_s(ctx, &val->version) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(100, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rversion(struct _marshal_ctx *ctx, struct lib9p_msg_Rversion *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_4(ctx, &val->max_msg_size) - || marshal_s(ctx, &val->version) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(101, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tauth(struct _marshal_ctx *ctx, struct lib9p_msg_Tauth *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->afid) - || marshal_s(ctx, &val->uname) - || marshal_s(ctx, &val->aname) + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Twstat", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + uint32_t offsetof_stat = 13; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 126); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U16LE(ctx, offsetof_end - offsetof_stat); + uint32_t offsetof_stat_end = 49 + val->stat.file_name.len + val->stat.file_owner_uid.len + val->stat.file_owner_gid.len + val->stat.file_last_modified_uid.len; +#if CONFIG_9P_ENABLE_9P2000_u + if is_ver(ctx, 9P2000_u) { + offsetof_stat_end += 14 + val->stat.file_extension.len; + } +#endif /* CONFIG_9P_ENABLE_9P2000_u */ + uint32_t offsetof_stat_kern_type = 2; + MARSHAL_U16LE(ctx, offsetof_stat_end - offsetof_stat_kern_type); + MARSHAL_U16LE(ctx, val->stat.kern_type); + MARSHAL_U32LE(ctx, val->stat.kern_dev); + MARSHAL_U8LE(ctx, val->stat.file_qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->stat.file_qid.vers); + MARSHAL_U64LE(ctx, val->stat.file_qid.path); + MARSHAL_U32LE(ctx, val->stat.file_mode & dm_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->stat.file_atime); + MARSHAL_U32LE(ctx, val->stat.file_mtime); + MARSHAL_U64LE(ctx, val->stat.file_size); + MARSHAL_U16LE(ctx, val->stat.file_name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_name.utf8, val->stat.file_name.len); + MARSHAL_U16LE(ctx, val->stat.file_owner_uid.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_owner_uid.utf8, val->stat.file_owner_uid.len); + MARSHAL_U16LE(ctx, val->stat.file_owner_gid.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_owner_gid.utf8, val->stat.file_owner_gid.len); + MARSHAL_U16LE(ctx, val->stat.file_last_modified_uid.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_last_modified_uid.utf8, val->stat.file_last_modified_uid.len); #if CONFIG_9P_ENABLE_9P2000_u - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && marshal_4(ctx, &val->n_uname) ) + if (is_ver(ctx, 9P2000_u)) { + MARSHAL_U16LE(ctx, val->stat.file_extension.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->stat.file_extension.utf8, val->stat.file_extension.len); + MARSHAL_U32LE(ctx, val->stat.file_owner_n_uid); + MARSHAL_U32LE(ctx, val->stat.file_owner_n_gid); + MARSHAL_U32LE(ctx, val->stat.file_last_modified_n_uid); + } #endif /* CONFIG_9P_ENABLE_9P2000_u */ - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(102, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rauth(struct _marshal_ctx *ctx, struct lib9p_msg_Rauth *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_qid(ctx, &val->aqid) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(103, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tattach(struct _marshal_ctx *ctx, struct lib9p_msg_Tattach *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || marshal_fid(ctx, &val->afid) - || marshal_s(ctx, &val->uname) - || marshal_s(ctx, &val->aname) + return false; +} + +static bool marshal_Rwstat(struct lib9p_ctx *ctx, struct lib9p_msg_Rwstat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rwstat", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 127); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_p9p +static bool marshal_Topenfd(struct lib9p_ctx *ctx, struct lib9p_msg_Topenfd *val, struct _marshal_ret *ret) { + uint32_t needed_size = 12; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Topenfd", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 98); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U8LE(ctx, val->mode & o_masks[ctx->version]); + return false; +} + +static bool marshal_Ropenfd(struct lib9p_ctx *ctx, struct lib9p_msg_Ropenfd *val, struct _marshal_ret *ret) { + uint32_t needed_size = 28; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Ropenfd", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 99); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + MARSHAL_U32LE(ctx, val->iounit); + MARSHAL_U32LE(ctx, val->unixfd); + return false; +} + +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_L +static bool marshal_Rlerror(struct lib9p_ctx *ctx, struct lib9p_msg_Rlerror *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rlerror", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 7); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->ecode); + return false; +} + +static bool marshal_Tstatfs(struct lib9p_ctx *ctx, struct lib9p_msg_Tstatfs *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tstatfs", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 8); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + return false; +} + +static bool marshal_Rstatfs(struct lib9p_ctx *ctx, struct lib9p_msg_Rstatfs *val, struct _marshal_ret *ret) { + uint32_t needed_size = 67; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rstatfs", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 9); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->type); + MARSHAL_U32LE(ctx, val->bsize); + MARSHAL_U64LE(ctx, val->blocks); + MARSHAL_U64LE(ctx, val->bfree); + MARSHAL_U64LE(ctx, val->bavail); + MARSHAL_U64LE(ctx, val->files); + MARSHAL_U64LE(ctx, val->ffree); + MARSHAL_U64LE(ctx, val->fsid); + MARSHAL_U32LE(ctx, val->namelen); + return false; +} + +static bool marshal_Tlopen(struct lib9p_ctx *ctx, struct lib9p_msg_Tlopen *val, struct _marshal_ret *ret) { + uint32_t needed_size = 15; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tlopen", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 12); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U32LE(ctx, val->flags & lo_masks[ctx->version]); + return false; +} + +static bool marshal_Rlopen(struct lib9p_ctx *ctx, struct lib9p_msg_Rlopen *val, struct _marshal_ret *ret) { + uint32_t needed_size = 24; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rlopen", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 13); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + MARSHAL_U32LE(ctx, val->iounit); + return false; +} + +static bool marshal_Tlcreate(struct lib9p_ctx *ctx, struct lib9p_msg_Tlcreate *val, struct _marshal_ret *ret) { + uint32_t needed_size = 25 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tlcreate", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 14); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + MARSHAL_U32LE(ctx, val->flags & lo_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->mode & mode_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->gid); + return false; +} + +static bool marshal_Rlcreate(struct lib9p_ctx *ctx, struct lib9p_msg_Rlcreate *val, struct _marshal_ret *ret) { + uint32_t needed_size = 24; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rlcreate", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 15); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + MARSHAL_U32LE(ctx, val->iounit); + return false; +} + +static bool marshal_Tsymlink(struct lib9p_ctx *ctx, struct lib9p_msg_Tsymlink *val, struct _marshal_ret *ret) { + uint32_t needed_size = 19 + val->name.len + val->symtgt.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tsymlink", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 16); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + MARSHAL_U16LE(ctx, val->symtgt.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->symtgt.utf8, val->symtgt.len); + MARSHAL_U32LE(ctx, val->gid); + return false; +} + +static bool marshal_Rsymlink(struct lib9p_ctx *ctx, struct lib9p_msg_Rsymlink *val, struct _marshal_ret *ret) { + uint32_t needed_size = 20; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rsymlink", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 17); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + return false; +} + +static bool marshal_Tmknod(struct lib9p_ctx *ctx, struct lib9p_msg_Tmknod *val, struct _marshal_ret *ret) { + uint32_t needed_size = 29 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tmknod", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 18); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->dfid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + MARSHAL_U32LE(ctx, val->mode & mode_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->major); + MARSHAL_U32LE(ctx, val->minor); + MARSHAL_U32LE(ctx, val->gid); + return false; +} + +static bool marshal_Rmknod(struct lib9p_ctx *ctx, struct lib9p_msg_Rmknod *val, struct _marshal_ret *ret) { + uint32_t needed_size = 20; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rmknod", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 19); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + return false; +} + +static bool marshal_Trename(struct lib9p_ctx *ctx, struct lib9p_msg_Trename *val, struct _marshal_ret *ret) { + uint32_t needed_size = 17 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Trename", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 20); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U32LE(ctx, val->dfid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + return false; +} + +static bool marshal_Rrename(struct lib9p_ctx *ctx, struct lib9p_msg_Rrename *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rrename", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 21); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +static bool marshal_Treadlink(struct lib9p_ctx *ctx, struct lib9p_msg_Treadlink *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Treadlink", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 22); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + return false; +} + +static bool marshal_Rreadlink(struct lib9p_ctx *ctx, struct lib9p_msg_Rreadlink *val, struct _marshal_ret *ret) { + uint32_t needed_size = 9 + val->target.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rreadlink", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 23); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U16LE(ctx, val->target.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->target.utf8, val->target.len); + return false; +} + +static bool marshal_Tgetattr(struct lib9p_ctx *ctx, struct lib9p_msg_Tgetattr *val, struct _marshal_ret *ret) { + uint32_t needed_size = 19; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tgetattr", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 24); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U64LE(ctx, val->request_mask & getattr_masks[ctx->version]); + return false; +} + +static bool marshal_Rgetattr(struct lib9p_ctx *ctx, struct lib9p_msg_Rgetattr *val, struct _marshal_ret *ret) { + uint32_t needed_size = 160; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rgetattr", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 25); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U64LE(ctx, val->valid & getattr_masks[ctx->version]); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + MARSHAL_U32LE(ctx, val->mode & mode_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->uid); + MARSHAL_U32LE(ctx, val->gid); + MARSHAL_U64LE(ctx, val->nlink); + MARSHAL_U64LE(ctx, val->rdev); + MARSHAL_U64LE(ctx, val->filesize); + MARSHAL_U64LE(ctx, val->blksize); + MARSHAL_U64LE(ctx, val->blocks); + MARSHAL_U64LE(ctx, val->atime_sec); + MARSHAL_U64LE(ctx, val->atime_nsec); + MARSHAL_U64LE(ctx, val->mtime_sec); + MARSHAL_U64LE(ctx, val->mtime_nsec); + MARSHAL_U64LE(ctx, val->ctime_sec); + MARSHAL_U64LE(ctx, val->ctime_nsec); + MARSHAL_U64LE(ctx, val->btime_sec); + MARSHAL_U64LE(ctx, val->btime_nsec); + MARSHAL_U64LE(ctx, val->gen); + MARSHAL_U64LE(ctx, val->data_version); + return false; +} + +static bool marshal_Tsetattr(struct lib9p_ctx *ctx, struct lib9p_msg_Tsetattr *val, struct _marshal_ret *ret) { + uint32_t needed_size = 67; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tsetattr", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 26); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U32LE(ctx, val->valid & setattr_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->mode & mode_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->uid); + MARSHAL_U32LE(ctx, val->gid); + MARSHAL_U64LE(ctx, val->filesize); + MARSHAL_U64LE(ctx, val->atime_sec); + MARSHAL_U64LE(ctx, val->atime_nsec); + MARSHAL_U64LE(ctx, val->mtime_sec); + MARSHAL_U64LE(ctx, val->mtime_nsec); + return false; +} + +static bool marshal_Rsetattr(struct lib9p_ctx *ctx, struct lib9p_msg_Rsetattr *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rsetattr", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 27); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +static bool marshal_Txattrwalk(struct lib9p_ctx *ctx, struct lib9p_msg_Txattrwalk *val, struct _marshal_ret *ret) { + uint32_t needed_size = 17 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Txattrwalk", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 30); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U32LE(ctx, val->newfid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + return false; +} + +static bool marshal_Rxattrwalk(struct lib9p_ctx *ctx, struct lib9p_msg_Rxattrwalk *val, struct _marshal_ret *ret) { + uint32_t needed_size = 15; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rxattrwalk", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 31); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U64LE(ctx, val->attr_size); + return false; +} + +static bool marshal_Txattrcreate(struct lib9p_ctx *ctx, struct lib9p_msg_Txattrcreate *val, struct _marshal_ret *ret) { + uint32_t needed_size = 25 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Txattrcreate", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 32); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + MARSHAL_U64LE(ctx, val->attr_size); + MARSHAL_U32LE(ctx, val->flags); + return false; +} + +static bool marshal_Rxattrcreate(struct lib9p_ctx *ctx, struct lib9p_msg_Rxattrcreate *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rxattrcreate", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 33); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +static bool marshal_Treaddir(struct lib9p_ctx *ctx, struct lib9p_msg_Treaddir *val, struct _marshal_ret *ret) { + uint32_t needed_size = 23; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Treaddir", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 40); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U64LE(ctx, val->offset); + MARSHAL_U32LE(ctx, val->count); + return false; +} + +static bool marshal_Rreaddir(struct lib9p_ctx *ctx, struct lib9p_msg_Rreaddir *val, struct _marshal_ret *ret) { + uint64_t needed_size = 11 + val->count; + if (needed_size > (uint64_t)(ctx->max_msg_size)) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rreaddir", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = (uint32_t)needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 41); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->count); + MARSHAL_BYTES_ZEROCOPY(ctx, val->data, val->count); + return false; +} + +static bool marshal_Tfsync(struct lib9p_ctx *ctx, struct lib9p_msg_Tfsync *val, struct _marshal_ret *ret) { + uint32_t needed_size = 15; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tfsync", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 50); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U32LE(ctx, val->datasync); + return false; +} + +static bool marshal_Rfsync(struct lib9p_ctx *ctx, struct lib9p_msg_Rfsync *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rfsync", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 51); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +static bool marshal_Tlock(struct lib9p_ctx *ctx, struct lib9p_msg_Tlock *val, struct _marshal_ret *ret) { + uint32_t needed_size = 38 + val->client_id.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tlock", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 52); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U8LE(ctx, val->type); + MARSHAL_U32LE(ctx, val->flags & lock_flags_masks[ctx->version]); + MARSHAL_U64LE(ctx, val->start); + MARSHAL_U64LE(ctx, val->length); + MARSHAL_U32LE(ctx, val->proc_id); + MARSHAL_U16LE(ctx, val->client_id.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->client_id.utf8, val->client_id.len); + return false; +} + +static bool marshal_Rlock(struct lib9p_ctx *ctx, struct lib9p_msg_Rlock *val, struct _marshal_ret *ret) { + uint32_t needed_size = 8; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rlock", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 53); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->status); + return false; +} + +static bool marshal_Tgetlock(struct lib9p_ctx *ctx, struct lib9p_msg_Tgetlock *val, struct _marshal_ret *ret) { + uint32_t needed_size = 34 + val->client_id.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tgetlock", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 54); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U8LE(ctx, val->type); + MARSHAL_U64LE(ctx, val->start); + MARSHAL_U64LE(ctx, val->length); + MARSHAL_U32LE(ctx, val->proc_id); + MARSHAL_U16LE(ctx, val->client_id.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->client_id.utf8, val->client_id.len); + return false; +} + +static bool marshal_Rgetlock(struct lib9p_ctx *ctx, struct lib9p_msg_Rgetlock *val, struct _marshal_ret *ret) { + uint32_t needed_size = 30 + val->client_id.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rgetlock", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 55); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->type); + MARSHAL_U64LE(ctx, val->start); + MARSHAL_U64LE(ctx, val->length); + MARSHAL_U32LE(ctx, val->proc_id); + MARSHAL_U16LE(ctx, val->client_id.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->client_id.utf8, val->client_id.len); + return false; +} + +static bool marshal_Tlink(struct lib9p_ctx *ctx, struct lib9p_msg_Tlink *val, struct _marshal_ret *ret) { + uint32_t needed_size = 17 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tlink", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 70); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->dfid); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + return false; +} + +static bool marshal_Rlink(struct lib9p_ctx *ctx, struct lib9p_msg_Rlink *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rlink", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 71); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +static bool marshal_Tmkdir(struct lib9p_ctx *ctx, struct lib9p_msg_Tmkdir *val, struct _marshal_ret *ret) { + uint32_t needed_size = 21 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tmkdir", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 72); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->dfid); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + MARSHAL_U32LE(ctx, val->mode & mode_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->gid); + return false; +} + +static bool marshal_Rmkdir(struct lib9p_ctx *ctx, struct lib9p_msg_Rmkdir *val, struct _marshal_ret *ret) { + uint32_t needed_size = 20; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rmkdir", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 73); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U8LE(ctx, val->qid.type & qt_masks[ctx->version]); + MARSHAL_U32LE(ctx, val->qid.vers); + MARSHAL_U64LE(ctx, val->qid.path); + return false; +} + +static bool marshal_Trenameat(struct lib9p_ctx *ctx, struct lib9p_msg_Trenameat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 19 + val->oldname.len + val->newname.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Trenameat", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 74); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->olddirfid); + MARSHAL_U16LE(ctx, val->oldname.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->oldname.utf8, val->oldname.len); + MARSHAL_U32LE(ctx, val->newdirfid); + MARSHAL_U16LE(ctx, val->newname.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->newname.utf8, val->newname.len); + return false; +} + +static bool marshal_Rrenameat(struct lib9p_ctx *ctx, struct lib9p_msg_Rrenameat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rrenameat", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 75); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +static bool marshal_Tunlinkat(struct lib9p_ctx *ctx, struct lib9p_msg_Tunlinkat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 17 + val->name.len; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tunlinkat", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 76); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->dirfd); + MARSHAL_U16LE(ctx, val->name.len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->name.utf8, val->name.len); + MARSHAL_U32LE(ctx, val->flags); + return false; +} + +static bool marshal_Runlinkat(struct lib9p_ctx *ctx, struct lib9p_msg_Runlinkat *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Runlinkat", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 77); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e +static bool marshal_Tsession(struct lib9p_ctx *ctx, struct lib9p_msg_Tsession *val, struct _marshal_ret *ret) { + uint32_t needed_size = 15; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tsession", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 150); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U64LE(ctx, val->key); + return false; +} + +static bool marshal_Rsession(struct lib9p_ctx *ctx, struct lib9p_msg_Rsession *val, struct _marshal_ret *ret) { + uint32_t needed_size = 7; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rsession", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 151); + MARSHAL_U16LE(ctx, val->tag); + return false; +} + +static bool marshal_Tsread(struct lib9p_ctx *ctx, struct lib9p_msg_Tsread *val, struct _marshal_ret *ret) { + uint64_t needed_size = 13; + for (uint16_t i = 0; i < val->nwname; i++) { + needed_size += 2 + val->wname[i].len; + } + if (needed_size > (uint64_t)(ctx->max_msg_size)) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tsread", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = (uint32_t)needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 152); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U16LE(ctx, val->nwname); + for (uint16_t i = 0; i < val->nwname; i++) { + MARSHAL_U16LE(ctx, val->wname[i].len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->wname[i].utf8, val->wname[i].len); + } + return false; +} + +static bool marshal_Rsread(struct lib9p_ctx *ctx, struct lib9p_msg_Rsread *val, struct _marshal_ret *ret) { + uint64_t needed_size = 11 + val->count; + if (needed_size > (uint64_t)(ctx->max_msg_size)) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rsread", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = (uint32_t)needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 153); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->count); + MARSHAL_BYTES_ZEROCOPY(ctx, val->data, val->count); + return false; +} + +static bool marshal_Tswrite(struct lib9p_ctx *ctx, struct lib9p_msg_Tswrite *val, struct _marshal_ret *ret) { + uint64_t needed_size = 17 + val->count; + for (uint16_t i = 0; i < val->nwname; i++) { + needed_size += 2 + val->wname[i].len; + } + if (needed_size > (uint64_t)(ctx->max_msg_size)) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Tswrite", + ctx->version ? "negotiated" : "client", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = (uint32_t)needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 154); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->fid); + MARSHAL_U16LE(ctx, val->nwname); + for (uint16_t i = 0; i < val->nwname; i++) { + MARSHAL_U16LE(ctx, val->wname[i].len); + MARSHAL_BYTES_ZEROCOPY(ctx, val->wname[i].utf8, val->wname[i].len); + } + MARSHAL_U32LE(ctx, val->count); + MARSHAL_BYTES_ZEROCOPY(ctx, val->data, val->count); + return false; +} + +static bool marshal_Rswrite(struct lib9p_ctx *ctx, struct lib9p_msg_Rswrite *val, struct _marshal_ret *ret) { + uint32_t needed_size = 11; + if (needed_size > ctx->max_msg_size) { + lib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")", + "Rswrite", + ctx->version ? "negotiated" : "server", + ctx->max_msg_size); + return true; + } + uint32_t offsetof_end = needed_size; + uint32_t offsetof_size = 0; + MARSHAL_U32LE(ctx, offsetof_end - offsetof_size); + MARSHAL_U8LE(ctx, 155); + MARSHAL_U16LE(ctx, val->tag); + MARSHAL_U32LE(ctx, val->count); + return false; +} +#endif /* CONFIG_9P_ENABLE_9P2000_e */ + +/* *_format *******************************************************************/ + +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_tag_format(lib9p_tag_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_TAG_NOTAG: + fmt_state_puts(state, "NOTAG"); + break; + default: + fmt_state_printf(state, "%"PRIu16, *self); + } +} + +static void lib9p_fid_format(lib9p_fid_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_FID_NOFID: + fmt_state_puts(state, "NOFID"); + break; + default: + fmt_state_printf(state, "%"PRIu32, *self); + } +} + +static void lib9p_s_format(struct lib9p_s *self, struct fmt_state *state) { + /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + fmt_state_printf(state, "%.*q", self->len, self->utf8); +#pragma GCC diagnostic pop +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_dm_format(lib9p_dm_t *self, struct fmt_state *state) { + bool empty = true; + fmt_state_putchar(state, '('); + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<31)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "DIR"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<30)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "APPEND"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<29)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "EXCL"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<28)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "_PLAN9_MOUNT"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<27)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "AUTH"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<26)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "TMP"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<25)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<25"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<24)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<24"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<23)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "DEVICE"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<22)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<22"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<21)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PIPE"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<20)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "SOCKET"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<19)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "SETUID"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<18)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "SETGID"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<17)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<17"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<16)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<16"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<15)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<15"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<14)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<14"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<13)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<13"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<12)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<12"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<11)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<11"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<10)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<10"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<9)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<9"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<8)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "OWNER_R"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<7)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "OWNER_W"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<6)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "OWNER_X"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<5)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "GROUP_R"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<4)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "GROUP_W"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<3)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "GROUP_X"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<2)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "OTHER_R"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<1)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "OTHER_W"); + empty = false; + } + if ((*self & ~((lib9p_dm_t)0777)) & (UINT32_C(1)<<0)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "OTHER_X"); + empty = false; + } + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_printf(state, "%#04"PRIo32, *self & 0777); + fmt_state_putchar(state, ')'); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_qt_format(lib9p_qt_t *self, struct fmt_state *state) { + bool empty = true; + fmt_state_putchar(state, '('); + if (*self & (UINT8_C(1)<<7)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "DIR"); + empty = false; + } + if (*self & (UINT8_C(1)<<6)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "APPEND"); + empty = false; + } + if (*self & (UINT8_C(1)<<5)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "EXCL"); + empty = false; + } + if (*self & (UINT8_C(1)<<4)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "_PLAN9_MOUNT"); + empty = false; + } + if (*self & (UINT8_C(1)<<3)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "AUTH"); + empty = false; + } + if (*self & (UINT8_C(1)<<2)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "TMP"); + empty = false; + } + if (*self & (UINT8_C(1)<<1)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "SYMLINK"); + empty = false; + } + if (*self & (UINT8_C(1)<<0)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<0"); + empty = false; + } + if (empty) + fmt_state_putchar(state, '0'); + fmt_state_putchar(state, ')'); +} + +static void lib9p_qid_format(struct lib9p_qid *self, struct fmt_state *state) { + fmt_state_putchar(state, '{'); + fmt_state_puts(state, " type="); + lib9p_qt_format(&self->type, state); + fmt_state_puts(state, " vers="); + fmt_state_printf(state, "%"PRIu32, self->vers); + fmt_state_puts(state, " path="); + fmt_state_printf(state, "%"PRIu64, self->path); + fmt_state_puts(state, " }"); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_stat_format(struct lib9p_stat *self, struct fmt_state *state) { + fmt_state_putchar(state, '{'); + fmt_state_puts(state, " kern_type="); + fmt_state_printf(state, "%"PRIu16, self->kern_type); + fmt_state_puts(state, " kern_dev="); + fmt_state_printf(state, "%"PRIu32, self->kern_dev); + fmt_state_puts(state, " file_qid="); + lib9p_qid_format(&self->file_qid, state); + fmt_state_puts(state, " file_mode="); + lib9p_dm_format(&self->file_mode, state); + fmt_state_puts(state, " file_atime="); + fmt_state_printf(state, "%"PRIu32, self->file_atime); + fmt_state_puts(state, " file_mtime="); + fmt_state_printf(state, "%"PRIu32, self->file_mtime); + fmt_state_puts(state, " file_size="); + fmt_state_printf(state, "%"PRIu64, self->file_size); + fmt_state_puts(state, " file_name="); + lib9p_s_format(&self->file_name, state); + fmt_state_puts(state, " file_owner_uid="); + lib9p_s_format(&self->file_owner_uid, state); + fmt_state_puts(state, " file_owner_gid="); + lib9p_s_format(&self->file_owner_gid, state); + fmt_state_puts(state, " file_last_modified_uid="); + lib9p_s_format(&self->file_last_modified_uid, state); #if CONFIG_9P_ENABLE_9P2000_u - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && marshal_4(ctx, &val->n_uname) ) + fmt_state_puts(state, " file_extension="); + lib9p_s_format(&self->file_extension, state); + fmt_state_puts(state, " file_owner_n_uid="); + lib9p_nuid_format(&self->file_owner_n_uid, state); + fmt_state_puts(state, " file_owner_n_gid="); + lib9p_nuid_format(&self->file_owner_n_gid, state); + fmt_state_puts(state, " file_last_modified_n_uid="); + lib9p_nuid_format(&self->file_last_modified_n_uid, state); #endif /* CONFIG_9P_ENABLE_9P2000_u */ - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(104, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rattach(struct _marshal_ctx *ctx, struct lib9p_msg_Rattach *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_qid(ctx, &val->qid) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(105, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rerror(struct _marshal_ctx *ctx, struct lib9p_msg_Rerror *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_s(ctx, &val->ename) + fmt_state_puts(state, " }"); +} + +static void lib9p_o_format(lib9p_o_t *self, struct fmt_state *state) { + bool empty = true; + fmt_state_putchar(state, '('); + if (*self & (UINT8_C(1)<<7)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<7"); + empty = false; + } + if (*self & (UINT8_C(1)<<6)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "RCLOSE"); + empty = false; + } + if (*self & (UINT8_C(1)<<5)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "CEXEC"); + empty = false; + } + if (*self & (UINT8_C(1)<<4)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "TRUNC"); + empty = false; + } + if (*self & (UINT8_C(1)<<3)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<3"); + empty = false; + } + if (*self & (UINT8_C(1)<<2)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<2"); + empty = false; + } + switch (*self & LIB9P_O_MODE_MASK) { + case LIB9P_O_MODE_READ: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE_READ"); + empty = false; + break; + case LIB9P_O_MODE_WRITE: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE_WRITE"); + empty = false; + break; + case LIB9P_O_MODE_RDWR: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE_RDWR"); + empty = false; + break; + case LIB9P_O_MODE_EXEC: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE_EXEC"); + empty = false; + break; + default: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_printf(state, "%"PRIu8, *self & LIB9P_O_MODE_MASK); + empty = false; + } + if (empty) + fmt_state_putchar(state, '0'); + fmt_state_putchar(state, ')'); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_msg_Tversion_format(struct lib9p_msg_Tversion *self, struct fmt_state *state) { + fmt_state_puts(state, "Tversion {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " max_msg_size="); + fmt_state_printf(state, "%"PRIu32, self->max_msg_size); + fmt_state_puts(state, " version="); + lib9p_s_format(&self->version, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rversion_format(struct lib9p_msg_Rversion *self, struct fmt_state *state) { + fmt_state_puts(state, "Rversion {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " max_msg_size="); + fmt_state_printf(state, "%"PRIu32, self->max_msg_size); + fmt_state_puts(state, " version="); + lib9p_s_format(&self->version, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tauth_format(struct lib9p_msg_Tauth *self, struct fmt_state *state) { + fmt_state_puts(state, "Tauth {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " afid="); + lib9p_fid_format(&self->afid, state); + fmt_state_puts(state, " uname="); + lib9p_s_format(&self->uname, state); + fmt_state_puts(state, " aname="); + lib9p_s_format(&self->aname, state); +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + fmt_state_puts(state, " n_uid="); + lib9p_nuid_format(&self->n_uid, state); +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rauth_format(struct lib9p_msg_Rauth *self, struct fmt_state *state) { + fmt_state_puts(state, "Rauth {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " aqid="); + lib9p_qid_format(&self->aqid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tattach_format(struct lib9p_msg_Tattach *self, struct fmt_state *state) { + fmt_state_puts(state, "Tattach {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " afid="); + lib9p_fid_format(&self->afid, state); + fmt_state_puts(state, " uname="); + lib9p_s_format(&self->uname, state); + fmt_state_puts(state, " aname="); + lib9p_s_format(&self->aname, state); +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + fmt_state_puts(state, " n_uid="); + lib9p_nuid_format(&self->n_uid, state); +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rattach_format(struct lib9p_msg_Rattach *self, struct fmt_state *state) { + fmt_state_puts(state, "Rattach {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rerror_format(struct lib9p_msg_Rerror *self, struct fmt_state *state) { + fmt_state_puts(state, "Rerror {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " ename="); + lib9p_s_format(&self->ename, state); #if CONFIG_9P_ENABLE_9P2000_u - || ( (ctx->ctx->version==LIB9P_VER_9P2000_u) && marshal_4(ctx, &val->errno) ) + fmt_state_puts(state, " errno="); + lib9p_errno_format(&self->errno, state); #endif /* CONFIG_9P_ENABLE_9P2000_u */ - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(107, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tflush(struct _marshal_ctx *ctx, struct lib9p_msg_Tflush *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_2(ctx, &val->oldtag) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(108, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rflush(struct _marshal_ctx *ctx, struct lib9p_msg_Rflush *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(109, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Twalk(struct _marshal_ctx *ctx, struct lib9p_msg_Twalk *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || marshal_fid(ctx, &val->newfid) - || marshal_2(ctx, &val->nwname) - || ({ bool err = false; - for (typeof(val->nwname) i = 0; i < val->nwname && !err; i++) - err = marshal_s(ctx, &val->wname[i]); - err; }) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(110, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rwalk(struct _marshal_ctx *ctx, struct lib9p_msg_Rwalk *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_2(ctx, &val->nwqid) - || ({ bool err = false; - for (typeof(val->nwqid) i = 0; i < val->nwqid && !err; i++) - err = marshal_qid(ctx, &val->wqid[i]); - err; }) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(111, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Topen(struct _marshal_ctx *ctx, struct lib9p_msg_Topen *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || marshal_o(ctx, &val->mode) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(112, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Ropen(struct _marshal_ctx *ctx, struct lib9p_msg_Ropen *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_qid(ctx, &val->qid) - || marshal_4(ctx, &val->iounit) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(113, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tcreate(struct _marshal_ctx *ctx, struct lib9p_msg_Tcreate *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || marshal_s(ctx, &val->name) - || marshal_dm(ctx, &val->perm) - || marshal_o(ctx, &val->mode) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(114, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rcreate(struct _marshal_ctx *ctx, struct lib9p_msg_Rcreate *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_qid(ctx, &val->qid) - || marshal_4(ctx, &val->iounit) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(115, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tread(struct _marshal_ctx *ctx, struct lib9p_msg_Tread *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || marshal_8(ctx, &val->offset) - || marshal_4(ctx, &val->count) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(116, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rread(struct _marshal_ctx *ctx, struct lib9p_msg_Rread *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_d(ctx, &val->data) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(117, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Twrite(struct _marshal_ctx *ctx, struct lib9p_msg_Twrite *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || marshal_8(ctx, &val->offset) - || marshal_d(ctx, &val->data) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(118, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rwrite(struct _marshal_ctx *ctx, struct lib9p_msg_Rwrite *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_4(ctx, &val->count) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(119, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tclunk(struct _marshal_ctx *ctx, struct lib9p_msg_Tclunk *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(120, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rclunk(struct _marshal_ctx *ctx, struct lib9p_msg_Rclunk *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(121, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tremove(struct _marshal_ctx *ctx, struct lib9p_msg_Tremove *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(122, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rremove(struct _marshal_ctx *ctx, struct lib9p_msg_Rremove *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(123, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tstat(struct _marshal_ctx *ctx, struct lib9p_msg_Tstat *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(124, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rstat(struct _marshal_ctx *ctx, struct lib9p_msg_Rstat *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - uint32_t _nstat_offset; - uint32_t _stat_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || ({ _nstat_offset = ctx->net_offset; ({ ctx->net_offset += 2; false; }); }) - || ({ _stat_offset = ctx->net_offset; marshal_stat(ctx, &val->stat); }) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(125, &ctx->net_bytes[_typ_offset]); false; }) - || ({ encode_u16le(ctx->net_offset - _stat_offset, &ctx->net_bytes[_nstat_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Twstat(struct _marshal_ctx *ctx, struct lib9p_msg_Twstat *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - uint32_t _nstat_offset; - uint32_t _stat_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_fid(ctx, &val->fid) - || ({ _nstat_offset = ctx->net_offset; ({ ctx->net_offset += 2; false; }); }) - || ({ _stat_offset = ctx->net_offset; marshal_stat(ctx, &val->stat); }) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(126, &ctx->net_bytes[_typ_offset]); false; }) - || ({ encode_u16le(ctx->net_offset - _stat_offset, &ctx->net_bytes[_nstat_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rwstat(struct _marshal_ctx *ctx, struct lib9p_msg_Rwstat *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(127, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */ + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tflush_format(struct lib9p_msg_Tflush *self, struct fmt_state *state) { + fmt_state_puts(state, "Tflush {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " oldtag="); + fmt_state_printf(state, "%"PRIu16, self->oldtag); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rflush_format(struct lib9p_msg_Rflush *self, struct fmt_state *state) { + fmt_state_puts(state, "Rflush {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Twalk_format(struct lib9p_msg_Twalk *self, struct fmt_state *state) { + fmt_state_puts(state, "Twalk {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " newfid="); + lib9p_fid_format(&self->newfid, state); + fmt_state_puts(state, " nwname="); + fmt_state_printf(state, "%"PRIu16, self->nwname); + fmt_state_puts(state, " wname=["); + for (uint16_t i = 0; i < self->nwname; i++) { + if (i) + fmt_state_puts(state, ", "); + lib9p_s_format(&self->wname[i], state); + } + fmt_state_puts(state, " ]"); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rwalk_format(struct lib9p_msg_Rwalk *self, struct fmt_state *state) { + fmt_state_puts(state, "Rwalk {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " nwqid="); + fmt_state_printf(state, "%"PRIu16, self->nwqid); + fmt_state_puts(state, " wqid=["); + for (uint16_t i = 0; i < self->nwqid; i++) { + if (i) + fmt_state_puts(state, ", "); + lib9p_qid_format(&self->wqid[i], state); + } + fmt_state_puts(state, " ]"); + fmt_state_puts(state, " }"); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_msg_Topen_format(struct lib9p_msg_Topen *self, struct fmt_state *state) { + fmt_state_puts(state, "Topen {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " mode="); + lib9p_o_format(&self->mode, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Ropen_format(struct lib9p_msg_Ropen *self, struct fmt_state *state) { + fmt_state_puts(state, "Ropen {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " iounit="); + fmt_state_printf(state, "%"PRIu32, self->iounit); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tcreate_format(struct lib9p_msg_Tcreate *self, struct fmt_state *state) { + fmt_state_puts(state, "Tcreate {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " perm="); + lib9p_dm_format(&self->perm, state); + fmt_state_puts(state, " mode="); + lib9p_o_format(&self->mode, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rcreate_format(struct lib9p_msg_Rcreate *self, struct fmt_state *state) { + fmt_state_puts(state, "Rcreate {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " iounit="); + fmt_state_printf(state, "%"PRIu32, self->iounit); + fmt_state_puts(state, " }"); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_msg_Tread_format(struct lib9p_msg_Tread *self, struct fmt_state *state) { + fmt_state_puts(state, "Tread {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " offset="); + fmt_state_printf(state, "%"PRIu64, self->offset); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rread_format(struct lib9p_msg_Rread *self, struct fmt_state *state) { + fmt_state_puts(state, "Rread {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " data=<bytedata>"); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Twrite_format(struct lib9p_msg_Twrite *self, struct fmt_state *state) { + fmt_state_puts(state, "Twrite {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " offset="); + fmt_state_printf(state, "%"PRIu64, self->offset); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " data=<bytedata>"); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rwrite_format(struct lib9p_msg_Rwrite *self, struct fmt_state *state) { + fmt_state_puts(state, "Rwrite {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tclunk_format(struct lib9p_msg_Tclunk *self, struct fmt_state *state) { + fmt_state_puts(state, "Tclunk {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rclunk_format(struct lib9p_msg_Rclunk *self, struct fmt_state *state) { + fmt_state_puts(state, "Rclunk {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tremove_format(struct lib9p_msg_Tremove *self, struct fmt_state *state) { + fmt_state_puts(state, "Tremove {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rremove_format(struct lib9p_msg_Rremove *self, struct fmt_state *state) { + fmt_state_puts(state, "Rremove {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_msg_Tstat_format(struct lib9p_msg_Tstat *self, struct fmt_state *state) { + fmt_state_puts(state, "Tstat {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rstat_format(struct lib9p_msg_Rstat *self, struct fmt_state *state) { + fmt_state_puts(state, "Rstat {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " stat="); + lib9p_stat_format(&self->stat, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Twstat_format(struct lib9p_msg_Twstat *self, struct fmt_state *state) { + fmt_state_puts(state, "Twstat {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " stat="); + lib9p_stat_format(&self->stat, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rwstat_format(struct lib9p_msg_Rwstat *self, struct fmt_state *state) { + fmt_state_puts(state, "Rwstat {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_p9p +static void lib9p_msg_Topenfd_format(struct lib9p_msg_Topenfd *self, struct fmt_state *state) { + fmt_state_puts(state, "Topenfd {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " mode="); + lib9p_o_format(&self->mode, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Ropenfd_format(struct lib9p_msg_Ropenfd *self, struct fmt_state *state) { + fmt_state_puts(state, "Ropenfd {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " iounit="); + fmt_state_printf(state, "%"PRIu32, self->iounit); + fmt_state_puts(state, " unixfd="); + fmt_state_printf(state, "%"PRIu32, self->unixfd); + fmt_state_puts(state, " }"); +} + +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u +static void lib9p_nuid_format(lib9p_nuid_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_NUID_NONUID: + fmt_state_puts(state, "NONUID"); + break; + default: + fmt_state_printf(state, "%"PRIu32, *self); + } +} + +static void lib9p_errno_format(lib9p_errno_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_ERRNO_NOERROR: + fmt_state_puts(state, "NOERROR"); + break; + default: + fmt_state_printf(state, "%"PRIu32, *self); + } +} + +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +static void lib9p_super_magic_format(lib9p_super_magic_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_SUPER_MAGIC_V9FS_MAGIC: + fmt_state_puts(state, "V9FS_MAGIC"); + break; + default: + fmt_state_printf(state, "%"PRIu32, *self); + } +} + +static void lib9p_lo_format(lib9p_lo_t *self, struct fmt_state *state) { + bool empty = true; + fmt_state_putchar(state, '('); + if (*self & (UINT32_C(1)<<31)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<31"); + empty = false; + } + if (*self & (UINT32_C(1)<<30)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<30"); + empty = false; + } + if (*self & (UINT32_C(1)<<29)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<29"); + empty = false; + } + if (*self & (UINT32_C(1)<<28)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<28"); + empty = false; + } + if (*self & (UINT32_C(1)<<27)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<27"); + empty = false; + } + if (*self & (UINT32_C(1)<<26)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<26"); + empty = false; + } + if (*self & (UINT32_C(1)<<25)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<25"); + empty = false; + } + if (*self & (UINT32_C(1)<<24)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<24"); + empty = false; + } + if (*self & (UINT32_C(1)<<23)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<23"); + empty = false; + } + if (*self & (UINT32_C(1)<<22)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<22"); + empty = false; + } + if (*self & (UINT32_C(1)<<21)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<21"); + empty = false; + } + if (*self & (UINT32_C(1)<<20)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "SYNC"); + empty = false; + } + if (*self & (UINT32_C(1)<<19)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "CLOEXEC"); + empty = false; + } + if (*self & (UINT32_C(1)<<18)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "NOATIME"); + empty = false; + } + if (*self & (UINT32_C(1)<<17)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "NOFOLLOW"); + empty = false; + } + if (*self & (UINT32_C(1)<<16)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "DIRECTORY"); + empty = false; + } + if (*self & (UINT32_C(1)<<15)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "LARGEFILE"); + empty = false; + } + if (*self & (UINT32_C(1)<<14)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "DIRECT"); + empty = false; + } + if (*self & (UINT32_C(1)<<13)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "BSD_FASYNC"); + empty = false; + } + if (*self & (UINT32_C(1)<<12)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "DSYNC"); + empty = false; + } + if (*self & (UINT32_C(1)<<11)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "NONBLOCK"); + empty = false; + } + if (*self & (UINT32_C(1)<<10)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "APPEND"); + empty = false; + } + if (*self & (UINT32_C(1)<<9)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "TRUNC"); + empty = false; + } + if (*self & (UINT32_C(1)<<8)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "NOCTTY"); + empty = false; + } + if (*self & (UINT32_C(1)<<7)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "EXCL"); + empty = false; + } + if (*self & (UINT32_C(1)<<6)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "CREATE"); + empty = false; + } + if (*self & (UINT32_C(1)<<5)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<5"); + empty = false; + } + if (*self & (UINT32_C(1)<<4)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<4"); + empty = false; + } + if (*self & (UINT32_C(1)<<3)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<3"); + empty = false; + } + if (*self & (UINT32_C(1)<<2)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<2"); + empty = false; + } + switch (*self & LIB9P_LO_MODE_MASK) { + case LIB9P_LO_MODE_RDONLY: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE_RDONLY"); + empty = false; + break; + case LIB9P_LO_MODE_WRONLY: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE_WRONLY"); + empty = false; + break; + case LIB9P_LO_MODE_RDWR: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE_RDWR"); + empty = false; + break; + case LIB9P_LO_MODE_NOACCESS: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE_NOACCESS"); + empty = false; + break; + default: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_printf(state, "%"PRIu32, *self & LIB9P_LO_MODE_MASK); + empty = false; + } + if (empty) + fmt_state_putchar(state, '0'); + fmt_state_putchar(state, ')'); +} + +static void lib9p_dt_format(lib9p_dt_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_DT_UNKNOWN: + fmt_state_puts(state, "UNKNOWN"); + break; + case LIB9P_DT_PIPE: + fmt_state_puts(state, "PIPE"); + break; + case LIB9P_DT_CHAR_DEV: + fmt_state_puts(state, "CHAR_DEV"); + break; + case LIB9P_DT_DIRECTORY: + fmt_state_puts(state, "DIRECTORY"); + break; + case LIB9P_DT_BLOCK_DEV: + fmt_state_puts(state, "BLOCK_DEV"); + break; + case LIB9P_DT_REGULAR: + fmt_state_puts(state, "REGULAR"); + break; + case LIB9P_DT_SYMLINK: + fmt_state_puts(state, "SYMLINK"); + break; + case LIB9P_DT_SOCKET: + fmt_state_puts(state, "SOCKET"); + break; + case _LIB9P_DT_WHITEOUT: + fmt_state_puts(state, "_WHITEOUT"); + break; + default: + fmt_state_printf(state, "%"PRIu8, *self); + } +} + +static void lib9p_mode_format(lib9p_mode_t *self, struct fmt_state *state) { + bool empty = true; + fmt_state_putchar(state, '('); + if (*self & (UINT32_C(1)<<31)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<31"); + empty = false; + } + if (*self & (UINT32_C(1)<<30)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<30"); + empty = false; + } + if (*self & (UINT32_C(1)<<29)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<29"); + empty = false; + } + if (*self & (UINT32_C(1)<<28)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<28"); + empty = false; + } + if (*self & (UINT32_C(1)<<27)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<27"); + empty = false; + } + if (*self & (UINT32_C(1)<<26)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<26"); + empty = false; + } + if (*self & (UINT32_C(1)<<25)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<25"); + empty = false; + } + if (*self & (UINT32_C(1)<<24)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<24"); + empty = false; + } + if (*self & (UINT32_C(1)<<23)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<23"); + empty = false; + } + if (*self & (UINT32_C(1)<<22)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<22"); + empty = false; + } + if (*self & (UINT32_C(1)<<21)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<21"); + empty = false; + } + if (*self & (UINT32_C(1)<<20)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<20"); + empty = false; + } + if (*self & (UINT32_C(1)<<19)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<19"); + empty = false; + } + if (*self & (UINT32_C(1)<<18)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<18"); + empty = false; + } + if (*self & (UINT32_C(1)<<17)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<17"); + empty = false; + } + if (*self & (UINT32_C(1)<<16)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<16"); + empty = false; + } + switch (*self & LIB9P_MODE_FMT_MASK) { + case LIB9P_MODE_FMT_PIPE: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "FMT_PIPE"); + empty = false; + break; + case LIB9P_MODE_FMT_CHAR_DEV: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "FMT_CHAR_DEV"); + empty = false; + break; + case LIB9P_MODE_FMT_DIRECTORY: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "FMT_DIRECTORY"); + empty = false; + break; + case LIB9P_MODE_FMT_BLOCK_DEV: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "FMT_BLOCK_DEV"); + empty = false; + break; + case LIB9P_MODE_FMT_REGULAR: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "FMT_REGULAR"); + empty = false; + break; + case LIB9P_MODE_FMT_SYMLINK: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "FMT_SYMLINK"); + empty = false; + break; + case LIB9P_MODE_FMT_SOCKET: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "FMT_SOCKET"); + empty = false; + break; + default: + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_printf(state, "%"PRIu32, *self & LIB9P_MODE_FMT_MASK); + empty = false; + } + if (*self & (UINT32_C(1)<<11)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_SETGROUP"); + empty = false; + } + if (*self & (UINT32_C(1)<<10)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_SETUSER"); + empty = false; + } + if (*self & (UINT32_C(1)<<9)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_STICKY"); + empty = false; + } + if (*self & (UINT32_C(1)<<8)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_OWNER_R"); + empty = false; + } + if (*self & (UINT32_C(1)<<7)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_OWNER_W"); + empty = false; + } + if (*self & (UINT32_C(1)<<6)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_OWNER_X"); + empty = false; + } + if (*self & (UINT32_C(1)<<5)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_GROUP_R"); + empty = false; + } + if (*self & (UINT32_C(1)<<4)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_GROUP_W"); + empty = false; + } + if (*self & (UINT32_C(1)<<3)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_GROUP_X"); + empty = false; + } + if (*self & (UINT32_C(1)<<2)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_OTHER_R"); + empty = false; + } + if (*self & (UINT32_C(1)<<1)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_OTHER_W"); + empty = false; + } + if (*self & (UINT32_C(1)<<0)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "PERM_OTHER_X"); + empty = false; + } + if (empty) + fmt_state_putchar(state, '0'); + fmt_state_putchar(state, ')'); +} + +static void lib9p_b4_format(lib9p_b4_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_B4_FALSE: + fmt_state_puts(state, "FALSE"); + break; + case LIB9P_B4_TRUE: + fmt_state_puts(state, "TRUE"); + break; + default: + fmt_state_printf(state, "%"PRIu32, *self); + } +} + +static void lib9p_getattr_format(lib9p_getattr_t *self, struct fmt_state *state) { + bool empty = true; + fmt_state_putchar(state, '('); + if (*self & (UINT64_C(1)<<63)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<63"); + empty = false; + } + if (*self & (UINT64_C(1)<<62)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<62"); + empty = false; + } + if (*self & (UINT64_C(1)<<61)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<61"); + empty = false; + } + if (*self & (UINT64_C(1)<<60)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<60"); + empty = false; + } + if (*self & (UINT64_C(1)<<59)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<59"); + empty = false; + } + if (*self & (UINT64_C(1)<<58)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<58"); + empty = false; + } + if (*self & (UINT64_C(1)<<57)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<57"); + empty = false; + } + if (*self & (UINT64_C(1)<<56)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<56"); + empty = false; + } + if (*self & (UINT64_C(1)<<55)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<55"); + empty = false; + } + if (*self & (UINT64_C(1)<<54)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<54"); + empty = false; + } + if (*self & (UINT64_C(1)<<53)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<53"); + empty = false; + } + if (*self & (UINT64_C(1)<<52)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<52"); + empty = false; + } + if (*self & (UINT64_C(1)<<51)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<51"); + empty = false; + } + if (*self & (UINT64_C(1)<<50)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<50"); + empty = false; + } + if (*self & (UINT64_C(1)<<49)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<49"); + empty = false; + } + if (*self & (UINT64_C(1)<<48)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<48"); + empty = false; + } + if (*self & (UINT64_C(1)<<47)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<47"); + empty = false; + } + if (*self & (UINT64_C(1)<<46)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<46"); + empty = false; + } + if (*self & (UINT64_C(1)<<45)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<45"); + empty = false; + } + if (*self & (UINT64_C(1)<<44)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<44"); + empty = false; + } + if (*self & (UINT64_C(1)<<43)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<43"); + empty = false; + } + if (*self & (UINT64_C(1)<<42)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<42"); + empty = false; + } + if (*self & (UINT64_C(1)<<41)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<41"); + empty = false; + } + if (*self & (UINT64_C(1)<<40)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<40"); + empty = false; + } + if (*self & (UINT64_C(1)<<39)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<39"); + empty = false; + } + if (*self & (UINT64_C(1)<<38)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<38"); + empty = false; + } + if (*self & (UINT64_C(1)<<37)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<37"); + empty = false; + } + if (*self & (UINT64_C(1)<<36)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<36"); + empty = false; + } + if (*self & (UINT64_C(1)<<35)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<35"); + empty = false; + } + if (*self & (UINT64_C(1)<<34)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<34"); + empty = false; + } + if (*self & (UINT64_C(1)<<33)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<33"); + empty = false; + } + if (*self & (UINT64_C(1)<<32)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<32"); + empty = false; + } + if (*self & (UINT64_C(1)<<31)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<31"); + empty = false; + } + if (*self & (UINT64_C(1)<<30)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<30"); + empty = false; + } + if (*self & (UINT64_C(1)<<29)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<29"); + empty = false; + } + if (*self & (UINT64_C(1)<<28)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<28"); + empty = false; + } + if (*self & (UINT64_C(1)<<27)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<27"); + empty = false; + } + if (*self & (UINT64_C(1)<<26)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<26"); + empty = false; + } + if (*self & (UINT64_C(1)<<25)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<25"); + empty = false; + } + if (*self & (UINT64_C(1)<<24)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<24"); + empty = false; + } + if (*self & (UINT64_C(1)<<23)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<23"); + empty = false; + } + if (*self & (UINT64_C(1)<<22)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<22"); + empty = false; + } + if (*self & (UINT64_C(1)<<21)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<21"); + empty = false; + } + if (*self & (UINT64_C(1)<<20)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<20"); + empty = false; + } + if (*self & (UINT64_C(1)<<19)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<19"); + empty = false; + } + if (*self & (UINT64_C(1)<<18)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<18"); + empty = false; + } + if (*self & (UINT64_C(1)<<17)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<17"); + empty = false; + } + if (*self & (UINT64_C(1)<<16)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<16"); + empty = false; + } + if (*self & (UINT64_C(1)<<15)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<15"); + empty = false; + } + if (*self & (UINT64_C(1)<<14)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<14"); + empty = false; + } + if (*self & (UINT64_C(1)<<13)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "DATA_VERSION"); + empty = false; + } + if (*self & (UINT64_C(1)<<12)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "GEN"); + empty = false; + } + if (*self & (UINT64_C(1)<<11)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "BTIME"); + empty = false; + } + if (*self & (UINT64_C(1)<<10)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "BLOCKS"); + empty = false; + } + if (*self & (UINT64_C(1)<<9)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "SIZE"); + empty = false; + } + if (*self & (UINT64_C(1)<<8)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "INO"); + empty = false; + } + if (*self & (UINT64_C(1)<<7)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "CTIME"); + empty = false; + } + if (*self & (UINT64_C(1)<<6)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MTIME"); + empty = false; + } + if (*self & (UINT64_C(1)<<5)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "ATIME"); + empty = false; + } + if (*self & (UINT64_C(1)<<4)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "RDEV"); + empty = false; + } + if (*self & (UINT64_C(1)<<3)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "GID"); + empty = false; + } + if (*self & (UINT64_C(1)<<2)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "UID"); + empty = false; + } + if (*self & (UINT64_C(1)<<1)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "NLINK"); + empty = false; + } + if (*self & (UINT64_C(1)<<0)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE"); + empty = false; + } + if (empty) + fmt_state_putchar(state, '0'); + fmt_state_putchar(state, ')'); +} + +static void lib9p_setattr_format(lib9p_setattr_t *self, struct fmt_state *state) { + bool empty = true; + fmt_state_putchar(state, '('); + if (*self & (UINT32_C(1)<<31)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<31"); + empty = false; + } + if (*self & (UINT32_C(1)<<30)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<30"); + empty = false; + } + if (*self & (UINT32_C(1)<<29)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<29"); + empty = false; + } + if (*self & (UINT32_C(1)<<28)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<28"); + empty = false; + } + if (*self & (UINT32_C(1)<<27)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<27"); + empty = false; + } + if (*self & (UINT32_C(1)<<26)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<26"); + empty = false; + } + if (*self & (UINT32_C(1)<<25)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<25"); + empty = false; + } + if (*self & (UINT32_C(1)<<24)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<24"); + empty = false; + } + if (*self & (UINT32_C(1)<<23)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<23"); + empty = false; + } + if (*self & (UINT32_C(1)<<22)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<22"); + empty = false; + } + if (*self & (UINT32_C(1)<<21)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<21"); + empty = false; + } + if (*self & (UINT32_C(1)<<20)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<20"); + empty = false; + } + if (*self & (UINT32_C(1)<<19)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<19"); + empty = false; + } + if (*self & (UINT32_C(1)<<18)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<18"); + empty = false; + } + if (*self & (UINT32_C(1)<<17)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<17"); + empty = false; + } + if (*self & (UINT32_C(1)<<16)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<16"); + empty = false; + } + if (*self & (UINT32_C(1)<<15)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<15"); + empty = false; + } + if (*self & (UINT32_C(1)<<14)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<14"); + empty = false; + } + if (*self & (UINT32_C(1)<<13)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<13"); + empty = false; + } + if (*self & (UINT32_C(1)<<12)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<12"); + empty = false; + } + if (*self & (UINT32_C(1)<<11)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<11"); + empty = false; + } + if (*self & (UINT32_C(1)<<10)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<10"); + empty = false; + } + if (*self & (UINT32_C(1)<<9)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<9"); + empty = false; + } + if (*self & (UINT32_C(1)<<8)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MTIME_SET"); + empty = false; + } + if (*self & (UINT32_C(1)<<7)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "ATIME_SET"); + empty = false; + } + if (*self & (UINT32_C(1)<<6)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "CTIME"); + empty = false; + } + if (*self & (UINT32_C(1)<<5)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MTIME"); + empty = false; + } + if (*self & (UINT32_C(1)<<4)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "ATIME"); + empty = false; + } + if (*self & (UINT32_C(1)<<3)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "SIZE"); + empty = false; + } + if (*self & (UINT32_C(1)<<2)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "GID"); + empty = false; + } + if (*self & (UINT32_C(1)<<1)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "UID"); + empty = false; + } + if (*self & (UINT32_C(1)<<0)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "MODE"); + empty = false; + } + if (empty) + fmt_state_putchar(state, '0'); + fmt_state_putchar(state, ')'); +} + +static void lib9p_lock_type_format(lib9p_lock_type_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_LOCK_TYPE_RDLCK: + fmt_state_puts(state, "RDLCK"); + break; + case LIB9P_LOCK_TYPE_WRLCK: + fmt_state_puts(state, "WRLCK"); + break; + case LIB9P_LOCK_TYPE_UNLCK: + fmt_state_puts(state, "UNLCK"); + break; + default: + fmt_state_printf(state, "%"PRIu8, *self); + } +} + +static void lib9p_lock_flags_format(lib9p_lock_flags_t *self, struct fmt_state *state) { + bool empty = true; + fmt_state_putchar(state, '('); + if (*self & (UINT32_C(1)<<31)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<31"); + empty = false; + } + if (*self & (UINT32_C(1)<<30)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<30"); + empty = false; + } + if (*self & (UINT32_C(1)<<29)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<29"); + empty = false; + } + if (*self & (UINT32_C(1)<<28)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<28"); + empty = false; + } + if (*self & (UINT32_C(1)<<27)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<27"); + empty = false; + } + if (*self & (UINT32_C(1)<<26)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<26"); + empty = false; + } + if (*self & (UINT32_C(1)<<25)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<25"); + empty = false; + } + if (*self & (UINT32_C(1)<<24)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<24"); + empty = false; + } + if (*self & (UINT32_C(1)<<23)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<23"); + empty = false; + } + if (*self & (UINT32_C(1)<<22)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<22"); + empty = false; + } + if (*self & (UINT32_C(1)<<21)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<21"); + empty = false; + } + if (*self & (UINT32_C(1)<<20)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<20"); + empty = false; + } + if (*self & (UINT32_C(1)<<19)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<19"); + empty = false; + } + if (*self & (UINT32_C(1)<<18)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<18"); + empty = false; + } + if (*self & (UINT32_C(1)<<17)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<17"); + empty = false; + } + if (*self & (UINT32_C(1)<<16)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<16"); + empty = false; + } + if (*self & (UINT32_C(1)<<15)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<15"); + empty = false; + } + if (*self & (UINT32_C(1)<<14)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<14"); + empty = false; + } + if (*self & (UINT32_C(1)<<13)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<13"); + empty = false; + } + if (*self & (UINT32_C(1)<<12)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<12"); + empty = false; + } + if (*self & (UINT32_C(1)<<11)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<11"); + empty = false; + } + if (*self & (UINT32_C(1)<<10)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<10"); + empty = false; + } + if (*self & (UINT32_C(1)<<9)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<9"); + empty = false; + } + if (*self & (UINT32_C(1)<<8)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<8"); + empty = false; + } + if (*self & (UINT32_C(1)<<7)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<7"); + empty = false; + } + if (*self & (UINT32_C(1)<<6)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<6"); + empty = false; + } + if (*self & (UINT32_C(1)<<5)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<5"); + empty = false; + } + if (*self & (UINT32_C(1)<<4)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<4"); + empty = false; + } + if (*self & (UINT32_C(1)<<3)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<3"); + empty = false; + } + if (*self & (UINT32_C(1)<<2)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "1<<2"); + empty = false; + } + if (*self & (UINT32_C(1)<<1)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "RECLAIM"); + empty = false; + } + if (*self & (UINT32_C(1)<<0)) { + if (!empty) + fmt_state_putchar(state, '|'); + fmt_state_puts(state, "BLOCK"); + empty = false; + } + if (empty) + fmt_state_putchar(state, '0'); + fmt_state_putchar(state, ')'); +} + +static void lib9p_lock_status_format(lib9p_lock_status_t *self, struct fmt_state *state) { + switch (*self) { + case LIB9P_LOCK_STATUS_SUCCESS: + fmt_state_puts(state, "SUCCESS"); + break; + case LIB9P_LOCK_STATUS_BLOCKED: + fmt_state_puts(state, "BLOCKED"); + break; + case LIB9P_LOCK_STATUS_ERROR: + fmt_state_puts(state, "ERROR"); + break; + case LIB9P_LOCK_STATUS_GRACE: + fmt_state_puts(state, "GRACE"); + break; + default: + fmt_state_printf(state, "%"PRIu8, *self); + } +} + +static void lib9p_msg_Rlerror_format(struct lib9p_msg_Rlerror *self, struct fmt_state *state) { + fmt_state_puts(state, "Rlerror {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " ecode="); + lib9p_errno_format(&self->ecode, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tstatfs_format(struct lib9p_msg_Tstatfs *self, struct fmt_state *state) { + fmt_state_puts(state, "Tstatfs {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rstatfs_format(struct lib9p_msg_Rstatfs *self, struct fmt_state *state) { + fmt_state_puts(state, "Rstatfs {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " type="); + lib9p_super_magic_format(&self->type, state); + fmt_state_puts(state, " bsize="); + fmt_state_printf(state, "%"PRIu32, self->bsize); + fmt_state_puts(state, " blocks="); + fmt_state_printf(state, "%"PRIu64, self->blocks); + fmt_state_puts(state, " bfree="); + fmt_state_printf(state, "%"PRIu64, self->bfree); + fmt_state_puts(state, " bavail="); + fmt_state_printf(state, "%"PRIu64, self->bavail); + fmt_state_puts(state, " files="); + fmt_state_printf(state, "%"PRIu64, self->files); + fmt_state_puts(state, " ffree="); + fmt_state_printf(state, "%"PRIu64, self->ffree); + fmt_state_puts(state, " fsid="); + fmt_state_printf(state, "%"PRIu64, self->fsid); + fmt_state_puts(state, " namelen="); + fmt_state_printf(state, "%"PRIu32, self->namelen); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tlopen_format(struct lib9p_msg_Tlopen *self, struct fmt_state *state) { + fmt_state_puts(state, "Tlopen {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " flags="); + lib9p_lo_format(&self->flags, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rlopen_format(struct lib9p_msg_Rlopen *self, struct fmt_state *state) { + fmt_state_puts(state, "Rlopen {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " iounit="); + fmt_state_printf(state, "%"PRIu32, self->iounit); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tlcreate_format(struct lib9p_msg_Tlcreate *self, struct fmt_state *state) { + fmt_state_puts(state, "Tlcreate {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " flags="); + lib9p_lo_format(&self->flags, state); + fmt_state_puts(state, " mode="); + lib9p_mode_format(&self->mode, state); + fmt_state_puts(state, " gid="); + lib9p_nuid_format(&self->gid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rlcreate_format(struct lib9p_msg_Rlcreate *self, struct fmt_state *state) { + fmt_state_puts(state, "Rlcreate {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " iounit="); + fmt_state_printf(state, "%"PRIu32, self->iounit); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tsymlink_format(struct lib9p_msg_Tsymlink *self, struct fmt_state *state) { + fmt_state_puts(state, "Tsymlink {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " symtgt="); + lib9p_s_format(&self->symtgt, state); + fmt_state_puts(state, " gid="); + lib9p_nuid_format(&self->gid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rsymlink_format(struct lib9p_msg_Rsymlink *self, struct fmt_state *state) { + fmt_state_puts(state, "Rsymlink {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tmknod_format(struct lib9p_msg_Tmknod *self, struct fmt_state *state) { + fmt_state_puts(state, "Tmknod {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " dfid="); + lib9p_fid_format(&self->dfid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " mode="); + lib9p_mode_format(&self->mode, state); + fmt_state_puts(state, " major="); + fmt_state_printf(state, "%"PRIu32, self->major); + fmt_state_puts(state, " minor="); + fmt_state_printf(state, "%"PRIu32, self->minor); + fmt_state_puts(state, " gid="); + lib9p_nuid_format(&self->gid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rmknod_format(struct lib9p_msg_Rmknod *self, struct fmt_state *state) { + fmt_state_puts(state, "Rmknod {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Trename_format(struct lib9p_msg_Trename *self, struct fmt_state *state) { + fmt_state_puts(state, "Trename {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " dfid="); + lib9p_fid_format(&self->dfid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rrename_format(struct lib9p_msg_Rrename *self, struct fmt_state *state) { + fmt_state_puts(state, "Rrename {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Treadlink_format(struct lib9p_msg_Treadlink *self, struct fmt_state *state) { + fmt_state_puts(state, "Treadlink {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rreadlink_format(struct lib9p_msg_Rreadlink *self, struct fmt_state *state) { + fmt_state_puts(state, "Rreadlink {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " target="); + lib9p_s_format(&self->target, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tgetattr_format(struct lib9p_msg_Tgetattr *self, struct fmt_state *state) { + fmt_state_puts(state, "Tgetattr {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " request_mask="); + lib9p_getattr_format(&self->request_mask, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rgetattr_format(struct lib9p_msg_Rgetattr *self, struct fmt_state *state) { + fmt_state_puts(state, "Rgetattr {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " valid="); + lib9p_getattr_format(&self->valid, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " mode="); + lib9p_mode_format(&self->mode, state); + fmt_state_puts(state, " uid="); + lib9p_nuid_format(&self->uid, state); + fmt_state_puts(state, " gid="); + lib9p_nuid_format(&self->gid, state); + fmt_state_puts(state, " nlink="); + fmt_state_printf(state, "%"PRIu64, self->nlink); + fmt_state_puts(state, " rdev="); + fmt_state_printf(state, "%"PRIu64, self->rdev); + fmt_state_puts(state, " filesize="); + fmt_state_printf(state, "%"PRIu64, self->filesize); + fmt_state_puts(state, " blksize="); + fmt_state_printf(state, "%"PRIu64, self->blksize); + fmt_state_puts(state, " blocks="); + fmt_state_printf(state, "%"PRIu64, self->blocks); + fmt_state_puts(state, " atime_sec="); + fmt_state_printf(state, "%"PRIu64, self->atime_sec); + fmt_state_puts(state, " atime_nsec="); + fmt_state_printf(state, "%"PRIu64, self->atime_nsec); + fmt_state_puts(state, " mtime_sec="); + fmt_state_printf(state, "%"PRIu64, self->mtime_sec); + fmt_state_puts(state, " mtime_nsec="); + fmt_state_printf(state, "%"PRIu64, self->mtime_nsec); + fmt_state_puts(state, " ctime_sec="); + fmt_state_printf(state, "%"PRIu64, self->ctime_sec); + fmt_state_puts(state, " ctime_nsec="); + fmt_state_printf(state, "%"PRIu64, self->ctime_nsec); + fmt_state_puts(state, " btime_sec="); + fmt_state_printf(state, "%"PRIu64, self->btime_sec); + fmt_state_puts(state, " btime_nsec="); + fmt_state_printf(state, "%"PRIu64, self->btime_nsec); + fmt_state_puts(state, " gen="); + fmt_state_printf(state, "%"PRIu64, self->gen); + fmt_state_puts(state, " data_version="); + fmt_state_printf(state, "%"PRIu64, self->data_version); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tsetattr_format(struct lib9p_msg_Tsetattr *self, struct fmt_state *state) { + fmt_state_puts(state, "Tsetattr {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " valid="); + lib9p_setattr_format(&self->valid, state); + fmt_state_puts(state, " mode="); + lib9p_mode_format(&self->mode, state); + fmt_state_puts(state, " uid="); + lib9p_nuid_format(&self->uid, state); + fmt_state_puts(state, " gid="); + lib9p_nuid_format(&self->gid, state); + fmt_state_puts(state, " filesize="); + fmt_state_printf(state, "%"PRIu64, self->filesize); + fmt_state_puts(state, " atime_sec="); + fmt_state_printf(state, "%"PRIu64, self->atime_sec); + fmt_state_puts(state, " atime_nsec="); + fmt_state_printf(state, "%"PRIu64, self->atime_nsec); + fmt_state_puts(state, " mtime_sec="); + fmt_state_printf(state, "%"PRIu64, self->mtime_sec); + fmt_state_puts(state, " mtime_nsec="); + fmt_state_printf(state, "%"PRIu64, self->mtime_nsec); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rsetattr_format(struct lib9p_msg_Rsetattr *self, struct fmt_state *state) { + fmt_state_puts(state, "Rsetattr {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Txattrwalk_format(struct lib9p_msg_Txattrwalk *self, struct fmt_state *state) { + fmt_state_puts(state, "Txattrwalk {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " newfid="); + lib9p_fid_format(&self->newfid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rxattrwalk_format(struct lib9p_msg_Rxattrwalk *self, struct fmt_state *state) { + fmt_state_puts(state, "Rxattrwalk {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " attr_size="); + fmt_state_printf(state, "%"PRIu64, self->attr_size); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Txattrcreate_format(struct lib9p_msg_Txattrcreate *self, struct fmt_state *state) { + fmt_state_puts(state, "Txattrcreate {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " attr_size="); + fmt_state_printf(state, "%"PRIu64, self->attr_size); + fmt_state_puts(state, " flags="); + fmt_state_printf(state, "%"PRIu32, self->flags); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rxattrcreate_format(struct lib9p_msg_Rxattrcreate *self, struct fmt_state *state) { + fmt_state_puts(state, "Rxattrcreate {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Treaddir_format(struct lib9p_msg_Treaddir *self, struct fmt_state *state) { + fmt_state_puts(state, "Treaddir {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " offset="); + fmt_state_printf(state, "%"PRIu64, self->offset); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rreaddir_format(struct lib9p_msg_Rreaddir *self, struct fmt_state *state) { + fmt_state_puts(state, "Rreaddir {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " data=<bytedata>"); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tfsync_format(struct lib9p_msg_Tfsync *self, struct fmt_state *state) { + fmt_state_puts(state, "Tfsync {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " datasync="); + lib9p_b4_format(&self->datasync, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rfsync_format(struct lib9p_msg_Rfsync *self, struct fmt_state *state) { + fmt_state_puts(state, "Rfsync {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tlock_format(struct lib9p_msg_Tlock *self, struct fmt_state *state) { + fmt_state_puts(state, "Tlock {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " type="); + lib9p_lock_type_format(&self->type, state); + fmt_state_puts(state, " flags="); + lib9p_lock_flags_format(&self->flags, state); + fmt_state_puts(state, " start="); + fmt_state_printf(state, "%"PRIu64, self->start); + fmt_state_puts(state, " length="); + fmt_state_printf(state, "%"PRIu64, self->length); + fmt_state_puts(state, " proc_id="); + fmt_state_printf(state, "%"PRIu32, self->proc_id); + fmt_state_puts(state, " client_id="); + lib9p_s_format(&self->client_id, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rlock_format(struct lib9p_msg_Rlock *self, struct fmt_state *state) { + fmt_state_puts(state, "Rlock {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " status="); + lib9p_lock_status_format(&self->status, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tgetlock_format(struct lib9p_msg_Tgetlock *self, struct fmt_state *state) { + fmt_state_puts(state, "Tgetlock {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " type="); + lib9p_lock_type_format(&self->type, state); + fmt_state_puts(state, " start="); + fmt_state_printf(state, "%"PRIu64, self->start); + fmt_state_puts(state, " length="); + fmt_state_printf(state, "%"PRIu64, self->length); + fmt_state_puts(state, " proc_id="); + fmt_state_printf(state, "%"PRIu32, self->proc_id); + fmt_state_puts(state, " client_id="); + lib9p_s_format(&self->client_id, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rgetlock_format(struct lib9p_msg_Rgetlock *self, struct fmt_state *state) { + fmt_state_puts(state, "Rgetlock {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " type="); + lib9p_lock_type_format(&self->type, state); + fmt_state_puts(state, " start="); + fmt_state_printf(state, "%"PRIu64, self->start); + fmt_state_puts(state, " length="); + fmt_state_printf(state, "%"PRIu64, self->length); + fmt_state_puts(state, " proc_id="); + fmt_state_printf(state, "%"PRIu32, self->proc_id); + fmt_state_puts(state, " client_id="); + lib9p_s_format(&self->client_id, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tlink_format(struct lib9p_msg_Tlink *self, struct fmt_state *state) { + fmt_state_puts(state, "Tlink {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " dfid="); + lib9p_fid_format(&self->dfid, state); + fmt_state_puts(state, " fid="); + lib9p_fid_format(&self->fid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rlink_format(struct lib9p_msg_Rlink *self, struct fmt_state *state) { + fmt_state_puts(state, "Rlink {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tmkdir_format(struct lib9p_msg_Tmkdir *self, struct fmt_state *state) { + fmt_state_puts(state, "Tmkdir {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " dfid="); + lib9p_fid_format(&self->dfid, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " mode="); + lib9p_mode_format(&self->mode, state); + fmt_state_puts(state, " gid="); + lib9p_nuid_format(&self->gid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rmkdir_format(struct lib9p_msg_Rmkdir *self, struct fmt_state *state) { + fmt_state_puts(state, "Rmkdir {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " qid="); + lib9p_qid_format(&self->qid, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Trenameat_format(struct lib9p_msg_Trenameat *self, struct fmt_state *state) { + fmt_state_puts(state, "Trenameat {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " olddirfid="); + lib9p_fid_format(&self->olddirfid, state); + fmt_state_puts(state, " oldname="); + lib9p_s_format(&self->oldname, state); + fmt_state_puts(state, " newdirfid="); + lib9p_fid_format(&self->newdirfid, state); + fmt_state_puts(state, " newname="); + lib9p_s_format(&self->newname, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rrenameat_format(struct lib9p_msg_Rrenameat *self, struct fmt_state *state) { + fmt_state_puts(state, "Rrenameat {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tunlinkat_format(struct lib9p_msg_Tunlinkat *self, struct fmt_state *state) { + fmt_state_puts(state, "Tunlinkat {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " dirfd="); + lib9p_fid_format(&self->dirfd, state); + fmt_state_puts(state, " name="); + lib9p_s_format(&self->name, state); + fmt_state_puts(state, " flags="); + fmt_state_printf(state, "%"PRIu32, self->flags); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Runlinkat_format(struct lib9p_msg_Runlinkat *self, struct fmt_state *state) { + fmt_state_puts(state, "Runlinkat {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +#endif /* CONFIG_9P_ENABLE_9P2000_L */ #if CONFIG_9P_ENABLE_9P2000_e -FLATTEN static bool marshal_Tsession(struct _marshal_ctx *ctx, struct lib9p_msg_Tsession *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_8(ctx, &val->key) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(150, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rsession(struct _marshal_ctx *ctx, struct lib9p_msg_Rsession *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(151, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tsread(struct _marshal_ctx *ctx, struct lib9p_msg_Tsread *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_4(ctx, &val->fid) - || marshal_2(ctx, &val->nwname) - || ({ bool err = false; - for (typeof(val->nwname) i = 0; i < val->nwname && !err; i++) - err = marshal_s(ctx, &val->wname[i]); - err; }) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(152, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rsread(struct _marshal_ctx *ctx, struct lib9p_msg_Rsread *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_d(ctx, &val->data) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(153, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Tswrite(struct _marshal_ctx *ctx, struct lib9p_msg_Tswrite *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_4(ctx, &val->fid) - || marshal_2(ctx, &val->nwname) - || ({ bool err = false; - for (typeof(val->nwname) i = 0; i < val->nwname && !err; i++) - err = marshal_s(ctx, &val->wname[i]); - err; }) - || marshal_d(ctx, &val->data) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(154, &ctx->net_bytes[_typ_offset]); false; }) - ; -} - -FLATTEN static bool marshal_Rswrite(struct _marshal_ctx *ctx, struct lib9p_msg_Rswrite *val) { - uint32_t _size_offset; - uint32_t _typ_offset; - return false - || ({ _size_offset = ctx->net_offset; ({ ctx->net_offset += 4; false; }); }) - || ({ _typ_offset = ctx->net_offset; ({ ctx->net_offset += 1; false; }); }) - || marshal_tag(ctx, &val->tag) - || marshal_4(ctx, &val->count) - || ({ encode_u32le(ctx->net_offset - _size_offset, &ctx->net_bytes[_size_offset]); false; }) - || ({ encode_u8le(155, &ctx->net_bytes[_typ_offset]); false; }) - ; +static void lib9p_msg_Tsession_format(struct lib9p_msg_Tsession *self, struct fmt_state *state) { + fmt_state_puts(state, "Tsession {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " key="); + fmt_state_printf(state, "%"PRIu64, self->key); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rsession_format(struct lib9p_msg_Rsession *self, struct fmt_state *state) { + fmt_state_puts(state, "Rsession {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tsread_format(struct lib9p_msg_Tsread *self, struct fmt_state *state) { + fmt_state_puts(state, "Tsread {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + fmt_state_printf(state, "%"PRIu32, self->fid); + fmt_state_puts(state, " nwname="); + fmt_state_printf(state, "%"PRIu16, self->nwname); + fmt_state_puts(state, " wname=["); + for (uint16_t i = 0; i < self->nwname; i++) { + if (i) + fmt_state_puts(state, ", "); + lib9p_s_format(&self->wname[i], state); + } + fmt_state_puts(state, " ]"); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rsread_format(struct lib9p_msg_Rsread *self, struct fmt_state *state) { + fmt_state_puts(state, "Rsread {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " data=<bytedata>"); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Tswrite_format(struct lib9p_msg_Tswrite *self, struct fmt_state *state) { + fmt_state_puts(state, "Tswrite {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " fid="); + fmt_state_printf(state, "%"PRIu32, self->fid); + fmt_state_puts(state, " nwname="); + fmt_state_printf(state, "%"PRIu16, self->nwname); + fmt_state_puts(state, " wname=["); + for (uint16_t i = 0; i < self->nwname; i++) { + if (i) + fmt_state_puts(state, ", "); + lib9p_s_format(&self->wname[i], state); + } + fmt_state_puts(state, " ]"); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " data=<bytedata>"); + fmt_state_puts(state, " }"); +} + +static void lib9p_msg_Rswrite_format(struct lib9p_msg_Rswrite *self, struct fmt_state *state) { + fmt_state_puts(state, "Rswrite {"); + fmt_state_puts(state, " tag="); + lib9p_tag_format(&self->tag, state); + fmt_state_puts(state, " count="); + fmt_state_printf(state, "%"PRIu32, self->count); + fmt_state_puts(state, " }"); } #endif /* CONFIG_9P_ENABLE_9P2000_e */ -/* tables / exports ***********************************************************/ - -#define _MSG(typ) [LIB9P_TYP_##typ] = { \ - .name = #typ, \ - .basesize = sizeof(struct lib9p_msg_##typ), \ - .validate = validate_##typ, \ - .unmarshal = (_unmarshal_fn_t)unmarshal_##typ, \ - .marshal = (_marshal_fn_t)marshal_##typ, \ - } -#define _NONMSG(num) [num] = { \ - .name = #num, \ - } - -struct _table_version _lib9p_versions[LIB9P_VER_NUM] = { - [LIB9P_VER_unknown] = { .msgs = { - _NONMSG(0x00), - _NONMSG(0x01), - _NONMSG(0x02), - _NONMSG(0x03), - _NONMSG(0x04), - _NONMSG(0x05), - _NONMSG(0x06), - _NONMSG(0x07), - _NONMSG(0x08), - _NONMSG(0x09), - _NONMSG(0x0A), - _NONMSG(0x0B), - _NONMSG(0x0C), - _NONMSG(0x0D), - _NONMSG(0x0E), - _NONMSG(0x0F), - _NONMSG(0x10), - _NONMSG(0x11), - _NONMSG(0x12), - _NONMSG(0x13), - _NONMSG(0x14), - _NONMSG(0x15), - _NONMSG(0x16), - _NONMSG(0x17), - _NONMSG(0x18), - _NONMSG(0x19), - _NONMSG(0x1A), - _NONMSG(0x1B), - _NONMSG(0x1C), - _NONMSG(0x1D), - _NONMSG(0x1E), - _NONMSG(0x1F), - _NONMSG(0x20), - _NONMSG(0x21), - _NONMSG(0x22), - _NONMSG(0x23), - _NONMSG(0x24), - _NONMSG(0x25), - _NONMSG(0x26), - _NONMSG(0x27), - _NONMSG(0x28), - _NONMSG(0x29), - _NONMSG(0x2A), - _NONMSG(0x2B), - _NONMSG(0x2C), - _NONMSG(0x2D), - _NONMSG(0x2E), - _NONMSG(0x2F), - _NONMSG(0x30), - _NONMSG(0x31), - _NONMSG(0x32), - _NONMSG(0x33), - _NONMSG(0x34), - _NONMSG(0x35), - _NONMSG(0x36), - _NONMSG(0x37), - _NONMSG(0x38), - _NONMSG(0x39), - _NONMSG(0x3A), - _NONMSG(0x3B), - _NONMSG(0x3C), - _NONMSG(0x3D), - _NONMSG(0x3E), - _NONMSG(0x3F), - _NONMSG(0x40), - _NONMSG(0x41), - _NONMSG(0x42), - _NONMSG(0x43), - _NONMSG(0x44), - _NONMSG(0x45), - _NONMSG(0x46), - _NONMSG(0x47), - _NONMSG(0x48), - _NONMSG(0x49), - _NONMSG(0x4A), - _NONMSG(0x4B), - _NONMSG(0x4C), - _NONMSG(0x4D), - _NONMSG(0x4E), - _NONMSG(0x4F), - _NONMSG(0x50), - _NONMSG(0x51), - _NONMSG(0x52), - _NONMSG(0x53), - _NONMSG(0x54), - _NONMSG(0x55), - _NONMSG(0x56), - _NONMSG(0x57), - _NONMSG(0x58), - _NONMSG(0x59), - _NONMSG(0x5A), - _NONMSG(0x5B), - _NONMSG(0x5C), - _NONMSG(0x5D), - _NONMSG(0x5E), - _NONMSG(0x5F), - _NONMSG(0x60), - _NONMSG(0x61), - _NONMSG(0x62), - _NONMSG(0x63), +/* tables.h *******************************************************************/ + +const struct _lib9p_ver_tentry _lib9p_table_ver[LIB9P_VER_NUM] = { + [LIB9P_VER_unknown] = {.name="unknown", .min_msg_size=9}, +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = {.name="9P2000", .min_msg_size=9}, +#endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = {.name="9P2000.L", .min_msg_size=9}, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = {.name="9P2000.e", .min_msg_size=9}, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = {.name="9P2000.p9p", .min_msg_size=9}, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_u + [LIB9P_VER_9P2000_u] = {.name="9P2000.u", .min_msg_size=13}, +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; + +#define _MSG(typ) [LIB9P_TYP_##typ] = { \ + .name = #typ, \ + .box_as_fmt_formatter = (_box_as_fmt_formatter_fn_t)lo_box_lib9p_msg_##typ##_as_fmt_formatter, \ + } +const struct _lib9p_msg_tentry _lib9p_table_msg[LIB9P_VER_NUM][0x100] = { + [LIB9P_VER_unknown] = { _MSG(Tversion), _MSG(Rversion), - _NONMSG(0x66), - _NONMSG(0x67), - _NONMSG(0x68), - _NONMSG(0x69), - _NONMSG(0x6A), _MSG(Rerror), - _NONMSG(0x6C), - _NONMSG(0x6D), - _NONMSG(0x6E), - _NONMSG(0x6F), - _NONMSG(0x70), - _NONMSG(0x71), - _NONMSG(0x72), - _NONMSG(0x73), - _NONMSG(0x74), - _NONMSG(0x75), - _NONMSG(0x76), - _NONMSG(0x77), - _NONMSG(0x78), - _NONMSG(0x79), - _NONMSG(0x7A), - _NONMSG(0x7B), - _NONMSG(0x7C), - _NONMSG(0x7D), - _NONMSG(0x7E), - _NONMSG(0x7F), - _NONMSG(0x80), - _NONMSG(0x81), - _NONMSG(0x82), - _NONMSG(0x83), - _NONMSG(0x84), - _NONMSG(0x85), - _NONMSG(0x86), - _NONMSG(0x87), - _NONMSG(0x88), - _NONMSG(0x89), - _NONMSG(0x8A), - _NONMSG(0x8B), - _NONMSG(0x8C), - _NONMSG(0x8D), - _NONMSG(0x8E), - _NONMSG(0x8F), - _NONMSG(0x90), - _NONMSG(0x91), - _NONMSG(0x92), - _NONMSG(0x93), - _NONMSG(0x94), - _NONMSG(0x95), - _NONMSG(0x96), - _NONMSG(0x97), - _NONMSG(0x98), - _NONMSG(0x99), - _NONMSG(0x9A), - _NONMSG(0x9B), - _NONMSG(0x9C), - _NONMSG(0x9D), - _NONMSG(0x9E), - _NONMSG(0x9F), - _NONMSG(0xA0), - _NONMSG(0xA1), - _NONMSG(0xA2), - _NONMSG(0xA3), - _NONMSG(0xA4), - _NONMSG(0xA5), - _NONMSG(0xA6), - _NONMSG(0xA7), - _NONMSG(0xA8), - _NONMSG(0xA9), - _NONMSG(0xAA), - _NONMSG(0xAB), - _NONMSG(0xAC), - _NONMSG(0xAD), - _NONMSG(0xAE), - _NONMSG(0xAF), - _NONMSG(0xB0), - _NONMSG(0xB1), - _NONMSG(0xB2), - _NONMSG(0xB3), - _NONMSG(0xB4), - _NONMSG(0xB5), - _NONMSG(0xB6), - _NONMSG(0xB7), - _NONMSG(0xB8), - _NONMSG(0xB9), - _NONMSG(0xBA), - _NONMSG(0xBB), - _NONMSG(0xBC), - _NONMSG(0xBD), - _NONMSG(0xBE), - _NONMSG(0xBF), - _NONMSG(0xC0), - _NONMSG(0xC1), - _NONMSG(0xC2), - _NONMSG(0xC3), - _NONMSG(0xC4), - _NONMSG(0xC5), - _NONMSG(0xC6), - _NONMSG(0xC7), - _NONMSG(0xC8), - _NONMSG(0xC9), - _NONMSG(0xCA), - _NONMSG(0xCB), - _NONMSG(0xCC), - _NONMSG(0xCD), - _NONMSG(0xCE), - _NONMSG(0xCF), - _NONMSG(0xD0), - _NONMSG(0xD1), - _NONMSG(0xD2), - _NONMSG(0xD3), - _NONMSG(0xD4), - _NONMSG(0xD5), - _NONMSG(0xD6), - _NONMSG(0xD7), - _NONMSG(0xD8), - _NONMSG(0xD9), - _NONMSG(0xDA), - _NONMSG(0xDB), - _NONMSG(0xDC), - _NONMSG(0xDD), - _NONMSG(0xDE), - _NONMSG(0xDF), - _NONMSG(0xE0), - _NONMSG(0xE1), - _NONMSG(0xE2), - _NONMSG(0xE3), - _NONMSG(0xE4), - _NONMSG(0xE5), - _NONMSG(0xE6), - _NONMSG(0xE7), - _NONMSG(0xE8), - _NONMSG(0xE9), - _NONMSG(0xEA), - _NONMSG(0xEB), - _NONMSG(0xEC), - _NONMSG(0xED), - _NONMSG(0xEE), - _NONMSG(0xEF), - _NONMSG(0xF0), - _NONMSG(0xF1), - _NONMSG(0xF2), - _NONMSG(0xF3), - _NONMSG(0xF4), - _NONMSG(0xF5), - _NONMSG(0xF6), - _NONMSG(0xF7), - _NONMSG(0xF8), - _NONMSG(0xF9), - _NONMSG(0xFA), - _NONMSG(0xFB), - _NONMSG(0xFC), - _NONMSG(0xFD), - _NONMSG(0xFE), - _NONMSG(0xFF), - }}, + }, #if CONFIG_9P_ENABLE_9P2000 - [LIB9P_VER_9P2000] = { .msgs = { - _NONMSG(0x00), - _NONMSG(0x01), - _NONMSG(0x02), - _NONMSG(0x03), - _NONMSG(0x04), - _NONMSG(0x05), - _NONMSG(0x06), - _NONMSG(0x07), - _NONMSG(0x08), - _NONMSG(0x09), - _NONMSG(0x0A), - _NONMSG(0x0B), - _NONMSG(0x0C), - _NONMSG(0x0D), - _NONMSG(0x0E), - _NONMSG(0x0F), - _NONMSG(0x10), - _NONMSG(0x11), - _NONMSG(0x12), - _NONMSG(0x13), - _NONMSG(0x14), - _NONMSG(0x15), - _NONMSG(0x16), - _NONMSG(0x17), - _NONMSG(0x18), - _NONMSG(0x19), - _NONMSG(0x1A), - _NONMSG(0x1B), - _NONMSG(0x1C), - _NONMSG(0x1D), - _NONMSG(0x1E), - _NONMSG(0x1F), - _NONMSG(0x20), - _NONMSG(0x21), - _NONMSG(0x22), - _NONMSG(0x23), - _NONMSG(0x24), - _NONMSG(0x25), - _NONMSG(0x26), - _NONMSG(0x27), - _NONMSG(0x28), - _NONMSG(0x29), - _NONMSG(0x2A), - _NONMSG(0x2B), - _NONMSG(0x2C), - _NONMSG(0x2D), - _NONMSG(0x2E), - _NONMSG(0x2F), - _NONMSG(0x30), - _NONMSG(0x31), - _NONMSG(0x32), - _NONMSG(0x33), - _NONMSG(0x34), - _NONMSG(0x35), - _NONMSG(0x36), - _NONMSG(0x37), - _NONMSG(0x38), - _NONMSG(0x39), - _NONMSG(0x3A), - _NONMSG(0x3B), - _NONMSG(0x3C), - _NONMSG(0x3D), - _NONMSG(0x3E), - _NONMSG(0x3F), - _NONMSG(0x40), - _NONMSG(0x41), - _NONMSG(0x42), - _NONMSG(0x43), - _NONMSG(0x44), - _NONMSG(0x45), - _NONMSG(0x46), - _NONMSG(0x47), - _NONMSG(0x48), - _NONMSG(0x49), - _NONMSG(0x4A), - _NONMSG(0x4B), - _NONMSG(0x4C), - _NONMSG(0x4D), - _NONMSG(0x4E), - _NONMSG(0x4F), - _NONMSG(0x50), - _NONMSG(0x51), - _NONMSG(0x52), - _NONMSG(0x53), - _NONMSG(0x54), - _NONMSG(0x55), - _NONMSG(0x56), - _NONMSG(0x57), - _NONMSG(0x58), - _NONMSG(0x59), - _NONMSG(0x5A), - _NONMSG(0x5B), - _NONMSG(0x5C), - _NONMSG(0x5D), - _NONMSG(0x5E), - _NONMSG(0x5F), - _NONMSG(0x60), - _NONMSG(0x61), - _NONMSG(0x62), - _NONMSG(0x63), + [LIB9P_VER_9P2000] = { _MSG(Tversion), _MSG(Rversion), _MSG(Tauth), _MSG(Rauth), _MSG(Tattach), _MSG(Rattach), - _NONMSG(0x6A), _MSG(Rerror), _MSG(Tflush), _MSG(Rflush), @@ -2181,245 +7397,78 @@ struct _table_version _lib9p_versions[LIB9P_VER_NUM] = { _MSG(Rstat), _MSG(Twstat), _MSG(Rwstat), - _NONMSG(0x80), - _NONMSG(0x81), - _NONMSG(0x82), - _NONMSG(0x83), - _NONMSG(0x84), - _NONMSG(0x85), - _NONMSG(0x86), - _NONMSG(0x87), - _NONMSG(0x88), - _NONMSG(0x89), - _NONMSG(0x8A), - _NONMSG(0x8B), - _NONMSG(0x8C), - _NONMSG(0x8D), - _NONMSG(0x8E), - _NONMSG(0x8F), - _NONMSG(0x90), - _NONMSG(0x91), - _NONMSG(0x92), - _NONMSG(0x93), - _NONMSG(0x94), - _NONMSG(0x95), - _NONMSG(0x96), - _NONMSG(0x97), - _NONMSG(0x98), - _NONMSG(0x99), - _NONMSG(0x9A), - _NONMSG(0x9B), - _NONMSG(0x9C), - _NONMSG(0x9D), - _NONMSG(0x9E), - _NONMSG(0x9F), - _NONMSG(0xA0), - _NONMSG(0xA1), - _NONMSG(0xA2), - _NONMSG(0xA3), - _NONMSG(0xA4), - _NONMSG(0xA5), - _NONMSG(0xA6), - _NONMSG(0xA7), - _NONMSG(0xA8), - _NONMSG(0xA9), - _NONMSG(0xAA), - _NONMSG(0xAB), - _NONMSG(0xAC), - _NONMSG(0xAD), - _NONMSG(0xAE), - _NONMSG(0xAF), - _NONMSG(0xB0), - _NONMSG(0xB1), - _NONMSG(0xB2), - _NONMSG(0xB3), - _NONMSG(0xB4), - _NONMSG(0xB5), - _NONMSG(0xB6), - _NONMSG(0xB7), - _NONMSG(0xB8), - _NONMSG(0xB9), - _NONMSG(0xBA), - _NONMSG(0xBB), - _NONMSG(0xBC), - _NONMSG(0xBD), - _NONMSG(0xBE), - _NONMSG(0xBF), - _NONMSG(0xC0), - _NONMSG(0xC1), - _NONMSG(0xC2), - _NONMSG(0xC3), - _NONMSG(0xC4), - _NONMSG(0xC5), - _NONMSG(0xC6), - _NONMSG(0xC7), - _NONMSG(0xC8), - _NONMSG(0xC9), - _NONMSG(0xCA), - _NONMSG(0xCB), - _NONMSG(0xCC), - _NONMSG(0xCD), - _NONMSG(0xCE), - _NONMSG(0xCF), - _NONMSG(0xD0), - _NONMSG(0xD1), - _NONMSG(0xD2), - _NONMSG(0xD3), - _NONMSG(0xD4), - _NONMSG(0xD5), - _NONMSG(0xD6), - _NONMSG(0xD7), - _NONMSG(0xD8), - _NONMSG(0xD9), - _NONMSG(0xDA), - _NONMSG(0xDB), - _NONMSG(0xDC), - _NONMSG(0xDD), - _NONMSG(0xDE), - _NONMSG(0xDF), - _NONMSG(0xE0), - _NONMSG(0xE1), - _NONMSG(0xE2), - _NONMSG(0xE3), - _NONMSG(0xE4), - _NONMSG(0xE5), - _NONMSG(0xE6), - _NONMSG(0xE7), - _NONMSG(0xE8), - _NONMSG(0xE9), - _NONMSG(0xEA), - _NONMSG(0xEB), - _NONMSG(0xEC), - _NONMSG(0xED), - _NONMSG(0xEE), - _NONMSG(0xEF), - _NONMSG(0xF0), - _NONMSG(0xF1), - _NONMSG(0xF2), - _NONMSG(0xF3), - _NONMSG(0xF4), - _NONMSG(0xF5), - _NONMSG(0xF6), - _NONMSG(0xF7), - _NONMSG(0xF8), - _NONMSG(0xF9), - _NONMSG(0xFA), - _NONMSG(0xFB), - _NONMSG(0xFC), - _NONMSG(0xFD), - _NONMSG(0xFE), - _NONMSG(0xFF), - }}, + }, #endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = { + _MSG(Rlerror), + _MSG(Tstatfs), + _MSG(Rstatfs), + _MSG(Tlopen), + _MSG(Rlopen), + _MSG(Tlcreate), + _MSG(Rlcreate), + _MSG(Tsymlink), + _MSG(Rsymlink), + _MSG(Tmknod), + _MSG(Rmknod), + _MSG(Trename), + _MSG(Rrename), + _MSG(Treadlink), + _MSG(Rreadlink), + _MSG(Tgetattr), + _MSG(Rgetattr), + _MSG(Tsetattr), + _MSG(Rsetattr), + _MSG(Txattrwalk), + _MSG(Rxattrwalk), + _MSG(Txattrcreate), + _MSG(Rxattrcreate), + _MSG(Treaddir), + _MSG(Rreaddir), + _MSG(Tfsync), + _MSG(Rfsync), + _MSG(Tlock), + _MSG(Rlock), + _MSG(Tgetlock), + _MSG(Rgetlock), + _MSG(Tlink), + _MSG(Rlink), + _MSG(Tmkdir), + _MSG(Rmkdir), + _MSG(Trenameat), + _MSG(Rrenameat), + _MSG(Tunlinkat), + _MSG(Runlinkat), + _MSG(Tversion), + _MSG(Rversion), + _MSG(Tauth), + _MSG(Rauth), + _MSG(Tattach), + _MSG(Rattach), + _MSG(Rerror), + _MSG(Tflush), + _MSG(Rflush), + _MSG(Twalk), + _MSG(Rwalk), + _MSG(Tread), + _MSG(Rread), + _MSG(Twrite), + _MSG(Rwrite), + _MSG(Tclunk), + _MSG(Rclunk), + _MSG(Tremove), + _MSG(Rremove), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ #if CONFIG_9P_ENABLE_9P2000_e - [LIB9P_VER_9P2000_e] = { .msgs = { - _NONMSG(0x00), - _NONMSG(0x01), - _NONMSG(0x02), - _NONMSG(0x03), - _NONMSG(0x04), - _NONMSG(0x05), - _NONMSG(0x06), - _NONMSG(0x07), - _NONMSG(0x08), - _NONMSG(0x09), - _NONMSG(0x0A), - _NONMSG(0x0B), - _NONMSG(0x0C), - _NONMSG(0x0D), - _NONMSG(0x0E), - _NONMSG(0x0F), - _NONMSG(0x10), - _NONMSG(0x11), - _NONMSG(0x12), - _NONMSG(0x13), - _NONMSG(0x14), - _NONMSG(0x15), - _NONMSG(0x16), - _NONMSG(0x17), - _NONMSG(0x18), - _NONMSG(0x19), - _NONMSG(0x1A), - _NONMSG(0x1B), - _NONMSG(0x1C), - _NONMSG(0x1D), - _NONMSG(0x1E), - _NONMSG(0x1F), - _NONMSG(0x20), - _NONMSG(0x21), - _NONMSG(0x22), - _NONMSG(0x23), - _NONMSG(0x24), - _NONMSG(0x25), - _NONMSG(0x26), - _NONMSG(0x27), - _NONMSG(0x28), - _NONMSG(0x29), - _NONMSG(0x2A), - _NONMSG(0x2B), - _NONMSG(0x2C), - _NONMSG(0x2D), - _NONMSG(0x2E), - _NONMSG(0x2F), - _NONMSG(0x30), - _NONMSG(0x31), - _NONMSG(0x32), - _NONMSG(0x33), - _NONMSG(0x34), - _NONMSG(0x35), - _NONMSG(0x36), - _NONMSG(0x37), - _NONMSG(0x38), - _NONMSG(0x39), - _NONMSG(0x3A), - _NONMSG(0x3B), - _NONMSG(0x3C), - _NONMSG(0x3D), - _NONMSG(0x3E), - _NONMSG(0x3F), - _NONMSG(0x40), - _NONMSG(0x41), - _NONMSG(0x42), - _NONMSG(0x43), - _NONMSG(0x44), - _NONMSG(0x45), - _NONMSG(0x46), - _NONMSG(0x47), - _NONMSG(0x48), - _NONMSG(0x49), - _NONMSG(0x4A), - _NONMSG(0x4B), - _NONMSG(0x4C), - _NONMSG(0x4D), - _NONMSG(0x4E), - _NONMSG(0x4F), - _NONMSG(0x50), - _NONMSG(0x51), - _NONMSG(0x52), - _NONMSG(0x53), - _NONMSG(0x54), - _NONMSG(0x55), - _NONMSG(0x56), - _NONMSG(0x57), - _NONMSG(0x58), - _NONMSG(0x59), - _NONMSG(0x5A), - _NONMSG(0x5B), - _NONMSG(0x5C), - _NONMSG(0x5D), - _NONMSG(0x5E), - _NONMSG(0x5F), - _NONMSG(0x60), - _NONMSG(0x61), - _NONMSG(0x62), - _NONMSG(0x63), + [LIB9P_VER_9P2000_e] = { _MSG(Tversion), _MSG(Rversion), _MSG(Tauth), _MSG(Rauth), _MSG(Tattach), _MSG(Rattach), - _NONMSG(0x6A), _MSG(Rerror), _MSG(Tflush), _MSG(Rflush), @@ -2441,245 +7490,55 @@ struct _table_version _lib9p_versions[LIB9P_VER_NUM] = { _MSG(Rstat), _MSG(Twstat), _MSG(Rwstat), - _NONMSG(0x80), - _NONMSG(0x81), - _NONMSG(0x82), - _NONMSG(0x83), - _NONMSG(0x84), - _NONMSG(0x85), - _NONMSG(0x86), - _NONMSG(0x87), - _NONMSG(0x88), - _NONMSG(0x89), - _NONMSG(0x8A), - _NONMSG(0x8B), - _NONMSG(0x8C), - _NONMSG(0x8D), - _NONMSG(0x8E), - _NONMSG(0x8F), - _NONMSG(0x90), - _NONMSG(0x91), - _NONMSG(0x92), - _NONMSG(0x93), - _NONMSG(0x94), - _NONMSG(0x95), _MSG(Tsession), _MSG(Rsession), _MSG(Tsread), _MSG(Rsread), _MSG(Tswrite), _MSG(Rswrite), - _NONMSG(0x9C), - _NONMSG(0x9D), - _NONMSG(0x9E), - _NONMSG(0x9F), - _NONMSG(0xA0), - _NONMSG(0xA1), - _NONMSG(0xA2), - _NONMSG(0xA3), - _NONMSG(0xA4), - _NONMSG(0xA5), - _NONMSG(0xA6), - _NONMSG(0xA7), - _NONMSG(0xA8), - _NONMSG(0xA9), - _NONMSG(0xAA), - _NONMSG(0xAB), - _NONMSG(0xAC), - _NONMSG(0xAD), - _NONMSG(0xAE), - _NONMSG(0xAF), - _NONMSG(0xB0), - _NONMSG(0xB1), - _NONMSG(0xB2), - _NONMSG(0xB3), - _NONMSG(0xB4), - _NONMSG(0xB5), - _NONMSG(0xB6), - _NONMSG(0xB7), - _NONMSG(0xB8), - _NONMSG(0xB9), - _NONMSG(0xBA), - _NONMSG(0xBB), - _NONMSG(0xBC), - _NONMSG(0xBD), - _NONMSG(0xBE), - _NONMSG(0xBF), - _NONMSG(0xC0), - _NONMSG(0xC1), - _NONMSG(0xC2), - _NONMSG(0xC3), - _NONMSG(0xC4), - _NONMSG(0xC5), - _NONMSG(0xC6), - _NONMSG(0xC7), - _NONMSG(0xC8), - _NONMSG(0xC9), - _NONMSG(0xCA), - _NONMSG(0xCB), - _NONMSG(0xCC), - _NONMSG(0xCD), - _NONMSG(0xCE), - _NONMSG(0xCF), - _NONMSG(0xD0), - _NONMSG(0xD1), - _NONMSG(0xD2), - _NONMSG(0xD3), - _NONMSG(0xD4), - _NONMSG(0xD5), - _NONMSG(0xD6), - _NONMSG(0xD7), - _NONMSG(0xD8), - _NONMSG(0xD9), - _NONMSG(0xDA), - _NONMSG(0xDB), - _NONMSG(0xDC), - _NONMSG(0xDD), - _NONMSG(0xDE), - _NONMSG(0xDF), - _NONMSG(0xE0), - _NONMSG(0xE1), - _NONMSG(0xE2), - _NONMSG(0xE3), - _NONMSG(0xE4), - _NONMSG(0xE5), - _NONMSG(0xE6), - _NONMSG(0xE7), - _NONMSG(0xE8), - _NONMSG(0xE9), - _NONMSG(0xEA), - _NONMSG(0xEB), - _NONMSG(0xEC), - _NONMSG(0xED), - _NONMSG(0xEE), - _NONMSG(0xEF), - _NONMSG(0xF0), - _NONMSG(0xF1), - _NONMSG(0xF2), - _NONMSG(0xF3), - _NONMSG(0xF4), - _NONMSG(0xF5), - _NONMSG(0xF6), - _NONMSG(0xF7), - _NONMSG(0xF8), - _NONMSG(0xF9), - _NONMSG(0xFA), - _NONMSG(0xFB), - _NONMSG(0xFC), - _NONMSG(0xFD), - _NONMSG(0xFE), - _NONMSG(0xFF), - }}, + }, #endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = { + _MSG(Topenfd), + _MSG(Ropenfd), + _MSG(Tversion), + _MSG(Rversion), + _MSG(Tauth), + _MSG(Rauth), + _MSG(Tattach), + _MSG(Rattach), + _MSG(Rerror), + _MSG(Tflush), + _MSG(Rflush), + _MSG(Twalk), + _MSG(Rwalk), + _MSG(Topen), + _MSG(Ropen), + _MSG(Tcreate), + _MSG(Rcreate), + _MSG(Tread), + _MSG(Rread), + _MSG(Twrite), + _MSG(Rwrite), + _MSG(Tclunk), + _MSG(Rclunk), + _MSG(Tremove), + _MSG(Rremove), + _MSG(Tstat), + _MSG(Rstat), + _MSG(Twstat), + _MSG(Rwstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ #if CONFIG_9P_ENABLE_9P2000_u - [LIB9P_VER_9P2000_u] = { .msgs = { - _NONMSG(0x00), - _NONMSG(0x01), - _NONMSG(0x02), - _NONMSG(0x03), - _NONMSG(0x04), - _NONMSG(0x05), - _NONMSG(0x06), - _NONMSG(0x07), - _NONMSG(0x08), - _NONMSG(0x09), - _NONMSG(0x0A), - _NONMSG(0x0B), - _NONMSG(0x0C), - _NONMSG(0x0D), - _NONMSG(0x0E), - _NONMSG(0x0F), - _NONMSG(0x10), - _NONMSG(0x11), - _NONMSG(0x12), - _NONMSG(0x13), - _NONMSG(0x14), - _NONMSG(0x15), - _NONMSG(0x16), - _NONMSG(0x17), - _NONMSG(0x18), - _NONMSG(0x19), - _NONMSG(0x1A), - _NONMSG(0x1B), - _NONMSG(0x1C), - _NONMSG(0x1D), - _NONMSG(0x1E), - _NONMSG(0x1F), - _NONMSG(0x20), - _NONMSG(0x21), - _NONMSG(0x22), - _NONMSG(0x23), - _NONMSG(0x24), - _NONMSG(0x25), - _NONMSG(0x26), - _NONMSG(0x27), - _NONMSG(0x28), - _NONMSG(0x29), - _NONMSG(0x2A), - _NONMSG(0x2B), - _NONMSG(0x2C), - _NONMSG(0x2D), - _NONMSG(0x2E), - _NONMSG(0x2F), - _NONMSG(0x30), - _NONMSG(0x31), - _NONMSG(0x32), - _NONMSG(0x33), - _NONMSG(0x34), - _NONMSG(0x35), - _NONMSG(0x36), - _NONMSG(0x37), - _NONMSG(0x38), - _NONMSG(0x39), - _NONMSG(0x3A), - _NONMSG(0x3B), - _NONMSG(0x3C), - _NONMSG(0x3D), - _NONMSG(0x3E), - _NONMSG(0x3F), - _NONMSG(0x40), - _NONMSG(0x41), - _NONMSG(0x42), - _NONMSG(0x43), - _NONMSG(0x44), - _NONMSG(0x45), - _NONMSG(0x46), - _NONMSG(0x47), - _NONMSG(0x48), - _NONMSG(0x49), - _NONMSG(0x4A), - _NONMSG(0x4B), - _NONMSG(0x4C), - _NONMSG(0x4D), - _NONMSG(0x4E), - _NONMSG(0x4F), - _NONMSG(0x50), - _NONMSG(0x51), - _NONMSG(0x52), - _NONMSG(0x53), - _NONMSG(0x54), - _NONMSG(0x55), - _NONMSG(0x56), - _NONMSG(0x57), - _NONMSG(0x58), - _NONMSG(0x59), - _NONMSG(0x5A), - _NONMSG(0x5B), - _NONMSG(0x5C), - _NONMSG(0x5D), - _NONMSG(0x5E), - _NONMSG(0x5F), - _NONMSG(0x60), - _NONMSG(0x61), - _NONMSG(0x62), - _NONMSG(0x63), + [LIB9P_VER_9P2000_u] = { _MSG(Tversion), _MSG(Rversion), _MSG(Tauth), _MSG(Rauth), _MSG(Tattach), _MSG(Rattach), - _NONMSG(0x6A), _MSG(Rerror), _MSG(Tflush), _MSG(Rflush), @@ -2701,144 +7560,478 @@ struct _table_version _lib9p_versions[LIB9P_VER_NUM] = { _MSG(Rstat), _MSG(Twstat), _MSG(Rwstat), - _NONMSG(0x80), - _NONMSG(0x81), - _NONMSG(0x82), - _NONMSG(0x83), - _NONMSG(0x84), - _NONMSG(0x85), - _NONMSG(0x86), - _NONMSG(0x87), - _NONMSG(0x88), - _NONMSG(0x89), - _NONMSG(0x8A), - _NONMSG(0x8B), - _NONMSG(0x8C), - _NONMSG(0x8D), - _NONMSG(0x8E), - _NONMSG(0x8F), - _NONMSG(0x90), - _NONMSG(0x91), - _NONMSG(0x92), - _NONMSG(0x93), - _NONMSG(0x94), - _NONMSG(0x95), - _NONMSG(0x96), - _NONMSG(0x97), - _NONMSG(0x98), - _NONMSG(0x99), - _NONMSG(0x9A), - _NONMSG(0x9B), - _NONMSG(0x9C), - _NONMSG(0x9D), - _NONMSG(0x9E), - _NONMSG(0x9F), - _NONMSG(0xA0), - _NONMSG(0xA1), - _NONMSG(0xA2), - _NONMSG(0xA3), - _NONMSG(0xA4), - _NONMSG(0xA5), - _NONMSG(0xA6), - _NONMSG(0xA7), - _NONMSG(0xA8), - _NONMSG(0xA9), - _NONMSG(0xAA), - _NONMSG(0xAB), - _NONMSG(0xAC), - _NONMSG(0xAD), - _NONMSG(0xAE), - _NONMSG(0xAF), - _NONMSG(0xB0), - _NONMSG(0xB1), - _NONMSG(0xB2), - _NONMSG(0xB3), - _NONMSG(0xB4), - _NONMSG(0xB5), - _NONMSG(0xB6), - _NONMSG(0xB7), - _NONMSG(0xB8), - _NONMSG(0xB9), - _NONMSG(0xBA), - _NONMSG(0xBB), - _NONMSG(0xBC), - _NONMSG(0xBD), - _NONMSG(0xBE), - _NONMSG(0xBF), - _NONMSG(0xC0), - _NONMSG(0xC1), - _NONMSG(0xC2), - _NONMSG(0xC3), - _NONMSG(0xC4), - _NONMSG(0xC5), - _NONMSG(0xC6), - _NONMSG(0xC7), - _NONMSG(0xC8), - _NONMSG(0xC9), - _NONMSG(0xCA), - _NONMSG(0xCB), - _NONMSG(0xCC), - _NONMSG(0xCD), - _NONMSG(0xCE), - _NONMSG(0xCF), - _NONMSG(0xD0), - _NONMSG(0xD1), - _NONMSG(0xD2), - _NONMSG(0xD3), - _NONMSG(0xD4), - _NONMSG(0xD5), - _NONMSG(0xD6), - _NONMSG(0xD7), - _NONMSG(0xD8), - _NONMSG(0xD9), - _NONMSG(0xDA), - _NONMSG(0xDB), - _NONMSG(0xDC), - _NONMSG(0xDD), - _NONMSG(0xDE), - _NONMSG(0xDF), - _NONMSG(0xE0), - _NONMSG(0xE1), - _NONMSG(0xE2), - _NONMSG(0xE3), - _NONMSG(0xE4), - _NONMSG(0xE5), - _NONMSG(0xE6), - _NONMSG(0xE7), - _NONMSG(0xE8), - _NONMSG(0xE9), - _NONMSG(0xEA), - _NONMSG(0xEB), - _NONMSG(0xEC), - _NONMSG(0xED), - _NONMSG(0xEE), - _NONMSG(0xEF), - _NONMSG(0xF0), - _NONMSG(0xF1), - _NONMSG(0xF2), - _NONMSG(0xF3), - _NONMSG(0xF4), - _NONMSG(0xF5), - _NONMSG(0xF6), - _NONMSG(0xF7), - _NONMSG(0xF8), - _NONMSG(0xF9), - _NONMSG(0xFA), - _NONMSG(0xFB), - _NONMSG(0xFC), - _NONMSG(0xFD), - _NONMSG(0xFE), - _NONMSG(0xFF), - }}, + }, +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; + +#define _MSG_RECV(typ) [LIB9P_TYP_##typ/2] = { \ + .validate = validate_##typ, \ + .unmarshal = (_unmarshal_fn_t)unmarshal_##typ, \ + } +#define _MSG_SEND(typ) [LIB9P_TYP_##typ/2] = { \ + .marshal = (_marshal_fn_t)marshal_##typ, \ + } + +const struct _lib9p_recv_tentry _lib9p_table_Tmsg_recv[LIB9P_VER_NUM][0x80] = { + [LIB9P_VER_unknown] = { + _MSG_RECV(Tversion), + }, +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = { + _MSG_RECV(Tversion), + _MSG_RECV(Tauth), + _MSG_RECV(Tattach), + _MSG_RECV(Tflush), + _MSG_RECV(Twalk), + _MSG_RECV(Topen), + _MSG_RECV(Tcreate), + _MSG_RECV(Tread), + _MSG_RECV(Twrite), + _MSG_RECV(Tclunk), + _MSG_RECV(Tremove), + _MSG_RECV(Tstat), + _MSG_RECV(Twstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = { + _MSG_RECV(Tstatfs), + _MSG_RECV(Tlopen), + _MSG_RECV(Tlcreate), + _MSG_RECV(Tsymlink), + _MSG_RECV(Tmknod), + _MSG_RECV(Trename), + _MSG_RECV(Treadlink), + _MSG_RECV(Tgetattr), + _MSG_RECV(Tsetattr), + _MSG_RECV(Txattrwalk), + _MSG_RECV(Txattrcreate), + _MSG_RECV(Treaddir), + _MSG_RECV(Tfsync), + _MSG_RECV(Tlock), + _MSG_RECV(Tgetlock), + _MSG_RECV(Tlink), + _MSG_RECV(Tmkdir), + _MSG_RECV(Trenameat), + _MSG_RECV(Tunlinkat), + _MSG_RECV(Tversion), + _MSG_RECV(Tauth), + _MSG_RECV(Tattach), + _MSG_RECV(Tflush), + _MSG_RECV(Twalk), + _MSG_RECV(Tread), + _MSG_RECV(Twrite), + _MSG_RECV(Tclunk), + _MSG_RECV(Tremove), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = { + _MSG_RECV(Tversion), + _MSG_RECV(Tauth), + _MSG_RECV(Tattach), + _MSG_RECV(Tflush), + _MSG_RECV(Twalk), + _MSG_RECV(Topen), + _MSG_RECV(Tcreate), + _MSG_RECV(Tread), + _MSG_RECV(Twrite), + _MSG_RECV(Tclunk), + _MSG_RECV(Tremove), + _MSG_RECV(Tstat), + _MSG_RECV(Twstat), + _MSG_RECV(Tsession), + _MSG_RECV(Tsread), + _MSG_RECV(Tswrite), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = { + _MSG_RECV(Topenfd), + _MSG_RECV(Tversion), + _MSG_RECV(Tauth), + _MSG_RECV(Tattach), + _MSG_RECV(Tflush), + _MSG_RECV(Twalk), + _MSG_RECV(Topen), + _MSG_RECV(Tcreate), + _MSG_RECV(Tread), + _MSG_RECV(Twrite), + _MSG_RECV(Tclunk), + _MSG_RECV(Tremove), + _MSG_RECV(Tstat), + _MSG_RECV(Twstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_u + [LIB9P_VER_9P2000_u] = { + _MSG_RECV(Tversion), + _MSG_RECV(Tauth), + _MSG_RECV(Tattach), + _MSG_RECV(Tflush), + _MSG_RECV(Twalk), + _MSG_RECV(Topen), + _MSG_RECV(Tcreate), + _MSG_RECV(Tread), + _MSG_RECV(Twrite), + _MSG_RECV(Tclunk), + _MSG_RECV(Tremove), + _MSG_RECV(Tstat), + _MSG_RECV(Twstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; + +const struct _lib9p_recv_tentry _lib9p_table_Rmsg_recv[LIB9P_VER_NUM][0x80] = { + [LIB9P_VER_unknown] = { + _MSG_RECV(Rversion), + _MSG_RECV(Rerror), + }, +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = { + _MSG_RECV(Rversion), + _MSG_RECV(Rauth), + _MSG_RECV(Rattach), + _MSG_RECV(Rerror), + _MSG_RECV(Rflush), + _MSG_RECV(Rwalk), + _MSG_RECV(Ropen), + _MSG_RECV(Rcreate), + _MSG_RECV(Rread), + _MSG_RECV(Rwrite), + _MSG_RECV(Rclunk), + _MSG_RECV(Rremove), + _MSG_RECV(Rstat), + _MSG_RECV(Rwstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = { + _MSG_RECV(Rlerror), + _MSG_RECV(Rstatfs), + _MSG_RECV(Rlopen), + _MSG_RECV(Rlcreate), + _MSG_RECV(Rsymlink), + _MSG_RECV(Rmknod), + _MSG_RECV(Rrename), + _MSG_RECV(Rreadlink), + _MSG_RECV(Rgetattr), + _MSG_RECV(Rsetattr), + _MSG_RECV(Rxattrwalk), + _MSG_RECV(Rxattrcreate), + _MSG_RECV(Rreaddir), + _MSG_RECV(Rfsync), + _MSG_RECV(Rlock), + _MSG_RECV(Rgetlock), + _MSG_RECV(Rlink), + _MSG_RECV(Rmkdir), + _MSG_RECV(Rrenameat), + _MSG_RECV(Runlinkat), + _MSG_RECV(Rversion), + _MSG_RECV(Rauth), + _MSG_RECV(Rattach), + _MSG_RECV(Rerror), + _MSG_RECV(Rflush), + _MSG_RECV(Rwalk), + _MSG_RECV(Rread), + _MSG_RECV(Rwrite), + _MSG_RECV(Rclunk), + _MSG_RECV(Rremove), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = { + _MSG_RECV(Rversion), + _MSG_RECV(Rauth), + _MSG_RECV(Rattach), + _MSG_RECV(Rerror), + _MSG_RECV(Rflush), + _MSG_RECV(Rwalk), + _MSG_RECV(Ropen), + _MSG_RECV(Rcreate), + _MSG_RECV(Rread), + _MSG_RECV(Rwrite), + _MSG_RECV(Rclunk), + _MSG_RECV(Rremove), + _MSG_RECV(Rstat), + _MSG_RECV(Rwstat), + _MSG_RECV(Rsession), + _MSG_RECV(Rsread), + _MSG_RECV(Rswrite), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = { + _MSG_RECV(Ropenfd), + _MSG_RECV(Rversion), + _MSG_RECV(Rauth), + _MSG_RECV(Rattach), + _MSG_RECV(Rerror), + _MSG_RECV(Rflush), + _MSG_RECV(Rwalk), + _MSG_RECV(Ropen), + _MSG_RECV(Rcreate), + _MSG_RECV(Rread), + _MSG_RECV(Rwrite), + _MSG_RECV(Rclunk), + _MSG_RECV(Rremove), + _MSG_RECV(Rstat), + _MSG_RECV(Rwstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_u + [LIB9P_VER_9P2000_u] = { + _MSG_RECV(Rversion), + _MSG_RECV(Rauth), + _MSG_RECV(Rattach), + _MSG_RECV(Rerror), + _MSG_RECV(Rflush), + _MSG_RECV(Rwalk), + _MSG_RECV(Ropen), + _MSG_RECV(Rcreate), + _MSG_RECV(Rread), + _MSG_RECV(Rwrite), + _MSG_RECV(Rclunk), + _MSG_RECV(Rremove), + _MSG_RECV(Rstat), + _MSG_RECV(Rwstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; + +const struct _lib9p_send_tentry _lib9p_table_Tmsg_send[LIB9P_VER_NUM][0x80] = { + [LIB9P_VER_unknown] = { + _MSG_SEND(Tversion), + }, +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = { + _MSG_SEND(Tversion), + _MSG_SEND(Tauth), + _MSG_SEND(Tattach), + _MSG_SEND(Tflush), + _MSG_SEND(Twalk), + _MSG_SEND(Topen), + _MSG_SEND(Tcreate), + _MSG_SEND(Tread), + _MSG_SEND(Twrite), + _MSG_SEND(Tclunk), + _MSG_SEND(Tremove), + _MSG_SEND(Tstat), + _MSG_SEND(Twstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = { + _MSG_SEND(Tstatfs), + _MSG_SEND(Tlopen), + _MSG_SEND(Tlcreate), + _MSG_SEND(Tsymlink), + _MSG_SEND(Tmknod), + _MSG_SEND(Trename), + _MSG_SEND(Treadlink), + _MSG_SEND(Tgetattr), + _MSG_SEND(Tsetattr), + _MSG_SEND(Txattrwalk), + _MSG_SEND(Txattrcreate), + _MSG_SEND(Treaddir), + _MSG_SEND(Tfsync), + _MSG_SEND(Tlock), + _MSG_SEND(Tgetlock), + _MSG_SEND(Tlink), + _MSG_SEND(Tmkdir), + _MSG_SEND(Trenameat), + _MSG_SEND(Tunlinkat), + _MSG_SEND(Tversion), + _MSG_SEND(Tauth), + _MSG_SEND(Tattach), + _MSG_SEND(Tflush), + _MSG_SEND(Twalk), + _MSG_SEND(Tread), + _MSG_SEND(Twrite), + _MSG_SEND(Tclunk), + _MSG_SEND(Tremove), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = { + _MSG_SEND(Tversion), + _MSG_SEND(Tauth), + _MSG_SEND(Tattach), + _MSG_SEND(Tflush), + _MSG_SEND(Twalk), + _MSG_SEND(Topen), + _MSG_SEND(Tcreate), + _MSG_SEND(Tread), + _MSG_SEND(Twrite), + _MSG_SEND(Tclunk), + _MSG_SEND(Tremove), + _MSG_SEND(Tstat), + _MSG_SEND(Twstat), + _MSG_SEND(Tsession), + _MSG_SEND(Tsread), + _MSG_SEND(Tswrite), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = { + _MSG_SEND(Topenfd), + _MSG_SEND(Tversion), + _MSG_SEND(Tauth), + _MSG_SEND(Tattach), + _MSG_SEND(Tflush), + _MSG_SEND(Twalk), + _MSG_SEND(Topen), + _MSG_SEND(Tcreate), + _MSG_SEND(Tread), + _MSG_SEND(Twrite), + _MSG_SEND(Tclunk), + _MSG_SEND(Tremove), + _MSG_SEND(Tstat), + _MSG_SEND(Twstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_u + [LIB9P_VER_9P2000_u] = { + _MSG_SEND(Tversion), + _MSG_SEND(Tauth), + _MSG_SEND(Tattach), + _MSG_SEND(Tflush), + _MSG_SEND(Twalk), + _MSG_SEND(Topen), + _MSG_SEND(Tcreate), + _MSG_SEND(Tread), + _MSG_SEND(Twrite), + _MSG_SEND(Tclunk), + _MSG_SEND(Tremove), + _MSG_SEND(Tstat), + _MSG_SEND(Twstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; + +const struct _lib9p_send_tentry _lib9p_table_Rmsg_send[LIB9P_VER_NUM][0x80] = { + [LIB9P_VER_unknown] = { + _MSG_SEND(Rversion), + _MSG_SEND(Rerror), + }, +#if CONFIG_9P_ENABLE_9P2000 + [LIB9P_VER_9P2000] = { + _MSG_SEND(Rversion), + _MSG_SEND(Rauth), + _MSG_SEND(Rattach), + _MSG_SEND(Rerror), + _MSG_SEND(Rflush), + _MSG_SEND(Rwalk), + _MSG_SEND(Ropen), + _MSG_SEND(Rcreate), + _MSG_SEND(Rread), + _MSG_SEND(Rwrite), + _MSG_SEND(Rclunk), + _MSG_SEND(Rremove), + _MSG_SEND(Rstat), + _MSG_SEND(Rwstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + [LIB9P_VER_9P2000_L] = { + _MSG_SEND(Rlerror), + _MSG_SEND(Rstatfs), + _MSG_SEND(Rlopen), + _MSG_SEND(Rlcreate), + _MSG_SEND(Rsymlink), + _MSG_SEND(Rmknod), + _MSG_SEND(Rrename), + _MSG_SEND(Rreadlink), + _MSG_SEND(Rgetattr), + _MSG_SEND(Rsetattr), + _MSG_SEND(Rxattrwalk), + _MSG_SEND(Rxattrcreate), + _MSG_SEND(Rreaddir), + _MSG_SEND(Rfsync), + _MSG_SEND(Rlock), + _MSG_SEND(Rgetlock), + _MSG_SEND(Rlink), + _MSG_SEND(Rmkdir), + _MSG_SEND(Rrenameat), + _MSG_SEND(Runlinkat), + _MSG_SEND(Rversion), + _MSG_SEND(Rauth), + _MSG_SEND(Rattach), + _MSG_SEND(Rerror), + _MSG_SEND(Rflush), + _MSG_SEND(Rwalk), + _MSG_SEND(Rread), + _MSG_SEND(Rwrite), + _MSG_SEND(Rclunk), + _MSG_SEND(Rremove), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e + [LIB9P_VER_9P2000_e] = { + _MSG_SEND(Rversion), + _MSG_SEND(Rauth), + _MSG_SEND(Rattach), + _MSG_SEND(Rerror), + _MSG_SEND(Rflush), + _MSG_SEND(Rwalk), + _MSG_SEND(Ropen), + _MSG_SEND(Rcreate), + _MSG_SEND(Rread), + _MSG_SEND(Rwrite), + _MSG_SEND(Rclunk), + _MSG_SEND(Rremove), + _MSG_SEND(Rstat), + _MSG_SEND(Rwstat), + _MSG_SEND(Rsession), + _MSG_SEND(Rsread), + _MSG_SEND(Rswrite), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + [LIB9P_VER_9P2000_p9p] = { + _MSG_SEND(Ropenfd), + _MSG_SEND(Rversion), + _MSG_SEND(Rauth), + _MSG_SEND(Rattach), + _MSG_SEND(Rerror), + _MSG_SEND(Rflush), + _MSG_SEND(Rwalk), + _MSG_SEND(Ropen), + _MSG_SEND(Rcreate), + _MSG_SEND(Rread), + _MSG_SEND(Rwrite), + _MSG_SEND(Rclunk), + _MSG_SEND(Rremove), + _MSG_SEND(Rstat), + _MSG_SEND(Rwstat), + }, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_u + [LIB9P_VER_9P2000_u] = { + _MSG_SEND(Rversion), + _MSG_SEND(Rauth), + _MSG_SEND(Rattach), + _MSG_SEND(Rerror), + _MSG_SEND(Rflush), + _MSG_SEND(Rwalk), + _MSG_SEND(Ropen), + _MSG_SEND(Rcreate), + _MSG_SEND(Rread), + _MSG_SEND(Rwrite), + _MSG_SEND(Rclunk), + _MSG_SEND(Rremove), + _MSG_SEND(Rstat), + _MSG_SEND(Rwstat), + }, #endif /* CONFIG_9P_ENABLE_9P2000_u */ }; -FLATTEN bool _lib9p_validate_stat(struct _validate_ctx *ctx) { - return validate_stat(ctx); +LM_FLATTEN ssize_t _lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size) { + return validate_stat(ctx, net_size, net_bytes, ret_net_size); } -FLATTEN void _lib9p_unmarshal_stat(struct _unmarshal_ctx *ctx, struct lib9p_stat *out) { - unmarshal_stat(ctx, out); +LM_FLATTEN void _lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out) { + unmarshal_stat(ctx, net_bytes, out); } -FLATTEN bool _lib9p_marshal_stat(struct _marshal_ctx *ctx, struct lib9p_stat *val) { - return marshal_stat(ctx, val); +LM_FLATTEN bool _lib9p_stat_marshal(struct lib9p_ctx *ctx, struct lib9p_stat *val, struct _marshal_ret *ret) { + return marshal_stat(ctx, val, ret); } diff --git a/lib9p/CMakeLists.txt b/lib9p/CMakeLists.txt index 488cff9..d433a12 100644 --- a/lib9p/CMakeLists.txt +++ b/lib9p/CMakeLists.txt @@ -1,17 +1,29 @@ -# lib9p/CMakeLists.txt - TODO +# lib9p/CMakeLists.txt - A 9P protocol and server library # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(lib9p INTERFACE) -target_include_directories(lib9p SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(lib9p PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_sources(lib9p INTERFACE 9p.generated.c 9p.c + tables.c srv.c ) target_link_libraries(lib9p INTERFACE libcr_ipc + libfmt + libhw_generic libmisc - libhw ) + +if (ENABLE_TESTS) + add_subdirectory(tests/test_server) + add_test( + NAME "lib9p/runtest" + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/runtest" + ) + add_lib_test(lib9p test_compile) + target_include_directories(test_compile PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_compile_config) +endif() diff --git a/lib9p/idl.gen b/lib9p/idl.gen deleted file mode 100755 index 262e5f0..0000000 --- a/lib9p/idl.gen +++ /dev/null @@ -1,1196 +0,0 @@ -#!/usr/bin/env python -# lib9p/idl.gen - Generate C marshalers/unmarshalers for .9p files -# defining 9P protocol variants. -# -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import enum -import os.path -import re -from abc import ABC, abstractmethod -from typing import Callable, Final, Literal, TypeAlias, TypeVar, cast - -# This strives to be "general-purpose" in that it just acts on the -# *.9p inputs; but (unfortunately?) there are a few special-cases in -# this script, marked with "SPECIAL". - -# Types ######################################################################## - - -class Primitive(enum.Enum): - u8 = 1 - u16 = 2 - u32 = 4 - u64 = 8 - - @property - def in_versions(self) -> set[str]: - return set() - - @property - def name(self) -> str: - return str(self.value) - - @property - def static_size(self) -> int: - return self.value - - -class Number: - name: str - in_versions: set[str] - - prim: Primitive - - def __init__(self) -> None: - self.in_versions = set() - - @property - def static_size(self) -> int: - return self.prim.static_size - - -class BitfieldVal: - name: str - in_versions: set[str] - - val: str - - def __init__(self) -> None: - self.in_versions = set() - - -class Bitfield: - name: str - in_versions: set[str] - - prim: Primitive - - bits: list[str] # bitnames - names: dict[str, BitfieldVal] # bits *and* aliases - - def __init__(self) -> None: - self.in_versions = set() - self.names = {} - - @property - def static_size(self) -> int: - return self.prim.static_size - - def bit_is_valid(self, bit: str | int, ver: str | None = None) -> bool: - """Return whether the given bit is valid in the given protocol - version. - - """ - bitname = self.bits[bit] if isinstance(bit, int) else bit - assert bitname in self.bits - if not bitname: - return False - if bitname.startswith("_"): - return False - if ver and (ver not in self.names[bitname].in_versions): - return False - return True - - -class ExprLit: - val: int - - def __init__(self, val: int) -> None: - self.val = val - - -class ExprSym: - name: str - - def __init__(self, name: str) -> None: - self.name = name - - -class ExprOp: - op: Literal["-", "+"] - - def __init__(self, op: Literal["-", "+"]) -> None: - self.op = op - - -class Expr: - tokens: list[ExprLit | ExprSym | ExprOp] - - def __init__(self) -> None: - self.tokens = [] - - def __bool__(self) -> bool: - return len(self.tokens) > 0 - - -class StructMember: - # from left-to-right when parsing - cnt: str | None = None - name: str - typ: "Type" - max: Expr - val: Expr - - in_versions: set[str] - - @property - def static_size(self) -> int | None: - if self.cnt: - return None - return self.typ.static_size - - -class Struct: - name: str - in_versions: set[str] - - members: list[StructMember] - - def __init__(self) -> None: - self.in_versions = set() - - @property - def static_size(self) -> int | None: - size = 0 - for member in self.members: - msize = member.static_size - if msize is None: - return None - size += msize - return size - - -class Message(Struct): - @property - def msgid(self) -> int: - assert len(self.members) >= 3 - assert self.members[1].name == "typ" - assert self.members[1].static_size == 1 - assert self.members[1].val - assert len(self.members[1].val.tokens) == 1 - assert isinstance(self.members[1].val.tokens[0], ExprLit) - return self.members[1].val.tokens[0].val - - -Type: TypeAlias = Primitive | Number | Bitfield | Struct | Message -# type Type = Primitive | Number | Bitfield | Struct | Message # Change to this once we have Python 3.13 -T = TypeVar("T", Number, Bitfield, Struct, Message) - -# Parse *.9p ################################################################### - -re_priname = "(?:1|2|4|8)" # primitive names -re_symname = "(?:[a-zA-Z_][a-zA-Z_0-9]*)" # "symbol" names; most *.9p-defined names -re_impname = r"(?:\*|" + re_symname + ")" # names we can import -re_msgname = r"(?:[TR][a-zA-Z_0-9]*)" # names a message can be - -re_memtype = f"(?:{re_symname}|{re_priname})" # typenames that a struct member can be - -re_expr = f"(?:(?:-|\\+|[0-9]+|&?{re_symname})+)" - -re_bitspec_bit = f"(?P<bit>[0-9]+)\\s*=\\s*(?P<name>{re_symname})" -re_bitspec_alias = f"(?P<name>{re_symname})\\s*=\\s*(?P<val>\\S+)" - -re_memberspec = f"(?:(?P<cnt>{re_symname})\\*\\()?(?P<name>{re_symname})\\[(?P<typ>{re_memtype})(?:,max=(?P<max>{re_expr})|,val=(?P<val>{re_expr}))*\\]\\)?" - - -def parse_bitspec(ver: str, bf: Bitfield, spec: str) -> None: - spec = spec.strip() - - bit: int | None - val: BitfieldVal - if m := re.fullmatch(re_bitspec_bit, spec): - bit = int(m.group("bit")) - name = m.group("name") - - val = BitfieldVal() - val.name = name - val.val = f"1<<{bit}" - val.in_versions.add(ver) - - if bit < 0 or bit >= len(bf.bits): - raise ValueError(f"{bf.name}: bit {bit} is out-of-bounds") - if bf.bits[bit]: - raise ValueError(f"{bf.name}: bit {bit} already assigned") - bf.bits[bit] = val.name - elif m := re.fullmatch(re_bitspec_alias, spec): - name = m.group("name") - valstr = m.group("val") - - val = BitfieldVal() - val.name = name - val.val = valstr - val.in_versions.add(ver) - else: - raise SyntaxError(f"invalid bitfield spec {repr(spec)}") - - if val.name in bf.names: - raise ValueError(f"{bf.name}: name {val.name} already assigned") - bf.names[val.name] = val - - -def parse_expr(expr: str) -> Expr: - assert re.fullmatch(re_expr, expr) - ret = Expr() - for tok in re.split("([-+])", expr): - if tok == "-" or tok == "+": - # I, for the life of me, do not understand why I need this - # cast() to keep mypy happy. - ret.tokens += [ExprOp(cast(Literal["-", "+"], tok))] - elif re.fullmatch("[0-9]+", tok): - ret.tokens += [ExprLit(int(tok))] - else: - ret.tokens += [ExprSym(tok)] - return ret - - -def parse_members(ver: str, env: dict[str, Type], struct: Struct, specs: str) -> None: - for spec in specs.split(): - m = re.fullmatch(re_memberspec, spec) - if not m: - raise SyntaxError(f"invalid member spec {repr(spec)}") - - member = StructMember() - member.in_versions = {ver} - - member.name = m.group("name") - if any(x.name == member.name for x in struct.members): - raise ValueError(f"duplicate member name {repr(member.name)}") - - if m.group("typ") not in env: - raise NameError(f"Unknown type {repr(m.group(2))}") - member.typ = env[m.group("typ")] - - if cnt := m.group("cnt"): - if len(struct.members) == 0 or struct.members[-1].name != cnt: - raise ValueError(f"list count must be previous item: {repr(cnt)}") - if not isinstance(struct.members[-1].typ, Primitive): - raise ValueError(f"list count must be an integer type: {repr(cnt)}") - member.cnt = cnt - - if maxstr := m.group("max"): - if (not isinstance(member.typ, Primitive)) or member.cnt: - raise ValueError("',max=' may only be specified on a non-repeated atom") - member.max = parse_expr(maxstr) - else: - member.max = Expr() - - if valstr := m.group("val"): - if (not isinstance(member.typ, Primitive)) or member.cnt: - raise ValueError("',val=' may only be specified on a non-repeated atom") - member.val = parse_expr(valstr) - else: - member.val = Expr() - - struct.members += [member] - - -def re_string(grpname: str) -> str: - return f'"(?P<{grpname}>[^"]*)"' - - -re_line_version = f"version\\s+{re_string('version')}" -re_line_import = f"from\\s+(?P<file>\\S+)\\s+import\\s+(?P<syms>{re_impname}(?:\\s*,\\s*{re_impname})*)" -re_line_num = f"num\\s+(?P<name>{re_symname})\\s*=\\s*(?P<prim>{re_priname})" -re_line_bitfield = f"bitfield\\s+(?P<name>{re_symname})\\s*=\\s*(?P<prim>{re_priname})" -re_line_bitfield_ = ( - f"bitfield\\s+(?P<name>{re_symname})\\s*\\+=\\s*{re_string('member')}" -) -re_line_struct = ( - f"struct\\s+(?P<name>{re_symname})\\s*(?P<op>\\+?=)\\s*{re_string('members')}" -) -re_line_msg = ( - f"msg\\s+(?P<name>{re_msgname})\\s*(?P<op>\\+?=)\\s*{re_string('members')}" -) -re_line_cont = f"\\s+{re_string('specs')}" # could be bitfield/struct/msg - - -def parse_file( - filename: str, get_include: Callable[[str], tuple[str, list[Type]]] -) -> tuple[str, list[Type]]: - version: str | None = None - env: dict[str, Type] = { - "1": Primitive.u8, - "2": Primitive.u16, - "4": Primitive.u32, - "8": Primitive.u64, - } - - def get_type(name: str, tc: type[T]) -> T: - nonlocal env - if name not in env: - raise NameError(f"Unknown type {repr(name)}") - ret = env[name] - if (not isinstance(ret, tc)) or (ret.__class__.__name__ != tc.__name__): - raise NameError(f"Type {repr(ret.name)} is not a {tc.__name__}") - return ret - - with open(filename, "r") as fh: - prev: Type | None = None - for line in fh: - line = line.split("#", 1)[0].rstrip() - if not line: - continue - if m := re.fullmatch(re_line_version, line): - if version: - raise SyntaxError("must have exactly 1 version line") - version = m.group("version") - continue - if not version: - raise SyntaxError("must have exactly 1 version line") - - if m := re.fullmatch(re_line_import, line): - other_version, other_typs = get_include(m.group("file")) - for symname in m.group("syms").split(sep=","): - symname = symname.strip() - for typ in other_typs: - if typ.name == symname or symname == "*": - match typ: - case Primitive(): - pass - case Number(): - typ.in_versions.add(version) - case Bitfield(): - typ.in_versions.add(version) - for val in typ.names.values(): - if other_version in val.in_versions: - val.in_versions.add(version) - case Struct(): # and Message() - typ.in_versions.add(version) - for member in typ.members: - if other_version in member.in_versions: - member.in_versions.add(version) - env[typ.name] = typ - elif m := re.fullmatch(re_line_num, line): - num = Number() - num.name = m.group("name") - num.in_versions.add(version) - - prim = env[m.group("prim")] - assert isinstance(prim, Primitive) - num.prim = prim - - env[num.name] = num - prev = num - elif m := re.fullmatch(re_line_bitfield, line): - bf = Bitfield() - bf.name = m.group("name") - bf.in_versions.add(version) - - prim = env[m.group("prim")] - assert isinstance(prim, Primitive) - bf.prim = prim - - bf.bits = (prim.static_size * 8) * [""] - - env[bf.name] = bf - prev = bf - elif m := re.fullmatch(re_line_bitfield_, line): - bf = get_type(m.group("name"), Bitfield) - parse_bitspec(version, bf, m.group("member")) - - prev = bf - elif m := re.fullmatch(re_line_struct, line): - match m.group("op"): - case "=": - struct = Struct() - struct.name = m.group("name") - struct.in_versions.add(version) - struct.members = [] - parse_members(version, env, struct, m.group("members")) - - env[struct.name] = struct - prev = struct - case "+=": - struct = get_type(m.group("name"), Struct) - parse_members(version, env, struct, m.group("members")) - - prev = struct - elif m := re.fullmatch(re_line_msg, line): - match m.group("op"): - case "=": - msg = Message() - msg.name = m.group("name") - msg.in_versions.add(version) - msg.members = [] - parse_members(version, env, msg, m.group("members")) - - env[msg.name] = msg - prev = msg - case "+=": - msg = get_type(m.group("name"), Message) - parse_members(version, env, msg, m.group("members")) - - prev = msg - elif m := re.fullmatch(re_line_cont, line): - match prev: - case Bitfield(): - parse_bitspec(version, prev, m.group("specs")) - case Struct(): # and Message() - parse_members(version, env, prev, m.group("specs")) - case _: - raise SyntaxError( - "continuation line must come after a bitfield, struct, or msg line" - ) - else: - raise SyntaxError(f"invalid line {repr(line)}") - if not version: - raise SyntaxError("must have exactly 1 version line") - - typs: list[Type] = [x for x in env.values() if not isinstance(x, Primitive)] - - for typ in [typ for typ in typs if isinstance(typ, Struct)]: - valid_syms = ["end", *["&" + m.name for m in typ.members]] - for member in typ.members: - for tok in [*member.max.tokens, *member.val.tokens]: - if isinstance(tok, ExprSym) and tok.name not in valid_syms: - raise ValueError( - f"{typ.name}.{member.name}: invalid sym: {tok.name}" - ) - - return version, typs - - -# Generate C ################################################################### - -idprefix = "lib9p_" - - -def c_ver_enum(ver: str) -> str: - return f"{idprefix.upper()}VER_{ver.replace('.', '_')}" - - -def c_ver_ifdef(versions: set[str]) -> str: - return " || ".join( - f"CONFIG_9P_ENABLE_{v.replace('.', '_')}" for v in sorted(versions) - ) - - -def c_ver_cond(versions: set[str]) -> str: - if len(versions) == 1: - return f"(ctx->ctx->version=={c_ver_enum(next(v for v in versions))})" - return "( " + (" || ".join(c_ver_cond({v}) for v in sorted(versions))) + " )" - - -def c_typename(typ: Type) -> str: - match typ: - case Primitive(): - return f"uint{typ.value*8}_t" - case Number(): - return f"{idprefix}{typ.name}_t" - case Bitfield(): - return f"{idprefix}{typ.name}_t" - case Message(): - return f"struct {idprefix}msg_{typ.name}" - case Struct(): - return f"struct {idprefix}{typ.name}" - case _: - raise ValueError(f"not a type: {typ.__class__.__name__}") - - -_ifdef_stack: list[str | None] = [] - - -def ifdef_push(n: int, _newval: str) -> str: - # Grow the stack as needed - global _ifdef_stack - while len(_ifdef_stack) < n: - _ifdef_stack += [None] - - # Set some variables - parentval: str | None = None - for x in _ifdef_stack[:-1]: - if x is not None: - parentval = x - oldval = _ifdef_stack[-1] - newval: str | None = _newval - if newval == parentval: - newval = None - - # Put newval on the stack. - _ifdef_stack[-1] = newval - - # Build output. - ret = "" - if newval != oldval: - if oldval is not None: - ret += f"#endif /* {oldval} */\n" - if newval is not None: - ret += f"#if {newval}\n" - return ret - - -def ifdef_pop(n: int) -> str: - global _ifdef_stack - ret = "" - while len(_ifdef_stack) > n: - if _ifdef_stack[-1] is not None: - ret += f"#endif /* {_ifdef_stack[-1]} */\n" - _ifdef_stack = _ifdef_stack[:-1] - return ret - - -def gen_h(versions: set[str], typs: list[Type]) -> str: - global _ifdef_stack - _ifdef_stack = [] - - ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ - -#ifndef _LIB9P_9P_H_ - #error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead -#endif - -#include <stdint.h> /* for uint{{n}}_t types */ -""" - - ret += f""" -/* config *********************************************************************/ - -#include "config.h" -""" - for ver in sorted(versions): - ret += "\n" - ret += f"#ifndef {c_ver_ifdef({ver})}\n" - ret += f"\t#error config.h must define {c_ver_ifdef({ver})}\n" - ret += "#endif\n" - - ret += f""" -/* versions *******************************************************************/ - -enum {idprefix}version {{ -""" - fullversions = ["unknown = 0", *sorted(versions)] - verwidth = max(len(v) for v in fullversions) - for ver in fullversions: - if ver in versions: - ret += ifdef_push(1, c_ver_ifdef({ver})) - ret += f"\t{c_ver_enum(ver)}," - ret += (" " * (verwidth - len(ver))) + ' /* "' + ver.split()[0] + '" */\n' - ret += ifdef_pop(0) - ret += f"\t{c_ver_enum('NUM')},\n" - ret += "};\n" - ret += "\n" - ret += f"const char *{idprefix}version_str(enum {idprefix}version);\n" - - ret += """ -/* non-message types **********************************************************/ -""" - for typ in [typ for typ in typs if not isinstance(typ, Message)]: - ret += "\n" - ret += ifdef_push(1, c_ver_ifdef(typ.in_versions)) - match typ: - case Number(): - ret += f"typedef {c_typename(typ.prim)} {c_typename(typ)};\n" - case Bitfield(): - ret += f"typedef {c_typename(typ.prim)} {c_typename(typ)};\n" - names = [ - *reversed( - [typ.bits[n] or f" {n}" for n in range(0, len(typ.bits))] - ), - "", - *[k for k in typ.names if k not in typ.bits], - ] - namewidth = max(len(name) for name in names) - - ret += "\n" - for name in names: - if name == "": - ret += "\n" - elif name.startswith(" "): - ret += ifdef_push(2, c_ver_ifdef(typ.in_versions)) - sp = " " * ( - len("# define ") - + len(idprefix) - + len(typ.name) - + 1 - + namewidth - + 2 - - len("/* unused") - ) - ret += f"/* unused{sp}(({c_typename(typ)})(1<<{name[1:]})) */\n" - else: - ret += ifdef_push(2, c_ver_ifdef(typ.names[name].in_versions)) - if name.startswith("_"): - c_name = f"_{idprefix.upper()}{typ.name.upper()}_{name[1:]}" - else: - c_name = f"{idprefix.upper()}{typ.name.upper()}_{name}" - sp1 = " " if _ifdef_stack[-1] else "" - sp2 = " " if _ifdef_stack[-1] else " " - sp3 = " " * (2 + namewidth - len(name)) - ret += f"#{sp1}define{sp2}{c_name}{sp3}(({c_typename(typ)})({typ.names[name].val}))\n" - ret += ifdef_pop(1) - case Struct(): - typewidth = max(len(c_typename(m.typ)) for m in typ.members) - - ret += c_typename(typ) + " {\n" - for member in typ.members: - if member.val: - continue - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - c_type = c_typename(member.typ) - if (typ.name in ["d", "s"]) and member.cnt: # SPECIAL - c_type = "char" - ret += f"\t{c_type.ljust(typewidth)} {'*' if member.cnt else ' '}{member.name};\n" - ret += ifdef_pop(1) - ret += "};\n" - ret += ifdef_pop(0) - - ret += """ -/* messages *******************************************************************/ - -""" - ret += f"enum {idprefix}msg_type {{ /* uint8_t */\n" - namewidth = max(len(msg.name) for msg in typs if isinstance(msg, Message)) - for msg in [msg for msg in typs if isinstance(msg, Message)]: - ret += ifdef_push(1, c_ver_ifdef(msg.in_versions)) - ret += f"\t{idprefix.upper()}TYP_{msg.name.ljust(namewidth)} = {msg.msgid},\n" - ret += ifdef_pop(0) - ret += "};\n" - - for msg in [msg for msg in typs if isinstance(msg, Message)]: - ret += "\n" - ret += ifdef_push(1, c_ver_ifdef(msg.in_versions)) - ret += c_typename(msg) + " {" - if not msg.members: - ret += "};\n" - continue - ret += "\n" - - typewidth = max(len(c_typename(m.typ)) for m in msg.members) - - for member in msg.members: - if member.val: - continue - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - ret += f"\t{c_typename(member.typ).ljust(typewidth)} {'*' if member.cnt else ' '}{member.name};\n" - ret += ifdef_pop(1) - ret += "};\n" - ret += ifdef_pop(0) - - return ret - - -def c_expr(expr: Expr) -> str: - ret: list[str] = [] - for tok in expr.tokens: - match tok: - case ExprOp(): - ret += [tok.op] - case ExprLit(): - ret += [str(tok.val)] - case ExprSym(name="end"): - ret += ["ctx->net_offset"] - case ExprSym(): - ret += [f"_{tok.name[1:]}_offset"] - return " ".join(ret) - - -def gen_c(versions: set[str], typs: list[Type]) -> str: - global _ifdef_stack - _ifdef_stack = [] - - ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ - -#include <stdbool.h> -#include <stddef.h> /* for size_t */ -#include <inttypes.h> /* for PRI* macros */ -#include <string.h> /* for memset() */ - -#include <libmisc/assert.h> - -#include <lib9p/9p.h> - -#include "internal.h" -""" - - def used(arg: str) -> str: - return arg - - def unused(arg: str) -> str: - return f"UNUSED({arg})" - - # strings ################################################################## - ret += f""" -/* strings ********************************************************************/ - -static const char *version_strs[{c_ver_enum('NUM')}] = {{ -""" - for ver in ["unknown", *sorted(versions)]: - if ver in versions: - ret += ifdef_push(1, c_ver_ifdef({ver})) - ret += f'\t[{c_ver_enum(ver)}] = "{ver}",\n' - ret += ifdef_pop(0) - ret += "};\n" - ret += f""" -const char *{idprefix}version_str(enum {idprefix}version ver) {{ - assert(0 <= ver && ver < {c_ver_enum('NUM')}); - return version_strs[ver]; -}} -""" - - # validate_* ############################################################### - ret += """ -/* validate_* *****************************************************************/ - -ALWAYS_INLINE static bool _validate_size_net(struct _validate_ctx *ctx, uint32_t n) { - if (__builtin_add_overflow(ctx->net_offset, n, &ctx->net_offset)) - /* If needed-net-size overflowed uint32_t, then - * there's no way that actual-net-size will live up to - * that. */ - return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content"); - if (ctx->net_offset > ctx->net_size) - return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content"); - return false; -} - -ALWAYS_INLINE static bool _validate_size_host(struct _validate_ctx *ctx, size_t n) { - if (__builtin_add_overflow(ctx->host_extra, n, &ctx->host_extra)) - /* If needed-host-size overflowed size_t, then there's - * no way that actual-net-size will live up to - * that. */ - return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content"); - return false; -} - -ALWAYS_INLINE static bool _validate_list(struct _validate_ctx *ctx, - size_t cnt, - _validate_fn_t item_fn, size_t item_host_size) { - for (size_t i = 0; i < cnt; i++) - if (_validate_size_host(ctx, item_host_size) || item_fn(ctx)) - return true; - return false; -} - -#define validate_1(ctx) _validate_size_net(ctx, 1) -#define validate_2(ctx) _validate_size_net(ctx, 2) -#define validate_4(ctx) _validate_size_net(ctx, 4) -#define validate_8(ctx) _validate_size_net(ctx, 8) -""" - for typ in typs: - inline = "FLATTEN" if isinstance(typ, Message) else "ALWAYS_INLINE" - argfn = unused if (isinstance(typ, Struct) and not typ.members) else used - ret += "\n" - ret += ifdef_push(1, c_ver_ifdef(typ.in_versions)) - - if isinstance(typ, Bitfield): - ret += f"static const {c_typename(typ)} {typ.name}_masks[{c_ver_enum('NUM')}] = {{\n" - verwidth = max(len(ver) for ver in versions) - for ver in sorted(versions): - ret += ifdef_push(2, c_ver_ifdef({ver})) - ret += ( - f"\t[{c_ver_enum(ver)}]{' '*(verwidth-len(ver))} = 0b" - + "".join( - "1" if typ.bit_is_valid(bitname, ver) else "0" - for bitname in reversed(typ.bits) - ) - + ",\n" - ) - ret += ifdef_pop(1) - ret += "};\n" - - ret += f"{inline} static bool validate_{typ.name}(struct _validate_ctx *{argfn('ctx')}) {{\n" - - if typ.name == "d": # SPECIAL - # Optimize... maybe the compiler could figure out to do - # this, but let's make it obvious. - ret += "\tuint32_t base_offset = ctx->net_offset;\n" - ret += "\tif (validate_4(ctx))\n" - ret += "\t\treturn true;\n" - ret += "\tuint32_t len = decode_u32le(&ctx->net_bytes[base_offset]);\n" - ret += "\treturn _validate_size_net(ctx, len) || _validate_size_host(ctx, len);\n" - ret += "}\n" - continue - if typ.name == "s": # SPECIAL - # Add an extra nul-byte on the host, and validate UTF-8 - # (also, similar optimization to "d"). - ret += "\tuint32_t base_offset = ctx->net_offset;\n" - ret += "\tif (validate_2(ctx))\n" - ret += "\t\treturn true;\n" - ret += "\tuint16_t len = decode_u16le(&ctx->net_bytes[base_offset]);\n" - ret += "\tif (_validate_size_net(ctx, len) || _validate_size_host(ctx, ((size_t)len)+1))\n" - ret += "\t\treturn true;\n" - ret += "\tif (!is_valid_utf8_without_nul(&ctx->net_bytes[base_offset+2], len))\n" - ret += '\t\treturn lib9p_error(ctx->ctx, LINUX_EBADMSG, "message contains invalid UTF-8");\n' - ret += "\treturn false;\n" - ret += "}\n" - continue - - match typ: - case Number(): - ret += f"\treturn validate_{typ.prim.name}(ctx);\n" - case Bitfield(): - ret += f"\t if (validate_{typ.static_size}(ctx))\n" - ret += "\t\treturn true;\n" - ret += ( - f"\t{c_typename(typ)} mask = {typ.name}_masks[ctx->ctx->version];\n" - ) - ret += f"\t{c_typename(typ)} val = decode_u{typ.static_size*8}le(&ctx->net_bytes[ctx->net_offset-{typ.static_size}]);\n" - ret += f"\tif (val & ~mask)\n" - ret += f'\t\treturn lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "unknown bits in {typ.name} bitfield: %#0{typ.static_size}"PRIx{typ.static_size*8}, val & ~mask);\n' - ret += "\treturn false;\n" - case Struct(): # and Message() - if len(typ.members) == 0: - ret += "\treturn false;\n" - ret += "}\n" - continue - - # Pass 1 - declare value variables - for member in typ.members: - if member.max or member.val: - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - ret += f"\t{c_typename(member.typ)} {member.name};\n" - ret += ifdef_pop(1) - - # Pass 2 - declare offset variables - mark_offset: set[str] = set() - for member in typ.members: - for tok in [*member.max.tokens, *member.val.tokens]: - if isinstance(tok, ExprSym) and tok.name.startswith("&"): - if tok.name[1:] not in mark_offset: - ret += f"\tuint32_t _{tok.name[1:]}_offset;\n" - mark_offset.add(tok.name[1:]) - - # Pass 3 - main pass - ret += "\treturn false\n" - prev_size: int | None = None - for member in typ.members: - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - ret += f"\t || " - if member.in_versions != typ.in_versions: - ret += "( " + c_ver_cond(member.in_versions) + " && " - if member.cnt is not None: - assert prev_size - ret += f"_validate_list(ctx, decode_u{prev_size*8}le(&ctx->net_bytes[ctx->net_offset-{prev_size}]), validate_{member.typ.name}, sizeof({c_typename(member.typ)}))" - else: - if member.max or member.val: - ret += "(" - if member.name in mark_offset: - ret += f"({{ _{member.name}_offset = ctx->net_offset; " - ret += f"validate_{member.typ.name}(ctx)" - if member.name in mark_offset: - ret += "; })" - if member.max or member.val: - bytes = member.static_size - assert bytes - bits = bytes * 8 - ret += f" || ({{ {member.name} = decode_u{bits}le(&ctx->net_bytes[ctx->net_offset-{bytes}]); false; }}))" - if member.in_versions != typ.in_versions: - ret += " )" - ret += "\n" - prev_size = member.static_size - - # Pass 4 - validate ,max= and ,val= constraints - for member in typ.members: - if member.max: - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - ret += f"\t || ({{ uint32_t max = {c_expr(member.max)}; (((uint32_t){member.name}) > max) &&\n" - ret += f'\t lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "{member.name} value is too large (%"PRIu32" > %"PRIu32")", {member.name}, max); }})\n' - if member.val: - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - ret += f"\t || ({{ uint32_t exp = {c_expr(member.val)}; (((uint32_t){member.name}) != exp) &&\n" - ret += f'\t lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "{member.name} value is wrong (actual:%"PRIu32" != correct:%"PRIu32")", (uint32_t){member.name}, exp); }})\n' - - ret += ifdef_pop(1) - ret += "\t ;\n" - ret += "}\n" - ret += ifdef_pop(0) - - # unmarshal_* ############################################################## - ret += """ -/* unmarshal_* ****************************************************************/ - -ALWAYS_INLINE static void unmarshal_1(struct _unmarshal_ctx *ctx, uint8_t *out) { - *out = decode_u8le(&ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 1; -} - -ALWAYS_INLINE static void unmarshal_2(struct _unmarshal_ctx *ctx, uint16_t *out) { - *out = decode_u16le(&ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 2; -} - -ALWAYS_INLINE static void unmarshal_4(struct _unmarshal_ctx *ctx, uint32_t *out) { - *out = decode_u32le(&ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 4; -} - -ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *out) { - *out = decode_u64le(&ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 8; -} -""" - for typ in typs: - inline = "FLATTEN" if isinstance(typ, Message) else "ALWAYS_INLINE" - argfn = unused if (isinstance(typ, Struct) and not typ.members) else used - ret += "\n" - ret += ifdef_push(1, c_ver_ifdef(typ.in_versions)) - ret += f"{inline} static void unmarshal_{typ.name}(struct _unmarshal_ctx *{argfn('ctx')}, {c_typename(typ)} *out) {{\n" - match typ: - case Number(): - ret += f"\tunmarshal_{typ.prim.name}(ctx, ({c_typename(typ.prim)} *)out);\n" - case Bitfield(): - ret += f"\tunmarshal_{typ.prim.name}(ctx, ({c_typename(typ.prim)} *)out);\n" - case Struct(): - ret += "\tmemset(out, 0, sizeof(*out));\n" - - for member in typ.members: - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - if member.val: - ret += f"\tctx->net_offset += {member.static_size};\n" - continue - ret += "\t" - - prefix = "\t" - if member.in_versions != typ.in_versions: - ret += "if ( " + c_ver_cond(member.in_versions) + " ) " - prefix = "\t\t" - if member.cnt: - if member.in_versions != typ.in_versions: - ret += "{\n" - ret += prefix - ret += f"out->{member.name} = ctx->extra;\n" - ret += f"{prefix}ctx->extra += sizeof(out->{member.name}[0]) * out->{member.cnt};\n" - ret += f"{prefix}for (typeof(out->{member.cnt}) i = 0; i < out->{member.cnt}; i++)\n" - if typ.name in ["d", "s"]: # SPECIAL - ret += f"{prefix}\tunmarshal_{member.typ.name}(ctx, (uint8_t *)&out->{member.name}[i]);\n" - else: - ret += f"{prefix}\tunmarshal_{member.typ.name}(ctx, &out->{member.name}[i]);\n" - if member.in_versions != typ.in_versions: - ret += "\t}\n" - else: - ret += ( - f"unmarshal_{member.typ.name}(ctx, &out->{member.name});\n" - ) - if typ.name == "s": # SPECIAL - ret += "\tctx->extra++;\n" - ret += "\tout->utf8[out->len] = '\\0';\n" - ret += ifdef_pop(1) - ret += "}\n" - ret += ifdef_pop(0) - - # marshal_* ################################################################ - ret += """ -/* marshal_* ******************************************************************/ - -ALWAYS_INLINE static bool _marshal_too_large(struct _marshal_ctx *ctx) { - lib9p_errorf(ctx->ctx, LINUX_ERANGE, "%s too large to marshal into %s limit (limit=%"PRIu32")", - (ctx->net_bytes[4] % 2 == 0) ? "T-message" : "R-message", - ctx->ctx->version ? "negotiated" : ((ctx->net_bytes[4] % 2 == 0) ? "client" : "server"), - ctx->ctx->max_msg_size); - return true; -} - -ALWAYS_INLINE static bool marshal_1(struct _marshal_ctx *ctx, uint8_t *val) { - if (ctx->net_offset + 1 > ctx->ctx->max_msg_size) - return _marshal_too_large(ctx); - ctx->net_bytes[ctx->net_offset] = *val; - ctx->net_offset += 1; - return false; -} - -ALWAYS_INLINE static bool marshal_2(struct _marshal_ctx *ctx, uint16_t *val) { - if (ctx->net_offset + 2 > ctx->ctx->max_msg_size) - return _marshal_too_large(ctx); - encode_u16le(*val, &ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 2; - return false; -} - -ALWAYS_INLINE static bool marshal_4(struct _marshal_ctx *ctx, uint32_t *val) { - if (ctx->net_offset + 4 > ctx->ctx->max_msg_size) - return true; - encode_u32le(*val, &ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 4; - return false; -} - -ALWAYS_INLINE static bool marshal_8(struct _marshal_ctx *ctx, uint64_t *val) { - if (ctx->net_offset + 8 > ctx->ctx->max_msg_size) - return true; - encode_u64le(*val, &ctx->net_bytes[ctx->net_offset]); - ctx->net_offset += 8; - return false; -} -""" - for typ in typs: - inline = "FLATTEN" if isinstance(typ, Message) else "ALWAYS_INLINE" - argfn = unused if (isinstance(typ, Struct) and not typ.members) else used - ret += "\n" - ret += ifdef_push(1, c_ver_ifdef(typ.in_versions)) - ret += f"{inline} static bool marshal_{typ.name}(struct _marshal_ctx *{argfn('ctx')}, {c_typename(typ)} *{argfn('val')}) {{\n" - match typ: - case Number(): - ret += f"\treturn marshal_{typ.prim.name}(ctx, ({c_typename(typ.prim)} *)val);\n" - case Bitfield(): - ret += f"\t{c_typename(typ)} masked_val = *val & {typ.name}_masks[ctx->ctx->version];\n" - ret += f"\treturn marshal_{typ.prim.name}(ctx, ({c_typename(typ.prim)} *)&masked_val);\n" - case Struct(): - if len(typ.members) == 0: - ret += "\treturn false;\n" - ret += "}\n" - continue - - # Pass 1 - declare offset variables - mark_offset = set() - for member in typ.members: - if member.val: - if member.name not in mark_offset: - ret += f"\tuint32_t _{member.name}_offset;\n" - mark_offset.add(member.name) - for tok in member.val.tokens: - if isinstance(tok, ExprSym) and tok.name.startswith("&"): - if tok.name[1:] not in mark_offset: - ret += f"\tuint32_t _{tok.name[1:]}_offset;\n" - mark_offset.add(tok.name[1:]) - - # Pass 2 - main pass - ret += "\treturn false\n" - for member in typ.members: - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - ret += "\t || " - if member.in_versions != typ.in_versions: - ret += "( " + c_ver_cond(member.in_versions) + " && " - if member.name in mark_offset: - ret += f"({{ _{member.name}_offset = ctx->net_offset; " - if member.cnt: - ret += "({ bool err = false;\n" - ret += f"\t for (typeof(val->{member.cnt}) i = 0; i < val->{member.cnt} && !err; i++)\n" - ret += "\t \terr = " - if typ.name in ["d", "s"]: # SPECIAL - # Special-case is that we cast from `char` to `uint8_t`. - ret += f"marshal_{member.typ.name}(ctx, (uint8_t *)&val->{member.name}[i]);\n" - else: - ret += f"marshal_{member.typ.name}(ctx, &val->{member.name}[i]);\n" - ret += f"\t err; }})" - elif member.val: - # Just increment net_offset, don't actually marsha anything (yet). - assert member.static_size - ret += ( - f"({{ ctx->net_offset += {member.static_size}; false; }})" - ) - else: - ret += f"marshal_{member.typ.name}(ctx, &val->{member.name})" - if member.name in mark_offset: - ret += "; })" - if member.in_versions != typ.in_versions: - ret += " )" - ret += "\n" - - # Pass 3 - marshal ,val= members - for member in typ.members: - if member.val: - assert member.static_size - ret += ifdef_push(2, c_ver_ifdef(member.in_versions)) - ret += f"\t || ({{ encode_u{member.static_size*8}le({c_expr(member.val)}, &ctx->net_bytes[_{member.name}_offset]); false; }})\n" - - ret += ifdef_pop(1) - ret += "\t ;\n" - ret += "}\n" - ret += ifdef_pop(0) - - # tables / exports ######################################################### - ret += f""" -/* tables / exports ***********************************************************/ - -#define _MSG(typ) [{idprefix.upper()}TYP_##typ] = {{ \\ - .name = #typ, \\ - .basesize = sizeof(struct {idprefix}msg_##typ), \\ - .validate = validate_##typ, \\ - .unmarshal = (_unmarshal_fn_t)unmarshal_##typ, \\ - .marshal = (_marshal_fn_t)marshal_##typ, \\ - }} -#define _NONMSG(num) [num] = {{ \\ - .name = #num, \\ - }} - -struct _table_version _{idprefix}versions[{c_ver_enum('NUM')}] = {{ -""" - id2typ: dict[int, Message] = {} - for msg in [msg for msg in typs if isinstance(msg, Message)]: - id2typ[msg.msgid] = msg - - for ver in ["unknown", *sorted(versions)]: - if ver != "unknown": - ret += ifdef_push(1, c_ver_ifdef({ver})) - ret += f"\t[{c_ver_enum(ver)}] = {{ .msgs = {{\n" - - for n in range(0, 0x100): - xmsg: Message | None = id2typ.get(n, None) - if xmsg: - if ver == "unknown": # SPECIAL - if xmsg.name not in ["Tversion", "Rversion", "Rerror"]: - xmsg = None - else: - if ver not in xmsg.in_versions: - xmsg = None - if xmsg: - ret += f"\t\t_MSG({xmsg.name}),\n" - else: - ret += "\t\t_NONMSG(0x{:02X}),\n".format(n) - ret += "\t}},\n" - ret += ifdef_pop(0) - ret += "};\n" - - ret += f""" -FLATTEN bool _{idprefix}validate_stat(struct _validate_ctx *ctx) {{ - return validate_stat(ctx); -}} -FLATTEN void _{idprefix}unmarshal_stat(struct _unmarshal_ctx *ctx, struct lib9p_stat *out) {{ - unmarshal_stat(ctx, out); -}} -FLATTEN bool _{idprefix}marshal_stat(struct _marshal_ctx *ctx, struct lib9p_stat *val) {{ - return marshal_stat(ctx, val); -}} -""" - - ############################################################################ - return ret - - -################################################################################ - - -class Parser: - cache: dict[str, tuple[str, list[Type]]] = {} - - def parse_file(self, filename: str) -> tuple[str, list[Type]]: - filename = os.path.normpath(filename) - if filename not in self.cache: - - def get_include(other_filename: str) -> tuple[str, list[Type]]: - return self.parse_file(os.path.join(filename, "..", other_filename)) - - self.cache[filename] = parse_file(filename, get_include) - return self.cache[filename] - - def all(self) -> tuple[set[str], list[Type]]: - ret_versions: set[str] = set() - ret_typs: dict[str, Type] = {} - for version, typs in self.cache.values(): - if version in ret_versions: - raise ValueError(f"duplicate protocol version {repr(version)}") - ret_versions.add(version) - for typ in typs: - if typ.name in ret_typs: - if typ != ret_typs[typ.name]: - raise ValueError(f"duplicate type name {repr(typ.name)}") - else: - ret_typs[typ.name] = typ - return ret_versions, list(ret_typs.values()) - - -if __name__ == "__main__": - import sys - - if len(sys.argv) < 2: - raise ValueError("requires at least 1 .9p filename") - parser = Parser() - for txtname in sys.argv[1:]: - parser.parse_file(txtname) - versions, typs = parser.all() - outdir = os.path.normpath(os.path.join(sys.argv[0], "..")) - with open(os.path.join(outdir, "include/lib9p/9p.generated.h"), "w") as fh: - fh.write(gen_h(versions, typs)) - with open(os.path.join(outdir, "9p.generated.c"), "w") as fh: - fh.write(gen_c(versions, typs)) diff --git a/lib9p/idl/0000-README.md b/lib9p/idl/0000-README.md index cec27e2..84cf865 100644 --- a/lib9p/idl/0000-README.md +++ b/lib9p/idl/0000-README.md @@ -1,7 +1,7 @@ <!-- - 0000-README.md - Overview of 9P protocol definitions + lib9p/idl/0000-README.md - Overview of 9P protocol definitions - Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> SPDX-License-Identifier: AGPL-3.0-or-later --> @@ -17,35 +17,82 @@ client->server requests, and R-messages are server->client responses type of a message is represented by a u8 ID; T-messages are even and R-messages are odd. -Messages are made up of the primitives; unsigned little-endian -integers, identified with the following single-character mnemonics: +9P messages are exchanged over a reliable bidirectional in-order octet +stream. Messages are made up of the primitives; unsigned +little-endian integers, identified with the following single-character +mnemonics: - 1 = u8 - 2 = u16le - 4 = u32le - 8 = u16le -Out of these primitives, we can make other numeric types, +Out of these primitives, we can make more complex types: + +## User-defined types + +### Numeric types num NUMNAME = PRIMITIVE_TYPE + "NAME=VAL"... + +Besides just being an alias for a primitive type, a numeric type may +define 0 or more named constants of that type, each wrapped in +"quotes". + +### Bitfields + + bitfield BFNAME = PRIMITIVE_TYPE + "bit NBIT=NAME"... + "bit NBIT=reserved(NAME)"... + "bit NBIT=num(NUMNAME)"... + "alias NAME=VAL"... + "mask NAME=VAL"... + "num(NUMNAME) NAME=VAL"... -bitfields, +The same NBIT may not be defined multiple times. The same NAME may +not be defined multiple times. - bitfield BFNAME = PRIMITIVE_TYPE "NBIT=NAME... ALIAS=VAL..." + - A `reserved(...)` bit indicates that the bit is named but is not + allowed to be used. + - `num(...)` bits embed a numeric/enumerated field within a set of + bits. Once several bits have been allocated to a numeric field + with `bit NBIT=num(NUMNAME)`, constant values for that field may be + declared with `num(NUMNAME) NAME=VAL`. For each numeric field, a + `mask NUMNAME=BITMASK` is automatically declared. + - A `mask` defines a bitmask that selects several bits. + - An `alias` defines a convenience alias for a bit or set of bits. -structures, +### Structures - struct STRUCTNAME = "FILENAME[FIELDTYPE]..." + struct STRUCTNAME = "FIELDNAME[FIELDTYPE]..." -and messages (which are a special-case of structures). +Or a special-case for structs that are messages; `msg` has the same +syntax as `struct`, but has restrictions on the STRUCTNAME and the +first 3 fields must all be declared in the same way: msg Tname = "size[4,val=end-&size] typ[1,val=TYP] tag[tag] REST..." Struct fields that have numeric types (either primitives or `num` types) can add to their type `,val=` and/or `,max=` to specify what the exact value must be and/or what the maximum (inclusive) value is. +A field that is repeated a variable number of times be wrapped in +parenthesis and prefixed with the fieldname containing that count: +`OTHERFIELDNAME*(FIELDNAME[FIELDTYPE])`. `,val=` and `,max` take a string of `+`/`-` tokens and values; a value -can either be a decimal numeric constant (eg: `107`), the `&fieldname` -to refer to the offset of a field name in that struct, or the special -value `end` to refer to the offset of the end of the struct. +can be + - a decimal numeric constant (eg: `107`), + - `&fieldname` to refer to the offset of a field name in that struct, + - the special value `end` to refer to the offset of the end of the + struct, + - the special value `u{8,16,32,64}_max` to refer to the constant + value `(1<<{n})-1`, or + - the special value `s{8,16,32,64}_max` to refer to the constant value + `(1<<({n}-1))-1`. + +## Parser + +A parser for this syntax is given in `__init__.py`. However, +`__init__.py` places the somewhat arbitrary undocumented restrictions +on fields referenced as the count of a repeated field. diff --git a/lib9p/idl/0000-TODO.md b/lib9p/idl/0000-TODO.md new file mode 100644 index 0000000..e52902f --- /dev/null +++ b/lib9p/idl/0000-TODO.md @@ -0,0 +1,11 @@ +<!-- + lib9p/idl/0000-TODO.md - Changes I intend to make to idl/__init__.py + and proto.gen + + Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later + --> + +- Decide how to handle duplicate type names from different versions +- Decide how to handle duplicate `enum lib9p_msg_type` names and + values diff --git a/lib9p/idl/1992-9P0.9p.wip b/lib9p/idl/1992-9P0.9p.wip index 4278fa3..a434ba2 100644 --- a/lib9p/idl/1992-9P0.9p.wip +++ b/lib9p/idl/1992-9P0.9p.wip @@ -1,9 +1,22 @@ -# 1992-9P0.9p - Definitions of 9P0 (Plan 9 1st ed) messages +# lib9p/idl/1992-9P0.9p - Definitions of 9P0 (Plan 9 1st ed) messages # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later -# https://man.cat-v.org/plan_9_1st_ed/5/ +# The original 9P protocol ("9P0"), from Plan 9 1st edition. +# +# Documentation: +# - https://github.com/plan9foundation/plan9/tree/1e-1993-01-03/sys/man/5 +# - https://github.com/plan9foundation/plan9/tree/1e-1993-01-03/sys/man/6/auth +# - https://man.cat-v.org/plan_9_1st_ed/5/ +# - https://man.cat-v.org/plan_9_1st_ed/6/auth +# +# Implementation references: +# - https://github.com/plan9foundation/plan9/blob/1e-1993-01-03/sys/include/fcall.h (MAXFDATA) +# - https://github.com/plan9foundation/plan9/blob/1e-1993-01-03/sys/include/libc.h (`ch` bits) +# - https://github.com/plan9foundation/plan9/blob/1e-1993-01-03/sys/src/fs/port/fcall.c (`stat`) +# - https://github.com/plan9foundation/plan9/blob/1e-1993-01-03/sys/src/fs/port/fs.c (`offset:max`) +# - https://github.com/plan9foundation/plan9/blob/1e-1993-01-03/sys/src/fs/port/portdata.h (MAXDAT) version "9P0" # tag - identify a request/response pair @@ -15,39 +28,114 @@ num fid = 2 # uni"Q"ue "ID"entification struct qid = "path[4] version[4]" -# a nul-padded string -struct name = 28*(txt[1]) - -msg Tnop = "typ[1,val=TODO] tag[tag,val=0xFFFF]" -msg Rnop = "typ[1,val=TODO] tag[tag,val=0xFFFF]" -msg Tsession = "typ[1,val=TODO] tag[tag,val=0xFFFF]" -msg Rsession = "typ[1,val=TODO] tag[tag,val=0xFFFF]" -msg Rerror = "typ[1,val=TODO] tag[tag] ename[64]" -msg Tflush = "typ[1,val=TODO] tag[tag] oldtag[tag]" -msg Rflush = "typ[1,val=TODO] tag[tag]" -msg Tauth = "typ[1,val=TODO] tag[tag] fid[fid] uid[28] chal[36]" -msg Rauth = "typ[1,val=TODO] tag[tag] fid[fid] chal[30]" -msg Tattach = "typ[1,val=TODO] tag[tag] fid[fid] uid[28] aname[28] auth[28]" -msg Rattach = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Tclone = "typ[1,val=TODO] tag[tag] fid[fid] newfid[fid]" -msg Rclone = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Tclwalk = "typ[1,val=TODO] tag[tag] fid[fid] newfid[fid] name[28]" -msg Rclwalk = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Twalk = "typ[1,val=TODO] tag[tag] fid[fid] name[28]" -msg Rwalk = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Topen = "typ[1,val=TODO] tag[tag] fid[fid] mode[1]" -msg Ropen = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Tcreate = "typ[1,val=TODO] tag[tag] fid[fid] name[28] perm[4] mode[1]" -msg Rcreate = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Tread = "typ[1,val=TODO] tag[tag] fid[fid] offset[8] count[2,max=8192]" -msg Rread = "typ[1,val=TODO] tag[tag] fid[fid] count[2,max=8192] pad[1] count*(data[1])" -msg Twrite = "typ[1,val=TODO] tag[tag] fid[fid] offset[8] count[2,max=8192] pad[1] count*(data[1])" -msg Rwrite = "typ[1,val=TODO] tag[tag] fid[fid] count[2,max=8192]" -msg Tclunk = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Rclunk = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Tremove = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Rremove = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Tstat = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Rstat = "typ[1,val=TODO] tag[tag] fid[fid] stat[116]" -msg Twstat = "typ[1,val=TODO] tag[tag] fid[fid] stat[116]" -msg Rwstat = "typ[1,val=TODO] tag[tag] fid[fid]" +# a nul-terminated+padded string +struct name = "28*(txt[1])" + +# a nul-terminated+padded string +struct errstr = "64*(txt[1])" + +# "O"pen flags (flags to pass to Topen and Tcreate) +# Unused bits are *ignored*. +bitfield o = 1 + "bit 0=num(MODE)" # low bit of the 2-bit READ/WRITE/RDWR/EXEC enum + "bit 1=num(MODE)" # high bit of the 2-bit READ/WRITE/RDWR/EXEC enum + #"bit 2=unused" + #"bit 3=unused" + "bit 4=TRUNC" + "bit 5=reserved(CEXEC)" # close-on-exec + "bit 6=RCLOSE" # remove-on-close + #"bit 7=unused" + + "num(MODE) READ = 0" # make available for this FID: Tread() + "num(MODE) WRITE = 1" # make available for this FID: Twrite() + "num(MODE) RDWR = 2" # make available for this FID: Tread() and Twrite() + "num(MODE) EXEC = 3" # make available for this FID: Tread() + + "mask FLAG = 0b11111100" + +# "CH"annel flags - file permissions and attributes (a "channel" is +# what a file handle is called inside of the Plan 9 kernel). +bitfield ch = 4 + "bit 31=DIR" + "bit 30=APPEND" + "bit 29=EXCL" + #... + "bit 8=OWNER_R" + "bit 7=OWNER_W" + "bit 6=OWNER_X" + "bit 5=GROUP_R" + "bit 4=GROUP_W" + "bit 3=GROUP_X" + "bit 2=OTHER_R" + "bit 1=OTHER_W" + "bit 0=OTHER_X" + + "mask PERM=0777" # {OWNER,GROUP,OTHER}_{R,W,X} + +struct stat = "file_name[name]" + "file_owner[name]" + "file_group[name]" + "file_qid[qid]" + "file_mode[ch]" + "file_atime[4]" + "file_mtime[4]" + "file_size[8]" + "kern_type[2]" + "kern_dev[2]" + +# Authentication uses symetric-key encryption, using a per-client +# secret-key. The encryption scheme is beyond the scope of this +# document. +struct auth_ticket = "15*(dat[1])" +struct encrypted_auth_challenge = "36*(ciphertext[1])" +struct cleartext_auth_challenge = "magic[1,val=1] 7*(client_challenge[1]) server[name]" +struct encrypted_auth_response = "30*(ciphertext[1])" +struct cleartext_auth_response = "magic[1,val=4] 7*(client_challenge[1]) ticket[auth_ticket]" + +# A 9P0 session goes: +# +# [nop()] +# session() +# [auth_tok=auth()] +# attach([auth_tok]) +# ... + +msg Tmux = "typ[1,val=48] mux[2]" # Undocumented, but implemented by mux(3) / libmux.a +#msg Rmux = "typ[1,val=49] illegal" +msg Tnop = "typ[1,val=50] tag[tag,val=0xFFFF]" +msg Rnop = "typ[1,val=51] tag[tag,val=0xFFFF]" +msg Tsession = "typ[1,val=52] tag[tag,val=0xFFFF]" +msg Rsession = "typ[1,val=53] tag[tag,val=0xFFFF]" +#msg Terror = "typ[1,val=54] illegal" +msg Rerror = "typ[1,val=55] tag[tag] ename[errstr]" +msg Tflush = "typ[1,val=56] tag[tag] oldtag[tag]" +msg Rflush = "typ[1,val=57] tag[tag]" +msg Tattach = "typ[1,val=58] tag[tag] fid[fid] uid[name] aname[name] auth[auth_ticket] 13*(pad[1])" # Pad to allow auth_tickets up to 28 bytes. +msg Rattach = "typ[1,val=59] tag[tag] fid[fid] qid[qid]" +msg Tclone = "typ[1,val=60] tag[tag] fid[fid] newfid[fid]" +msg Rclone = "typ[1,val=61] tag[tag] fid[fid]" +msg Twalk = "typ[1,val=62] tag[tag] fid[fid] name[name]" +msg Rwalk = "typ[1,val=63] tag[tag] fid[fid] qid[qid]" +msg Topen = "typ[1,val=64] tag[tag] fid[fid] mode[o]" +msg Ropen = "typ[1,val=65] tag[tag] fid[fid] qid[qid]" +msg Tcreate = "typ[1,val=66] tag[tag] fid[fid] name[name] perm[ch] mode[o]" +msg Rcreate = "typ[1,val=67] tag[tag] fid[fid] qid[qid]" +# For `count:max`, see 1e/2e/3e `sys/include/fcall.h:MAXFDATA` or 1e/2e `sys/src/fs/port/portdata.h:MAXDAT`. +# For read `offset:max`, see 1e/2e/3e `sys/src/fs/port/fs.c:f_read()` or 3e `sys/src/lib9p/srv.c:srv():case Tread`. +# For write `offset:max`, see 1e/2e/3e `sys/src/fs/port/fs.c:f_write()`. +msg Tread = "typ[1,val=68] tag[tag] fid[fid] offset[8,max=s64_max] count[2,max=8192]" +msg Rread = "typ[1,val=69] tag[tag] fid[fid] count[2,max=8192] pad[1] count*(data[1])" +msg Twrite = "typ[1,val=70] tag[tag] fid[fid] offset[8,max=s64_max] count[2,max=8192] pad[1] count*(data[1])" +msg Rwrite = "typ[1,val=71] tag[tag] fid[fid] count[2,max=8192]" +msg Tclunk = "typ[1,val=72] tag[tag] fid[fid]" +msg Rclunk = "typ[1,val=73] tag[tag] fid[fid]" +msg Tremove = "typ[1,val=74] tag[tag] fid[fid]" +msg Rremove = "typ[1,val=75] tag[tag] fid[fid]" +msg Tstat = "typ[1,val=76] tag[tag] fid[fid]" +msg Rstat = "typ[1,val=77] tag[tag] fid[fid] stat[stat]" +msg Twstat = "typ[1,val=78] tag[tag] fid[fid] stat[stat]" +msg Rwstat = "typ[1,val=79] tag[tag] fid[fid]" +msg Tclwalk = "typ[1,val=80] tag[tag] fid[fid] newfid[fid] name[name]" +msg Rclwalk = "typ[1,val=81] tag[tag] fid[fid] qid[qid]" +msg Tauth = "typ[1,val=82] tag[tag] fid[fid] uid[name] chal[encrypted_auth_challenge]" # chal is an encrypted +msg Rauth = "typ[1,val=83] tag[tag] fid[fid] chal[encrypted_auth_response]" diff --git a/lib9p/idl/1995-9P1.9p.wip b/lib9p/idl/1995-9P1.9p.wip index 55814d4..660e24a 100644 --- a/lib9p/idl/1995-9P1.9p.wip +++ b/lib9p/idl/1995-9P1.9p.wip @@ -1,52 +1,107 @@ -# 1995-9P1.9p - Definitions of 9P1 (Plan 9 2nd ed and 3rd ed) messages +# lib9p/idl/1995-9P1.9p - Definitions of 9P1 (Plan 9 2nd ed and 3rd ed) messages # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later -# https://man.cat-v.org/plan_9_2nd_ed/5/ -# https://man.cat-v.org/plan_9_3rd_ed/5/ +# Plan 9 2nd and 3rd edition used a version of 9P lightly revised from +# the 1st edition version, re-thinking authentication. +# +# 2nd edition documentation: +# - https://github.com/plan9foundation/plan9/tree/2e-1995-04-05/sys/man/5 +# - https://github.com/plan9foundation/plan9/tree/2e-1995-04-05/sys/man/6/auth +# - https://man.cat-v.org/plan_9_2nd_ed/5/ +# - https://man.cat-v.org/plan_9_2nd_ed/6/auth +# +# 2nd edition implementation references: +# - https://github.com/plan9foundation/plan9/blob/2e-1995-04-05/sys/include/fcall.h (MAXFDATA) +# - https://github.com/plan9foundation/plan9/blob/2e-1995-04-05/sys/include/libc.h (`ch` bits) +# - https://github.com/plan9foundation/plan9/blob/2e-1995-04-05/sys/include/auth.h (auth matic) +# - https://github.com/plan9foundation/plan9/blob/2e-1995-04-05/sys/src/fs/port/fcall.c (`stat`) +# - https://github.com/plan9foundation/plan9/blob/2e-1995-04-05/sys/src/fs/port/fs.c (`offset:max`) +# - https://github.com/plan9foundation/plan9/blob/2e-1995-04-05/sys/src/fs/port/portdata.h (`MAXDAT`) +# - https://github.com/plan9foundation/plan9/blob/2e-1995-04-05/sys/src/libauth/convM2T.c (`auth_ticket`) +# +# 3rd edition documentation: +# - https://github.com/plan9foundation/plan9/tree/3e-2001-03-28/sys/man/5 +# - https://github.com/plan9foundation/plan9/tree/3e-2001-03-28/sys/man/6/auth +# - https://man.cat-v.org/plan_9_3rd_ed/5/ +# - https://man.cat-v.org/plan_9_3rd_ed/6/auth +# +# 3rd edition implementation references: +# - https://github.com/plan9foundation/plan9/blob/3e-2001-03-28/sys/include/fcall.h (MAXFDATA) +# - https://github.com/plan9foundation/plan9/blob/3e-2001-03-28/sys/include/libc.h (`ch` bits) +# - https://github.com/plan9foundation/plan9/blob/3e-2001-03-28/sys/include/auth.h (auth magic) +# - https://github.com/plan9foundation/plan9/blob/3e-2001-03-28/sys/src/fs/port/fcall.c (`stat`) +# - https://github.com/plan9foundation/plan9/blob/3e-2001-03-28/sys/src/fs/port/fs.c (`offset:max`) +# - https://github.com/plan9foundation/plan9/blob/3e-2001-03-28/sys/src/lib9p/srv.c (read `offset:max`) +# - https://github.com/plan9foundation/plan9/blob/3e-2001-03-28/sys/src/libauth/convM2T.c (`auth_ticket`) version "9P1" -# tag - identify a request/response pair -num tag = 2 +from ./1992-9P0.9p import tag, fid, qid, name, errstr, o, ch, stat -# file identifier - like a UNIX file-descriptor -num fid = 2 +# CHMOUNT is undocumented (and is explicitly excluded from the 9P2000 +# draft RFC). As I understand it, CHMOUNT indicates that the file is +# mounted by the kernel as a 9P transport; that the kernel has a lock +# on doing I/O on it, so userspace can't do I/O on it. +bitfield ch += "bit 28=_PLAN9_MOUNT" -# uni"Q"ue "ID"entification -struct qid = "path[4] version[4]" +# Authentication uses DES encryption. The client obtains a ticket and +# a nonce-key from a separate auth-server; how it does this is beyond +# the scope of this document. +struct random = "8*(dat[1])" +struct domain_name = "48*(txt[1])" +struct des_key = "7*(dat[1])" +struct encrypted_ticket = "72*(ciphertext[1])" # encrypted by auth-server with server-key +struct cleartext_ticket = "magic[1,val=64] server_chal[random] client_uid[name] server_uid[name] nonce_key[des_key]" +struct encrypted_authenticator_challenge = "13*(ciphertext[1])" # encrypted by client with nonce-key obtained from auth-server +struct cleartext_authenticator_challenge = "magic[1,val=66] server_chal[random] replay_count[4]" +struct encrypted_authenticator_response = "13*(ciphertext[1])" # encrypted by server with nonce-key obtained from ticket +struct cleartext_authenticator_response = "magic[1,val=67] client_chal[random] replay_count[4]" -# a nul-padded string -struct name = 28*(txt[1]) +# A 9P0 session goes: +# +# [nop()] +# auth_tok=[session()] +# attach(auth_tok) +# ... -msg Tnop = "typ[1,val=TODO] tag[tag,val=0xFFFF]" -msg Rnop = "typ[1,val=TODO] tag[tag,val=0xFFFF]" -msg Tsession = "typ[1,val=TODO] tag[tag,val=0xFFFF] chal[8]" -msg Rsession = "typ[1,val=TODO] tag[tag,val=0xFFFF] chal[8] authid[28] authdom[48]" -msg Rerror = "typ[1,val=TODO] tag[tag] ename[64]" -msg Tflush = "typ[1,val=TODO] tag[tag] oldtag[tag]" -msg Rflush = "typ[1,val=TODO] tag[tag]" -msg Tattach = "typ[1,val=TODO] tag[tag] fid[fid] uid[28] aname[28] ticket[72] auth[13]" -msg Rattach = "typ[1,val=TODO] tag[tag] fid[fid] qid[8] rauth[13]" -msg Tclone = "typ[1,val=TODO] tag[tag] fid[fid] newfid[fid]" -msg Rclone = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Tclwalk = "typ[1,val=TODO] tag[tag] fid[fid] newfid[fid] name[28]" -msg Rclwalk = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Twalk = "typ[1,val=TODO] tag[tag] fid[fid] name[28]" -msg Rwalk = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Topen = "typ[1,val=TODO] tag[tag] fid[fid] mode[1]" -msg Ropen = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Tcreate = "typ[1,val=TODO] tag[tag] fid[fid] name[28] perm[4] mode[1]" -msg Rcreate = "typ[1,val=TODO] tag[tag] fid[fid] qid[8]" -msg Tread = "typ[1,val=TODO] tag[tag] fid[fid] offset[8] count[2,max=8192]" -msg Rread = "typ[1,val=TODO] tag[tag] fid[fid] count[2,max=8192] pad[1] count*(data[1])" -msg Twrite = "typ[1,val=TODO] tag[tag] fid[fid] offset[8] count[2,max=8192] pad[1] count*(data[1])" -msg Rwrite = "typ[1,val=TODO] tag[tag] fid[fid] count[2,max=8192]" -msg Tclunk = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Rclunk = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Tremove = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Rremove = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Tstat = "typ[1,val=TODO] tag[tag] fid[fid]" -msg Rstat = "typ[1,val=TODO] tag[tag] fid[fid] stat[116]" -msg Twstat = "typ[1,val=TODO] tag[tag] fid[fid] stat[116]" -msg Rwstat = "typ[1,val=TODO] tag[tag] fid[fid]" +#from ./1992-9P0.9p import Tmux # typ=48 ; removed +#from ./1992-9P0.9p import Rmux # typ=49 ; removed +from ./1992-9P0.9p import Tnop # typ=50 +from ./1992-9P0.9p import Rnop # typ=51 +#from ./1992-9P0.9p import Tsession # typ=52 ; revised, now has typ=84 +#from ./1992-9P0.9p import Rsession # typ=53 ; revised, now has typ=85 +#from ./1992-9P0.9p import Terror # typ=54 ; never existed +from ./1992-9P0.9p import Rerror # typ=55 +from ./1992-9P0.9p import Tflush # typ=56 +from ./1992-9P0.9p import Rflush # typ=57 +#from ./1992-9P0.9p import Tattach # typ=58 ; revised, now has typ=86 +#from ./1992-9P0.9p import Rattach # typ=59 ; revised, now has typ=87 +from ./1992-9P0.9p import Tclone # typ=60 +from ./1992-9P0.9p import Rclone # typ=61 +from ./1992-9P0.9p import Twalk # typ=62 +from ./1992-9P0.9p import Rwalk # typ=63 +from ./1992-9P0.9p import Topen # typ=64 +from ./1992-9P0.9p import Ropen # typ=65 +from ./1992-9P0.9p import Tcreate # typ=66 +from ./1992-9P0.9p import Rcreate # typ=67 +from ./1992-9P0.9p import Tread # typ=68 +from ./1992-9P0.9p import Rread # typ=69 +from ./1992-9P0.9p import Twrite # typ=70 +from ./1992-9P0.9p import Rwrite # typ=71 +from ./1992-9P0.9p import Tclunk # typ=72 +from ./1992-9P0.9p import Rclunk # typ=73 +from ./1992-9P0.9p import Tremove # typ=74 +from ./1992-9P0.9p import Rremove # typ=75 +from ./1992-9P0.9p import Tstat # typ=76 +from ./1992-9P0.9p import Rstat # typ=77 +from ./1992-9P0.9p import Twstat # typ=78 +from ./1992-9P0.9p import Rwstat # typ=79 +from ./1992-9P0.9p import Tclwalk # typ=80 +from ./1992-9P0.9p import Rclwalk # typ=81 +#from ./1992-9P0.9p import Tauth # typ=82 ; merged into Tsession +#from ./1992-9P0.9p import Rauth # typ=83 ; merged into Rsession +msg Tsession = "typ[1,val=84] tag[tag,val=0xFFFF] chal[random]" +msg Rsession = "typ[1,val=85] tag[tag,val=0xFFFF] chal[random] server_name[name] server_domain[domain_name]" +msg Tattach = "typ[1,val=86] tag[tag] fid[fid] uid[name] aname[name] ticket[encrypted_ticket] auth[encrypted_authenticator_challenge]" +msg Rattach = "typ[1,val=87] tag[tag] fid[fid] qid[qid] rauth[encrypted_authenticator_response]" diff --git a/lib9p/idl/1996-Styx.9p.wip b/lib9p/idl/1996-Styx.9p.wip index 2feb24f..3cb3774 100644 --- a/lib9p/idl/1996-Styx.9p.wip +++ b/lib9p/idl/1996-Styx.9p.wip @@ -1,15 +1,59 @@ -# 1996-Styx.9p - Definitions of Styx messages +# lib9p/idl/1996-Styx.9p - Definitions of Styx messages # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later # Styx was a variant of the 9P protocol used by the Inferno operating -# system. Message framing looks like 9P1 (1995), but semantics look -# more like 9P2000 (2002). I am not sure whether there are Styx -# protocol differences between Inferno 1e, 2e, or 3e (4e adopted -# 9P2000). -# -# - 1996 beta -# - 1997 1.0 -# - 1999 2nd ed -# - 2001 3rd ed +# system (before it switched to 9P2000 in 4th edition). It looks +# exactly like 9P1 but with different message-type numbers and without +# `clwalk` or `session`, and no authentication in `attach`. +# +# There do not appear to be Styx protocol differences between Inferno +# 1e, 2e, or 3e. +# +# - 1996 beta https://github.com/inferno-os/inferno-1e0/blob/main/ see `man/html/proto*.htm`, `include/styx.h`, and `Linux/386/include/lib9.h` +# - 1997 1e https://github.com/inferno-os/inferno-1e1/blob/master/ see `man/html/mpgs{113..124}.htm`, `include/styx.h`, `os/port/lib.h`, and `os/fs/fs.c` +# - 1999 2e https://github.com/inferno-os/inferno-2e/blob/master/ see `include/styx.h`, `os/port/lib.h`, and `os/kfs/fs.c` (no public manpages) +# - 2001 3e https://github.com/inferno-os/inferno-3e/blob/master/ see `include/man/5/`, `include/styx.h`, `os/port/lib.h`, and `os/kfs/fs.c` +version "Styx" + +from ./1992-9P1.9p import tag, fid, qid, name, errstr, o, ch, stat + +# A Styx session goes: +# +# [nop()] +# attach() +# ... + +msg Tnop = "typ[1,val=0] tag[tag,val=0xFFFF]" +msg Rnop = "typ[1,val=1] tag[tag,val=0xFFFF]" +#msg Terror = "typ[1,val=2] illegal" +msg Rerror = "typ[1,val=3] tag[tag] ename[errstr]" +msg Tflush = "typ[1,val=4] tag[tag] oldtag[tag]" +msg Rflush = "typ[1,val=5] tag[tag]" +msg Tclone = "typ[1,val=6] tag[tag] fid[fid] newfid[fid]" +msg Rclone = "typ[1,val=7] tag[tag] fid[fid]" +msg Twalk = "typ[1,val=8] tag[tag] fid[fid] name[name]" +msg Rwalk = "typ[1,val=9] tag[tag] fid[fid] qid[qid]" +msg Topen = "typ[1,val=10] tag[tag] fid[fid] mode[o]" +msg Ropen = "typ[1,val=11] tag[tag] fid[fid] qid[qid]" +msg Tcreate = "typ[1,val=12] tag[tag] fid[fid] name[name] perm[ch] mode[o]" +msg Rcreate = "typ[1,val=13] tag[tag] fid[fid] qid[qid]" +# For `offset:max`, see `fs.c` `f_read()` and `f_write()`. +# For `count:max`, see `styx.h:MAXFDATA`. +msg Tread = "typ[1,val=14] tag[tag] fid[fid] offset[8,max=s64_max] count[2,max=8192]" +msg Rread = "typ[1,val=15] tag[tag] fid[fid] count[2,max=8192] pad[1] count*(data[1])" +msg Twrite = "typ[1,val=16] tag[tag] fid[fid] offset[8,max=s64_max] count[2,max=8192] pad[1] count*(data[1])" +msg Rwrite = "typ[1,val=17] tag[tag] fid[fid] count[2,max=8192]" +msg Tclunk = "typ[1,val=18] tag[tag] fid[fid]" +msg Rclunk = "typ[1,val=19] tag[tag] fid[fid]" +msg Tremove = "typ[1,val=20] tag[tag] fid[fid]" +msg Rremove = "typ[1,val=21] tag[tag] fid[fid]" +msg Tstat = "typ[1,val=22] tag[tag] fid[fid]" +msg Rstat = "typ[1,val=23] tag[tag] fid[fid] stat[stat]" +msg Twstat = "typ[1,val=24] tag[tag] fid[fid] stat[stat]" +msg Rwstat = "typ[1,val=25] tag[tag] fid[fid]" +#msg Tsession = "typ[1,val=26]" # The 1e kernel used Tsession in structs internally, but never transmitted it. +#msg Rsession = "typ[1,val=27]" # Implied by Tsession. +msg Tattach = "typ[1,val=28] tag[tag] fid[fid] uid[name] aname[name]" +msg Rattach = "typ[1,val=29] tag[tag] fid[fid] qid[qid]" diff --git a/lib9p/idl/2002-9P2000.9p b/lib9p/idl/2002-9P2000.9p index 47d402a..2b51612 100644 --- a/lib9p/idl/2002-9P2000.9p +++ b/lib9p/idl/2002-9P2000.9p @@ -1,6 +1,6 @@ -# 2002-9P2000.9p - Definitions of 9P2000 messages +# lib9p/idl/2002-9P2000.9p - Definitions of 9P2000 messages # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later # "9P2000" base protocol @@ -9,74 +9,77 @@ # # But due to incompleteness of the draft RFC, the Plan 9 manual # section-5 and the Plan 9 headers (particularly fcall.h) are often -# better references. +# better references. The s{32,64}_max limitations are not documented +# in the draft RFC or the manual pages, but have been enforced by +# lib9p/srv.c in every release of Plan 9 4e. # # https://github.com/plan9foundation/plan9/tree/main/sys/man/5 # https://man.cat-v.org/plan_9/5/ # https://github.com/plan9foundation/plan9/blob/main/sys/include/fcall.h +# https://github.com/plan9foundation/plan9/blob/main/sys/include/libc.h +# https://github.com/plan9foundation/plan9/blob/main/sys/src/lib9p/srv.c version "9P2000" # tag - identify a request/response pair num tag = 2 + "NOTAG = u16_max" # file identifier - like a UNIX file-descriptor num fid = 4 - -# data - u32le `n`, then `n` bytes of data -struct d = "len[4] len*(dat[1])" + "NOFID = u32_max" # string - u16le `n`, then `n` bytes of UTF-8, without any nul-bytes struct s = "len[2] len*(utf8[1])" -# "d"? mode - file permissions and attributes +# "D"ir-entry "M"ode - file permissions and attributes bitfield dm = 4 - "31=DIR" - "30=APPEND" - "29=EXCL" - # DMMOUNT has been around in Plan 9 forever, but is - # undocumented, and is explicitly excluded from the 9P2000 - # draft RFC. As I understand it, DMMOUNT indicates that the - # file is mounted by the kernel as a 9P transport; that the - # kernel has a lock on doing I/O on it, so userspace can't do - # I/O on it. - "28=_PLAN9_MOUNT" - "27=AUTH" - "26=TMP" + "bit 31=DIR" + "bit 30=APPEND" + "bit 29=EXCL" + # DMMOUNT has been around in Plan 9 since 2e (CHMOUNT in <4e), + # but is undocumented, and is explicitly excluded from the + # 9P2000 draft RFC. As I understand it, DMMOUNT indicates + # that the file is mounted by the kernel as a 9P transport; + # that the kernel has a lock on doing I/O on it, so userspace + # can't do I/O on it. + "bit 28=_PLAN9_MOUNT" + "bit 27=AUTH" + "bit 26=TMP" #... - "8=OWNER_R" - "7=OWNER_W" - "6=OWNER_X" - "5=GROUP_R" - "4=GROUP_W" - "3=GROUP_X" - "2=OTHER_R" - "1=OTHER_W" - "0=OTHER_X" + "bit 8=OWNER_R" + "bit 7=OWNER_W" + "bit 6=OWNER_X" + "bit 5=GROUP_R" + "bit 4=GROUP_W" + "bit 3=GROUP_X" + "bit 2=OTHER_R" + "bit 1=OTHER_W" + "bit 0=OTHER_X" - "PERM_MASK=0777" # {OWNER,GROUP,OTHER}_{R,W,X} + "mask PERM=0777" # {OWNER,GROUP,OTHER}_{R,W,X} # QID Type - see `struct qid` below bitfield qt = 1 - "7=DIR" - "6=APPEND" - "5=EXCL" - "4=_PLAN9_MOUNT" # see "MOUNT" in "dm" above - "3=AUTH" + "bit 7=DIR" + "bit 6=APPEND" + "bit 5=EXCL" + "bit 4=_PLAN9_MOUNT" # See "_PLAN9_MOUNT" in "dm" above. + "bit 3=AUTH" # Fun historical fact: QTTMP was a relatively late addition to # Plan 9, in 2003-12. - "2=TMP" - #"1=unused" + "bit 2=TMP" + #"bit 1=unused" # "The name QTFILE, defined to be zero, identifies the value # of the type for a plain file." - "FILE=0" + "alias FILE=0" # uni"Q"ue "ID"entification - "two files on the same server hierarchy # are the same if and only if their qids are the same" # # - "path" is a unique uint64_t that does most of the work in the # above statement about files being the same if their QIDs are the -# same; " If a file is deleted and recreated with the same name in +# same; "If a file is deleted and recreated with the same name in # the same directory, the old and new path components of the qids # should be different" # @@ -102,23 +105,30 @@ struct stat = "stat_size[2,val=end-&kern_type]" "file_last_modified_uid[s]" # "O"pen flags (flags to pass to Topen and Tcreate) +# Unused bits *must* be 0. bitfield o = 1 - "0=mode_0" # low bit of the 2-bit READ/WRITE/RDWR/EXEC enum - "1=mode_1" # high bit of the 2-bit READ/WRITE/RDWR/EXEC enum" - #"2=unused" - #"3=unused" - "4=TRUNC" - #"5=unused" - "6=RCLOSE" # remove-on-close - #"7=unused" + "bit 0=num(MODE)" # low bit of the 2-bit READ/WRITE/RDWR/EXEC enum + "bit 1=num(MODE)" # high bit of the 2-bit READ/WRITE/RDWR/EXEC enum + #"bit 2=unused" + #"bit 3=unused" + "bit 4=TRUNC" + "bit 5=reserved(CEXEC)" # close-on-exec + "bit 6=RCLOSE" # remove-on-close + #"bit 7=unused" + + "num(MODE) READ = 0" # make available for this FID: Tread() + "num(MODE) WRITE = 1" # make available for this FID: Twrite() + "num(MODE) RDWR = 2" # make available for this FID: Tread() and Twrite() + "num(MODE) EXEC = 3" # make available for this FID: Tread() - "READ = 0" # make available for this FID: Tread() - "WRITE = 1" # make available for this FID: Twrite() - "RDWR = 2" # make available for this FID: Tread() and Twrite() - "EXEC = 3" # make available for this FID: Tread() + "mask FLAG = 0b11111100" - "MODE_MASK = 0b00000011" - "FLAG_MASK = 0b11111100" +# A 9P2000 session goes: +# +# version() +# [auth_fid=auth] +# attach([auth_fid]) +# ... msg Tversion = "size[4,val=end-&size] typ[1,val=100] tag[tag] max_msg_size[4] version[s]" msg Rversion = "size[4,val=end-&size] typ[1,val=101] tag[tag] max_msg_size[4] version[s]" @@ -136,10 +146,10 @@ msg Topen = "size[4,val=end-&size] typ[1,val=112] tag[tag] fid[fid] mode[o]" msg Ropen = "size[4,val=end-&size] typ[1,val=113] tag[tag] qid[qid] iounit[4]" msg Tcreate = "size[4,val=end-&size] typ[1,val=114] tag[tag] fid[fid] name[s] perm[dm] mode[o]" msg Rcreate = "size[4,val=end-&size] typ[1,val=115] tag[tag] qid[qid] iounit[4]" -msg Tread = "size[4,val=end-&size] typ[1,val=116] tag[tag] fid[fid] offset[8] count[4]" -msg Rread = "size[4,val=end-&size] typ[1,val=117] tag[tag] data[d]" # for directories `data` is the sequence "cnt*(entries[stat])" -msg Twrite = "size[4,val=end-&size] typ[1,val=118] tag[tag] fid[fid] offset[8] data[d]" -msg Rwrite = "size[4,val=end-&size] typ[1,val=119] tag[tag] count[4]" +msg Tread = "size[4,val=end-&size] typ[1,val=116] tag[tag] fid[fid] offset[8,max=s64_max] count[4,max=s32_max]" # See 4e `sys/src/lib9p/srv.c:sread()` for `offset:max` and `count:max`. +msg Rread = "size[4,val=end-&size] typ[1,val=117] tag[tag] count[4,max=s32_max] count*(data[1])" # `max` is inherited from Tread, for directories `data` is the sequence "cnt*(entries[stat])". +msg Twrite = "size[4,val=end-&size] typ[1,val=118] tag[tag] fid[fid] offset[8,max=s64_max] count[4,max=s32_max] count*(data[1])" # See 4e `sys/src/lib9p/srv.c:swrite()` for `offset:max` and `count:max`. +msg Rwrite = "size[4,val=end-&size] typ[1,val=119] tag[tag] count[4,max=s32_max]" # `max` is inherited from Twrite. msg Tclunk = "size[4,val=end-&size] typ[1,val=120] tag[tag] fid[fid]" msg Rclunk = "size[4,val=end-&size] typ[1,val=121] tag[tag]" msg Tremove = "size[4,val=end-&size] typ[1,val=122] tag[tag] fid[fid]" diff --git a/lib9p/idl/2003-9P2000.p9p.9p b/lib9p/idl/2003-9P2000.p9p.9p new file mode 100644 index 0000000..3f6a524 --- /dev/null +++ b/lib9p/idl/2003-9P2000.p9p.9p @@ -0,0 +1,49 @@ +# lib9p/idl/2003-9P2000.p9p.9p - Definitions of plan9port extension messages +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +# Plan 9 from User Space (a.k.a. plan9port)'s lib9pclient:fsopenfd(3) +# and 9pserve(4) proxy server (which takes the place of the Plan 9 +# kernel) add a special-purpose `openfd` command to 9P2000. +# +# https://9fans.github.io/plan9port/man/man9/openfd.htlm +# https://github.com/9fans/plan9port/blob/master/man/man9/openfd.9p +# https://github.com/9fans/plan9port/commit/32f69c36e0eec1227934bbd34854bfebd88686f2 +# https://github.com/9fans/plan9port/pull/692 + +# BUG: There is no version-string for this extension; plan9port still +# calls it vanilla "9P2000". +version "9P2000.p9p" + +from ./2002-9P2000.9p import * + +# On Plan 9 the usual 9P client is the kernel, not normal userspace +# programs. The kernel multiplexes multiple userspace processes onto +# a single 9P connection; the userspace programs make 9P calls by +# making syscalls. plan9port emulates this by having mock syscalls +# that make 9P client calls over an AF_UNIX socket to a local +# `9pserve` daemon that does the multiplexing (you add an "fs" prefix; +# e.g. you replace syscall:`open()` with lib9pclient:`fsopen()`). +# +# "Unfortunately", programs in plan9port must deal both with 9P files +# and native "Unix" files; and need to turn an 9P FID into a native +# file descriptor. To do this, the `9pserve` program and lib9pclient +# add an extension call to 9P2000: Topenfd/Ropenfd/fsopenfd(). +# +# An AF_UNIX socket has the ability to send a file descriptor over it +# via an out-of-band "socket control message" ("CMSG" or "SCM"). +# +# Topenfd asks 9pserve to create a socketpair() file descriptor that +# 9pserve will pump to/from a FID, and then send that pipe file +# descriptor over a control-message to the client program. +# +# When replying, the server sends not just an in-band Ropenfd message, +# but also an out-of-band control-message with a socketpair() file +# descriptor. A successful call results in the FID being clunked. +msg Topenfd = "size[4,val=end-&size] typ[1,val=98] tag[tag] fid[fid] mode[o]" +msg Ropenfd = "size[4,val=end-&size] typ[1,val=99] tag[tag] qid[qid] iounit[4] unixfd[4]" +# BUG: The "unixfd" field nominally indicates the the file descriptor +# of the pipe, but really 9pserve doesn't know which FD it will end up +# on the client process, and lib9pclient ignores the value here and +# overwrites it with the file descriptor indicated from the CMSG diff --git a/lib9p/idl/2005-9P2000.u.9p b/lib9p/idl/2005-9P2000.u.9p index 8b59efa..6c2f2dc 100644 --- a/lib9p/idl/2005-9P2000.u.9p +++ b/lib9p/idl/2005-9P2000.u.9p @@ -1,6 +1,6 @@ -# 2005-9P2000.u.9p - Definitions of 9P2000.u messages +# lib9p/idl/2005-9P2000.u.9p - Definitions of 9P2000.u messages # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later # "9P2000.u" Unix extension @@ -10,20 +10,27 @@ version "9P2000.u" from ./2002-9P2000.9p import * +# numeric user ID +num nuid = 4 + "NONUID = u32_max" + +num errno = 4 + "NOERROR = 0" + struct stat += "file_extension[s]" - "file_owner_n_uid[4]" - "file_owner_n_gid[4]" - "file_last_modified_n_uid[4]" + "file_owner_n_uid[nuid]" + "file_owner_n_gid[nuid]" + "file_last_modified_n_uid[nuid]" -msg Tauth += "n_uname[4]" -msg Tattach += "n_uname[4]" +msg Tauth += "n_uid[nuid]" +msg Tattach += "n_uid[nuid]" -msg Rerror += "errno[4]" +msg Rerror += "errno[errno]" -bitfield dm += "23=DEVICE" - "21=NAMEDPIPE" - "20=SOCKET" - "19=SETUID" - "18=SETGID" +bitfield dm += "bit 23=DEVICE" + "bit 21=PIPE" + "bit 20=SOCKET" + "bit 19=SETUID" + "bit 18=SETGID" -bitfield qt += "1=SYMLINK" +bitfield qt += "bit 1=SYMLINK" diff --git a/lib9p/idl/2010-9P2000.L.9p b/lib9p/idl/2010-9P2000.L.9p new file mode 100644 index 0000000..d81a15b --- /dev/null +++ b/lib9p/idl/2010-9P2000.L.9p @@ -0,0 +1,230 @@ +# lib9p/idl/2010-9P2000.L.9p - Definitions of 9P2000.L messages +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +# "9P2000.L" Linux extension +# https://github.com/chaos/diod/blob/master/protocol.md +# https://github.com/chaos/diod/blob/master/src/libnpfs/protocol.h +version "9P2000.L" + +from ./2002-9P2000.9p import tag, fid, s, qt, qid +from ./2002-9P2000.9p import Rerror +from ./2002-9P2000.9p import Tversion, Rversion, Tflush, Rflush, Twalk, Rwalk, Tread, Rread, Twrite, Rwrite, Tclunk, Rclunk, Tremove, Rremove +from ./2005-9P2000.u.9p import nuid, errno, Tauth, Rauth, Tattach, Rattach + +#num errno += # TODO + +num super_magic = 4 + # See <linux/magic.h> (linux.git include/uapi/linux/magic.h). + # + # To quote `util-linux.git:include/statfs_magic.h`: + # "Unfortunately, Linux kernel header file <linux/magic.h> is + # incomplete mess and kernel returns by statfs f_type many numbers + # that are nowhere specified (in API)." + # + # util-linux <statfs_magic.h> is also incomplete. As is the + # statfs(2) man-page. + # + # I'm working on a patchset to the kernel to get <linux/magic.h> + # to be complete, but in the mean-time I'm just not going to + # bother with putting a list here. + # + # TODO + "V9FS_MAGIC=0x01021997" + +# "L"inux "O"pen flags (flags to pass to Tlopen and Tlcreate) +# +# The values are not specified in in protocol.md, but are specified in +# protocol.h (and are different than the Linux kernel's values, which +# vary by architecture). +bitfield lo = 4 + "bit 0=num(MODE)" # low bit of the 2-bit RDONLY/WRONLY/RDWR/NOACCESS enum + "bit 1=num(MODE)" # high bit of the 2-bit RDONLY/WRONLY/RDWR/NOACCESS enum + #"bit 2=unused" + #"bit 3=unused" + #"bit 4=unused" + #"bit 5=unused" + "bit 6=CREATE" + "bit 7=EXCL" + "bit 8=NOCTTY" + "bit 9=TRUNC" + "bit 10=APPEND" + "bit 11=NONBLOCK" + "bit 12=DSYNC" + "bit 13=BSD_FASYNC" + "bit 14=DIRECT" + "bit 15=LARGEFILE" + "bit 16=DIRECTORY" + "bit 17=NOFOLLOW" + "bit 18=NOATIME" + "bit 19=CLOEXEC" + "bit 20=SYNC" + + "num(MODE) RDONLY = 0" + "num(MODE) WRONLY = 1" + "num(MODE) RDWR = 2" + "num(MODE) NOACCESS = 3" + + "mask FLAG = 0b111111111111111000000" + +# "D"irentry "T"ype +# +# These match the Linux kernel's values. +num dt = 1 + "UNKNOWN = 0" + "PIPE = 1" + "CHAR_DEV = 2" + "DIRECTORY = 4" + "BLOCK_DEV = 6" # proof it's not a bitfield + "REGULAR = 8" + "SYMLINK = 10" # proof it's not a bitfield + "SOCKET = 12" # proof it's not a bitfield + "_WHITEOUT = 14" # proof it's not a bitfield + +# Mode +# +# These match the Linux kernel's values. Why is this 32-bits wide +# instead of just 16? Who knows? +bitfield mode = 4 + #... + "bit 15=num(FMT)" # bit of the 4-bit FMT_ enum + "bit 14=num(FMT)" # bit of the 4-bit FMT_ enum + "bit 13=num(FMT)" # bit of the 4-bit FMT_ enum + "bit 12=num(FMT)" # bit of the 4-bit FMT_ enum + #... + "bit 11=PERM_SETGROUP" + "bit 10=PERM_SETUSER" + "bit 9=PERM_STICKY" + "bit 8=PERM_OWNER_R" + "bit 7=PERM_OWNER_W" + "bit 6=PERM_OWNER_X" + "bit 5=PERM_GROUP_R" + "bit 4=PERM_GROUP_W" + "bit 3=PERM_GROUP_X" + "bit 2=PERM_OTHER_R" + "bit 1=PERM_OTHER_W" + "bit 0=PERM_OTHER_X" + + "num(FMT) PIPE = dt.PIPE<<12" + "num(FMT) CHAR_DEV = dt.CHAR_DEV<<12" + "num(FMT) DIRECTORY = dt.DIRECTORY<<12" + "num(FMT) BLOCK_DEV = dt.BLOCK_DEV<<12" + "num(FMT) REGULAR = dt.REGULAR<<12" + "num(FMT) SYMLINK = dt.SYMLINK<<12" + "num(FMT) SOCKET = dt.SOCKET<<12" + + "mask PERM = 07777" # PERM_* + +# A boolean value that is for some reason 4 bytes wide. +num b4 = 4 + "FALSE=0" + "TRUE=1" + # all other values are true also + +bitfield getattr = 8 + "bit 0=MODE" + "bit 1=NLINK" + "bit 2=UID" + "bit 3=GID" + "bit 4=RDEV" + "bit 5=ATIME" + "bit 6=MTIME" + "bit 7=CTIME" + "bit 8=INO" + "bit 9=SIZE" + "bit 10=BLOCKS" + + "bit 11=BTIME" + "bit 12=GEN" + "bit 13=DATA_VERSION" + + "alias BASIC=0x000007ff" # Mask for fields up to BLOCKS + "alias ALL =0x00003fff" # Mask for All fields above + +bitfield setattr = 4 + "bit 0=MODE" + "bit 1=UID" + "bit 2=GID" + "bit 3=SIZE" + "bit 4=ATIME" + "bit 5=MTIME" + "bit 6=CTIME" + "bit 7=ATIME_SET" + "bit 8=MTIME_SET" + +num lock_type = 1 + "RDLCK=0" + "WRLCK=1" + "UNLCK=2" + +bitfield lock_flags = 4 + "bit 0=BLOCK" + "bit 1=RECLAIM" + +num lock_status = 1 + "SUCCESS=0" + "BLOCKED=1" + "ERROR=2" + "GRACE=3" + +#msg Tlerror = "size[4,val=end-&size] typ[1,val=6] tag[tag] illegal" # analogous to 106/Terror +msg Rlerror = "size[4,val=end-&size] typ[1,val=7] tag[tag] ecode[errno]" # analogous to 107/Rerror +msg Tstatfs = "size[4,val=end-&size] typ[1,val=8] tag[tag] fid[fid]" +msg Rstatfs = "size[4,val=end-&size] typ[1,val=9] tag[tag]" # Description | statfs | statvfs + "type[super_magic]" # Type of filesystem | f_type | - + "bsize[4]" # Block size in bytes | f_bsize | f_bsize + # - # Fragment size in bytes | f_frsize (since Linux 2.6) | f_frsize + "blocks[8]" # Size of FS in f_frsize units | f_blocks | f_blocks + "bfree[8]" # Number of free blocks | f_bfree | f_bfree + "bavail[8]" # Number of free blocks for unprivileged users | f_bavail | b_avail + "files[8]" # Number of inodes | f_files | f_files + "ffree[8]" # Number of free inodes | f_ffree | f_ffree + # - # Number of free inodes for unprivileged users | - | f_favail + "fsid[8]" # Filesystem instance ID | f_fsid | f_fsid + # - # Mount flags | f_flags (since Linux 2.6.36) | f_flag + "namelen[4]" # Maximum filename length | f_namemax | f_namemax +msg Tlopen = "size[4,val=end-&size] typ[1,val=12] tag[tag] fid[fid] flags[lo]" # analogous to 112/Topen +msg Rlopen = "size[4,val=end-&size] typ[1,val=13] tag[tag] qid[qid] iounit[4]" # analogous to 113/Ropen +msg Tlcreate = "size[4,val=end-&size] typ[1,val=14] tag[tag] fid[fid] name[s] flags[lo] mode[mode] gid[nuid]" # analogous to 114/Tcreate +msg Rlcreate = "size[4,val=end-&size] typ[1,val=15] tag[tag] qid[qid] iounit[4]" # analogous to 115/Rcreate +msg Tsymlink = "size[4,val=end-&size] typ[1,val=16] tag[tag] fid[fid] name[s] symtgt[s] gid[nuid]" +msg Rsymlink = "size[4,val=end-&size] typ[1,val=17] tag[tag] qid[qid]" +msg Tmknod = "size[4,val=end-&size] typ[1,val=18] tag[tag] dfid[fid] name[s] mode[mode] major[4] minor[4] gid[nuid]" +msg Rmknod = "size[4,val=end-&size] typ[1,val=19] tag[tag] qid[qid]" +msg Trename = "size[4,val=end-&size] typ[1,val=20] tag[tag] fid[fid] dfid[fid] name[s]" +msg Rrename = "size[4,val=end-&size] typ[1,val=21] tag[tag]" +msg Treadlink = "size[4,val=end-&size] typ[1,val=22] tag[tag] fid[fid]" +msg Rreadlink = "size[4,val=end-&size] typ[1,val=23] tag[tag] target[s]" +msg Tgetattr = "size[4,val=end-&size] typ[1,val=24] tag[tag] fid[fid] request_mask[getattr]" +msg Rgetattr = "size[4,val=end-&size] typ[1,val=25] tag[tag] valid[getattr] qid[qid] mode[mode] uid[nuid] gid[nuid] nlink[8]" + "rdev[8] filesize[8] blksize[8] blocks[8]" + "atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8]" + "ctime_sec[8] ctime_nsec[8] btime_sec[8] btime_nsec[8]" + "gen[8] data_version[8]" +msg Tsetattr = "size[4,val=end-&size] typ[1,val=26] tag[tag] fid[fid] valid[setattr] mode[mode] uid[nuid] gid[nuid] filesize[8] atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8]" +msg Rsetattr = "size[4,val=end-&size] typ[1,val=27] tag[tag]" +#... +msg Txattrwalk = "size[4,val=end-&size] typ[1,val=30] tag[tag] fid[fid] newfid[fid] name[s]" +msg Rxattrwalk = "size[4,val=end-&size] typ[1,val=31] tag[tag] attr_size[8]" +msg Txattrcreate = "size[4,val=end-&size] typ[1,val=32] tag[tag] fid[fid] name[s] attr_size[8] flags[4]" +msg Rxattrcreate = "size[4,val=end-&size] typ[1,val=33] tag[tag]" +#... +msg Treaddir = "size[4,val=end-&size] typ[1,val=40] tag[tag] fid[fid] offset[8] count[4]" +msg Rreaddir = "size[4,val=end-&size] typ[1,val=41] tag[tag] count[4] count*(data[1])" # data is "qid[qid] offset[8] type[dt] name[s]" +#... +msg Tfsync = "size[4,val=end-&size] typ[1,val=50] tag[tag] fid[fid] datasync[b4]" +msg Rfsync = "size[4,val=end-&size] typ[1,val=51] tag[tag]" +msg Tlock = "size[4,val=end-&size] typ[1,val=52] tag[tag] fid[fid] type[lock_type] flags[lock_flags] start[8] length[8] proc_id[4] client_id[s]" +msg Rlock = "size[4,val=end-&size] typ[1,val=53] tag[tag] status[lock_status]" +msg Tgetlock = "size[4,val=end-&size] typ[1,val=54] tag[tag] fid[fid] type[lock_type] start[8] length[8] proc_id[4] client_id[s]" +msg Rgetlock = "size[4,val=end-&size] typ[1,val=55] tag[tag] type[lock_type] start[8] length[8] proc_id[4] client_id[s]" +# ... +msg Tlink = "size[4,val=end-&size] typ[1,val=70] tag[tag] dfid[fid] fid[fid] name[s]" +msg Rlink = "size[4,val=end-&size] typ[1,val=71] tag[tag]" +msg Tmkdir = "size[4,val=end-&size] typ[1,val=72] tag[tag] dfid[fid] name[s] mode[mode] gid[nuid]" +msg Rmkdir = "size[4,val=end-&size] typ[1,val=73] tag[tag] qid[qid]" +msg Trenameat = "size[4,val=end-&size] typ[1,val=74] tag[tag] olddirfid[fid] oldname[s] newdirfid[fid] newname[s]" +msg Rrenameat = "size[4,val=end-&size] typ[1,val=75] tag[tag]" +msg Tunlinkat = "size[4,val=end-&size] typ[1,val=76] tag[tag] dirfd[fid] name[s] flags[4]" +msg Runlinkat = "size[4,val=end-&size] typ[1,val=77] tag[tag]" diff --git a/lib9p/idl/2010-9P2000.L.9p.wip b/lib9p/idl/2010-9P2000.L.9p.wip deleted file mode 100644 index 5261f7e..0000000 --- a/lib9p/idl/2010-9P2000.L.9p.wip +++ /dev/null @@ -1,56 +0,0 @@ -# 2010-9P2000.L.9p - Definitions of 9P2000.L messages -# -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -# "9P2000.L" Linux extension -# https://github.com/chaos/diod/blob/master/protocol.md -version "9P2000.L" - -from ./2002-9P2000.9p import * -from ./2005-9P2000.u.9p import Tauth, Tattach - -#msg Tlerror = "size[4,val=end-&size] typ[1,val=6] tag[tag] illegal" # analogous to 106/Terror -msg Rlerror = "size[4,val=end-&size] typ[1,val=7] tag[tag] ecode[4]" # analogous to 107/Rerror -msg Tstatfs = "size[4,val=end-&size] typ[1,val=8] tag[tag] TODO" -msg Rstatfs = "size[4,val=end-&size] typ[1,val=9] tag[tag] TODO" -msg Tlopen = "size[4,val=end-&size] typ[1,val=12] tag[tag] TODO" # analogous to 112/Topen -msg Rlopen = "size[4,val=end-&size] typ[1,val=13] tag[tag] TODO" # analogous to 113/Ropen -msg Tlcreate = "size[4,val=end-&size] typ[1,val=14] tag[tag] TODO" # analogous to 114/Tcreate -msg Rlcreate = "size[4,val=end-&size] typ[1,val=15] tag[tag] TODO" # analogous to 115/Rcreate -msg Tsymlink = "size[4,val=end-&size] typ[1,val=16] tag[tag] TODO" -msg Rsymlink = "size[4,val=end-&size] typ[1,val=17] tag[tag] TODO" -msg Tmknod = "size[4,val=end-&size] typ[1,val=18] tag[tag] TODO" -msg Rmknod = "size[4,val=end-&size] typ[1,val=19] tag[tag] TODO" -msg Trename = "size[4,val=end-&size] typ[1,val=20] tag[tag] TODO" -msg Rrename = "size[4,val=end-&size] typ[1,val=21] tag[tag] TODO" -msg Treadlink = "size[4,val=end-&size] typ[1,val=22] tag[tag] TODO" -msg Rreadlink = "size[4,val=end-&size] typ[1,val=23] tag[tag] TODO" -msg Tgetattr = "size[4,val=end-&size] typ[1,val=24] tag[tag] TODO" -msg Rgetattr = "size[4,val=end-&size] typ[1,val=25] tag[tag] TODO" -msg Tsetattr = "size[4,val=end-&size] typ[1,val=26] tag[tag] TODO" -msg Rsetattr = "size[4,val=end-&size] typ[1,val=27] tag[tag] TODO" -#... -msg Txattrwalk = "size[4,val=end-&size] typ[1,val=30] tag[tag] TODO" -msg Rxattrwalk = "size[4,val=end-&size] typ[1,val=31] tag[tag] TODO" -msg Txattrcreate = "size[4,val=end-&size] typ[1,val=32] tag[tag] TODO" -msg Rxattrcreate = "size[4,val=end-&size] typ[1,val=33] tag[tag] TODO" -#... -msg Treaddir = "size[4,val=end-&size] typ[1,val=40] tag[tag] TODO" -msg Rreaddir = "size[4,val=end-&size] typ[1,val=41] tag[tag] TODO" -#... -msg Tfsync = "size[4,val=end-&size] typ[1,val=50] tag[tag] TODO" -msg Rfsync = "size[4,val=end-&size] typ[1,val=51] tag[tag] TODO" -msg Tlock = "size[4,val=end-&size] typ[1,val=52] tag[tag] TODO" -msg Rlock = "size[4,val=end-&size] typ[1,val=53] tag[tag] TODO" -msg Tgetlock = "size[4,val=end-&size] typ[1,val=54] tag[tag] TODO" -msg Rgetlock = "size[4,val=end-&size] typ[1,val=55] tag[tag] TODO" -# ... -msg Tlink = "size[4,val=end-&size] typ[1,val=70] tag[tag] TODO" -msg Rlink = "size[4,val=end-&size] typ[1,val=71] tag[tag] TODO" -msg Tmkdir = "size[4,val=end-&size] typ[1,val=72] tag[tag] TODO" -msg Tmkdir = "size[4,val=end-&size] typ[1,val=73] tag[tag] TODO" -msg Trenameat = "size[4,val=end-&size] typ[1,val=74] tag[tag] TODO" -msg Rrenameat = "size[4,val=end-&size] typ[1,val=75] tag[tag] TODO" -msg Tunlinkat = "size[4,val=end-&size] typ[1,val=76] tag[tag] TODO" -msg Runlinkat = "size[4,val=end-&size] typ[1,val=77] tag[tag] TODO" diff --git a/lib9p/idl/2012-9P2000.e.9p b/lib9p/idl/2012-9P2000.e.9p index 27db50f..dde9d96 100644 --- a/lib9p/idl/2012-9P2000.e.9p +++ b/lib9p/idl/2012-9P2000.e.9p @@ -1,6 +1,6 @@ -# 2012-9P2000.e.9p - Definitions of 9P2000.e messages +# lib9p/idl/2012-9P2000.e.9p - Definitions of 9P2000.e messages # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later # "9P2000.e" Erlang extension @@ -13,6 +13,6 @@ from ./2002-9P2000.9p import * msg Tsession = "size[4,val=end-&size] typ[1,val=150] tag[tag] key[8]" msg Rsession = "size[4,val=end-&size] typ[1,val=151] tag[tag]" msg Tsread = "size[4,val=end-&size] typ[1,val=152] tag[tag] fid[4] nwname[2] nwname*(wname[s])" -msg Rsread = "size[4,val=end-&size] typ[1,val=153] tag[tag] data[d]" -msg Tswrite = "size[4,val=end-&size] typ[1,val=154] tag[tag] fid[4] nwname[2] nwname*(wname[s]) data[d]" +msg Rsread = "size[4,val=end-&size] typ[1,val=153] tag[tag] count[4] count*(data[1])" +msg Tswrite = "size[4,val=end-&size] typ[1,val=154] tag[tag] fid[4] nwname[2] nwname*(wname[s]) count[4] count*(data[1])" msg Rswrite = "size[4,val=end-&size] typ[1,val=155] tag[tag] count[4]" diff --git a/lib9p/idl/__init__.py b/lib9p/idl/__init__.py new file mode 100644 index 0000000..2d09217 --- /dev/null +++ b/lib9p/idl/__init__.py @@ -0,0 +1,878 @@ +# lib9p/idl/__init__.py - A parser for .9p specification files. +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import enum +import os.path +import re +import typing + +# pylint: disable=unused-variable +__all__ = [ + # entrypoint + "Parser", + # types + "Type", + "Primitive", + *["Expr", "ExprTok", "ExprOp", "ExprLit", "ExprSym", "ExprOff", "ExprNum"], + "Number", + *["Bitfield", "Bit", "BitCat", "BitNum", "BitAlias"], + *["Struct", "StructMember"], + "Message", +] + +# The syntax that this parses is described in `./0000-README.md`. + +# Utilities #################################################################### + + +def get_type(env: dict[str, "Type"], name: str, tc: type["T"]) -> "T": + if name not in env: + raise NameError(f"Unknown type {name!r}") + ret = env[name] + if (not isinstance(ret, tc)) or (ret.__class__.__name__ != tc.__name__): + raise NameError(f"Type {ret.typname!r} is not a {tc.__name__}") + return ret + + +# Types ######################################################################## + + +class Primitive(enum.Enum): + u8 = 1 + u16 = 2 + u32 = 4 + u64 = 8 + + @property + def in_versions(self) -> set[str]: + return set() + + @property + def typname(self) -> str: + return str(self.value) + + @property + def static_size(self) -> int: + return self.value + + def min_size(self, version: str) -> int: + return self.value + + def max_size(self, version: str) -> int: + return self.value + + +class ExprOp: + op: typing.Literal["-", "+", "<<"] + + def __init__(self, op: typing.Literal["-", "+", "<<"]) -> None: + self.op = op + + +class ExprLit: + val: int + + def __init__(self, val: int) -> None: + self.val = val + + +class ExprSym: + symname: str + + def __init__(self, name: str) -> None: + self.symname = name + + +class ExprOff: + membname: str + + def __init__(self, name: str) -> None: + self.membname = name + + +class ExprNum: + numname: str + valname: str + + def __init__(self, numname: str, valname: str) -> None: + self.numname = numname + self.valname = valname + + +type ExprTok = ExprOp | ExprLit | ExprSym | ExprOff | ExprNum + + +class Expr: + tokens: typing.Sequence[ExprTok] + const: int | None + + def __init__( + self, env: dict[str, "Type"], tokens: typing.Sequence[ExprTok] = () + ) -> None: + self.tokens = tokens + self.const = self._const(env, tokens) + + def _const( + self, env: dict[str, "Type"], toks: typing.Sequence[ExprTok] + ) -> int | None: + if not toks: + return None + + def read_val() -> int | None: + nonlocal toks + assert toks + neg = False + match toks[0]: + case ExprOp(op="-"): + neg = True + toks = toks[1:] + assert not isinstance(toks[0], ExprOp) + val: int + match toks[0]: + case ExprLit(): + val = toks[0].val + case ExprSym(): + if m := re.fullmatch(r"^u(8|16|32|64)_max$", toks[0].symname): + n = int(m.group(1)) + val = (1 << n) - 1 + elif m := re.fullmatch(r"^s(8|16|32|64)_max$", toks[0].symname): + n = int(m.group(1)) + val = (1 << (n - 1)) - 1 + else: + return None + case ExprOff(): + return None + case ExprNum(): + num = get_type(env, toks[0].numname, Number) + if toks[0].valname not in num.vals: + raise NameError( + f"Type {toks[0].numname!r} does not have a value {toks[0].valname!r}" + ) + _val = num.vals[toks[0].valname].const + if _val is None: + return None + val = _val + toks = toks[1:] + return -val if neg else val + + ret = read_val() + if ret is None: + return None + while toks: + assert isinstance(toks[0], ExprOp) + op = toks[0].op + toks = toks[1:] + operand = read_val() + if operand is None: + return None + match op: + case "+": + ret = ret + operand + case "-": + ret = ret - operand + case "<<": + ret = ret << operand + return ret + + def __bool__(self) -> bool: + return len(self.tokens) > 0 + + +class Number: + typname: str + in_versions: set[str] + + prim: Primitive + + vals: dict[str, Expr] + + def __init__(self) -> None: + self.in_versions = set() + self.vals = {} + + @property + def static_size(self) -> int: + return self.prim.static_size + + def min_size(self, version: str) -> int: + return self.static_size + + def max_size(self, version: str) -> int: + return self.static_size + + +class BitAlias: + bitname: str + in_versions: set[str] + val: Expr + + def __init__(self, name: str, val: Expr) -> None: + if val.const is None: + raise ValueError(f"{name!r} value is not constant") + self.bitname = name + self.in_versions = set() + self.val = val + + +class BitNum: + numname: str + mask: int + vals: dict[str, BitAlias] + + def __init__(self, name: str) -> None: + self.numname = name + self.mask = 0 + self.vals = {} + + +type BitCat = typing.Literal["UNUSED", "USED", "RESERVED"] | BitNum + + +class Bit: + bitname: str + in_versions: set[str] + num: int + cat: BitCat + + def __init__(self, num: int) -> None: + self.bitname = "" + self.in_versions = set() + self.num = num + self.cat = "UNUSED" + + +class Bitfield: + typname: str + in_versions: set[str] + prim: Primitive + + bits: list[Bit] + nums: dict[str, BitNum] + masks: dict[str, BitAlias] + aliases: dict[str, BitAlias] + + names: set[str] + + def __init__(self, name: str, prim: Primitive) -> None: + self.typname = name + self.in_versions = set() + self.prim = prim + + self.bits = [Bit(i) for i in range(prim.static_size * 8)] + self.nums = {} + self.masks = {} + self.aliases = {} + + self.names = set() + + @property + def static_size(self) -> int: + return self.prim.static_size + + def min_size(self, version: str) -> int: + return self.static_size + + def max_size(self, version: str) -> int: + return self.static_size + + +class StructMember: + # from left-to-right when parsing + cnt: "StructMember| int | None" = None + membname: str + typ: "Type" + max: Expr + val: Expr + + in_versions: set[str] + + @property + def min_cnt(self) -> int: + assert self.cnt + if isinstance(self.cnt, int): + return self.cnt + if not isinstance(self.cnt.typ, Primitive): + raise ValueError( + f"list count must be an integer type: {self.cnt.membname!r}" + ) + if self.cnt.val: # TODO: allow this? + raise ValueError(f"list count may not have ,val=: {self.cnt.membname!r}") + return 0 + + @property + def max_cnt(self) -> int: + assert self.cnt + if isinstance(self.cnt, int): + return self.cnt + if not isinstance(self.cnt.typ, Primitive): + raise ValueError( + f"list count must be an integer type: {self.cnt.membname!r}" + ) + if self.cnt.val: # TODO: allow this? + raise ValueError(f"list count may not have ,val=: {self.cnt.membname!r}") + if self.cnt.max: + # TODO: be more flexible? + val = self.cnt.max.const + if val is None: + raise ValueError( + f"list count ,max= must be a constant value: {self.cnt.membname!r}" + ) + return val + return (1 << (self.cnt.typ.value * 8)) - 1 + + @property + def static_size(self) -> int | None: + if self.cnt: + return None + return self.typ.static_size + + def min_size(self, version: str) -> int: + cnt = self.min_cnt if self.cnt else 1 + return cnt * self.typ.min_size(version) + + def max_size(self, version: str) -> int: + cnt = self.max_cnt if self.cnt else 1 + return cnt * self.typ.max_size(version) + + +class Struct: + typname: str + in_versions: set[str] + + members: list[StructMember] + + def __init__(self) -> None: + self.in_versions = set() + + @property + def static_size(self) -> int | None: + size = 0 + for member in self.members: + if member.in_versions < self.in_versions: + return None + msize = member.static_size + if msize is None: + return None + size += msize + return size + + def min_size(self, version: str) -> int: + return sum( + member.min_size(version) + for member in self.members + if (version in member.in_versions) + ) + + def max_size(self, version: str) -> int: + return sum( + member.max_size(version) + for member in self.members + if (version in member.in_versions) + ) + + +class Message(Struct): + @property + def msgid(self) -> int: + assert len(self.members) >= 3 + assert self.members[1].membname == "typ" + assert self.members[1].static_size == 1 + assert self.members[1].val + assert len(self.members[1].val.tokens) == 1 + assert isinstance(self.members[1].val.tokens[0], ExprLit) + return self.members[1].val.tokens[0].val + + +type Type = Primitive | Number | Bitfield | Struct | Message +type UserType = Number | Bitfield | Struct | Message +T = typing.TypeVar("T", Number, Bitfield, Struct, Message) + +# Parse ######################################################################## + +# common elements ###################### + +re_priname = "(?:1|2|4|8)" # primitive names +re_symname = "(?:[a-zA-Z_][a-zA-Z_0-9]*)" # "symbol" names; most *.9p-defined names +re_symname_u = "(?:[A-Z_][A-Z_0-9]*)" # upper-case "symbol" names; bit names +re_symname_l = "(?:[a-z_][a-z_0-9]*)" # lower-case "symbol" names; bit names +re_impname = r"(?:\*|" + re_symname + ")" # names we can import +re_msgname = r"(?:[TR][a-zA-Z_0-9]*)" # names a message can be + +re_memtype = f"(?:{re_symname}|{re_priname})" # typenames that a struct member can be + +valid_syms = [ + "end", + "u8_max", + "u16_max", + "u32_max", + "u64_max", + "s8_max", + "s16_max", + "s32_max", + "s64_max", +] + +_re_expr_op = r"(?:-|\+|<<)" + +_res_expr_val = { + "lit_2": r"0b[01]+", + "lit_8": r"0[0-7]+", + "lit_10": r"0(?![0-9bxX])|[1-9][0-9]*", + "lit_16": r"0[xX][0-9a-fA-F]+", + "sym": "|".join(valid_syms), # pre-defined symbols + "off": f"&{re_symname}", # offset of a field this struct + "num": f"{re_symname}\\.{re_symname}", # `num` values +} + +re_expr_tok = ( + "(?:" + + "|".join( + [ + f"(?P<op>{_re_expr_op})", + *[f"(?P<{k}>{v})" for k, v in _res_expr_val.items()], + ] + ) + + ")" +) + +_re_expr_val = "(?:" + "|".join(_res_expr_val.values()) + ")" + +re_expr = f"(?:\\s*(?:-\\s*)?{_re_expr_val}\\s*(?:{_re_expr_op}\\s*(?:-\\s*)?{_re_expr_val}\\s*)*)" + + +def parse_expr(env: dict[str, Type], expr: str) -> Expr: + assert re.fullmatch(re_expr, expr) + tokens: list[ExprTok] = [] + for m in re.finditer(re_expr_tok, expr): + if tok := m.group("op"): + tokens.append(ExprOp(typing.cast(typing.Literal["-", "+", "<<"], tok))) + elif tok := m.group("lit_2"): + tokens.append(ExprLit(int(tok[2:], 2))) + elif tok := m.group("lit_8"): + tokens.append(ExprLit(int(tok[1:], 8))) + elif tok := m.group("lit_10"): + tokens.append(ExprLit(int(tok, 10))) + elif tok := m.group("lit_16"): + tokens.append(ExprLit(int(tok[2:], 16))) + elif tok := m.group("sym"): + tokens.append(ExprSym(tok)) + elif tok := m.group("off"): + tokens.append(ExprOff(tok[1:])) + elif tok := m.group("num"): + [numname, valname] = tok.split(".", 1) + tokens.append(ExprNum(numname, valname)) + else: + assert False + return Expr(env, tokens) + + +# numspec ############################## + +re_numspec = f"(?P<name>{re_symname})\\s*=\\s*(?P<val>{re_expr})" + + +def parse_numspec(env: dict[str, Type], ver: str, n: Number, spec: str) -> None: + spec = spec.strip() + + if m := re.fullmatch(re_numspec, spec): + name = m.group("name") + if name in n.vals: + raise ValueError(f"{n.typname}: name {name!r} already assigned") + val = parse_expr(env, m.group("val")) + if val is None: + raise ValueError( + f"{n.typname}: {name!r} value is not constant: {m.group('val')!r}" + ) + n.vals[name] = val + else: + raise SyntaxError(f"invalid num spec {spec!r}") + + +# bitspec ############################## + +re_bitspec_bit = ( + "bit\\s+(?P<bitnum>[0-9]+)\\s*=\\s*(?:" + + "|".join( + [ + f"(?P<name_used>{re_symname_u})", + f"reserved\\((?P<name_reserved>{re_symname_u})\\)", + f"num\\((?P<name_num>{re_symname_u})\\)", + ] + ) + + ")" +) +re_bitspec_mask = f"mask\\s+(?P<name>{re_symname_u})\\s*=\\s*(?P<val>{re_expr})" +re_bitspec_alias = f"alias\\s+(?P<name>{re_symname_u})\\s*=\\s*(?P<val>{re_expr})" +re_bitspec_num = f"num\\((?P<num>{re_symname_u})\\)\\s+(?P<name>{re_symname_u})\\s*=\\s*(?P<val>{re_expr})" + + +def parse_bitspec(env: dict[str, Type], ver: str, bf: Bitfield, spec: str) -> None: + spec = spec.strip() + + def check_name(name: str, is_num: bool = False) -> None: + if name == "MASK": + raise ValueError(f"{bf.typname}: bit name may not be {'MASK'!r}: {name!r}") + if name.endswith("_MASK"): + raise ValueError( + f"{bf.typname}: bit name may not end with {'_MASK'!r}: {name!r}" + ) + if name in bf.names and not (is_num and name in bf.nums): + raise ValueError(f"{bf.typname}: bit name already assigned: {name!r}") + + if m := re.fullmatch(re_bitspec_bit, spec): + bitnum = int(m.group("bitnum")) + if bitnum < 0 or bitnum >= len(bf.bits): + raise ValueError(f"{bf.typname}: bit num {bitnum} out-of-bounds") + bit = bf.bits[bitnum] + if bit.cat != "UNUSED": + raise ValueError(f"{bf.typname}: bit num {bitnum} already assigned") + if name := m.group("name_used"): + bit.bitname = name + bit.cat = "USED" + bit.in_versions.add(ver) + elif name := m.group("name_reserved"): + bit.bitname = name + bit.cat = "RESERVED" + bit.in_versions.add(ver) + elif name := m.group("name_num"): + bit.bitname = name + if name not in bf.nums: + bf.nums[name] = BitNum(name) + bf.nums[name].mask |= 1 << bit.num + bit.cat = bf.nums[name] + bit.in_versions.add(ver) + if bit.bitname: + check_name(name, isinstance(bit.cat, BitNum)) + bf.names.add(bit.bitname) + elif m := re.fullmatch(re_bitspec_mask, spec): + mask = BitAlias(m.group("name"), parse_expr(env, m.group("val"))) + mask.in_versions.add(ver) + check_name(mask.bitname) + bf.masks[mask.bitname] = mask + bf.names.add(mask.bitname) + elif m := re.fullmatch(re_bitspec_alias, spec): + alias = BitAlias(m.group("name"), parse_expr(env, m.group("val"))) + alias.in_versions.add(ver) + check_name(alias.bitname) + bf.aliases[alias.bitname] = alias + bf.names.add(alias.bitname) + elif m := re.fullmatch(re_bitspec_num, spec): + numname = m.group("num") + alias = BitAlias(m.group("name"), parse_expr(env, m.group("val"))) + alias.in_versions.add(ver) + check_name(alias.bitname) + if numname not in bf.nums: + raise NameError( + f"{bf.typname}: nested num not allocated any bits: {numname!r}" + ) + assert alias.val.const is not None + if alias.val.const & ~bf.nums[numname].mask: + raise ValueError( + f"{bf.typname}: {alias.bitname!r} does not fit within bitmask: val={alias.val.const:b} mask={bf.nums[numname].mask}" + ) + bf.nums[numname].vals[alias.bitname] = alias + bf.names.add(alias.bitname) + else: + raise SyntaxError(f"invalid bitfield spec {spec!r}") + + +# struct members ####################### + + +re_memberspec = f"(?:(?P<cnt>{re_symname}|[1-9][0-9]*)\\*\\()?(?P<name>{re_symname})\\[(?P<typ>{re_memtype})(?:,max=(?P<max>{re_expr})|,val=(?P<val>{re_expr}))*\\]\\)?" + + +def parse_members(ver: str, env: dict[str, Type], struct: Struct, specs: str) -> None: + for spec in specs.split(): + m = re.fullmatch(re_memberspec, spec) + if not m: + raise SyntaxError(f"invalid member spec {spec!r}") + + member = StructMember() + member.in_versions = {ver} + + member.membname = m.group("name") + if any(x.membname == member.membname for x in struct.members): + raise ValueError(f"duplicate member name {member.membname!r}") + + if m.group("typ") not in env: + raise NameError(f"Unknown type {m.group('typ')!r}") + member.typ = env[m.group("typ")] + + if cnt := m.group("cnt"): + if cnt.isnumeric(): + member.cnt = int(cnt) + else: + if len(struct.members) == 0 or struct.members[-1].membname != cnt: + raise ValueError(f"list count must be previous item: {cnt!r}") + member.cnt = struct.members[-1] + _ = member.max_cnt # force validation + + if maxstr := m.group("max"): + if ( + not isinstance(member.typ, Primitive) + and not isinstance(member.typ, Number) + ) or member.cnt: + raise ValueError( + "',max=' may only be specified on a non-repeated numeric type" + ) + member.max = parse_expr(env, maxstr) + else: + member.max = Expr(env) + + if valstr := m.group("val"): + if ( + not isinstance(member.typ, Primitive) + and not isinstance(member.typ, Number) + ) or member.cnt: + raise ValueError( + "',val=' may only be specified on a non-repeated numeric type" + ) + member.val = parse_expr(env, valstr) + else: + member.val = Expr(env) + + struct.members += [member] + + +# main parser ########################## + + +def re_string(grpname: str) -> str: + return f'"(?P<{grpname}>[^"]*)"' + + +re_line_version = f"version\\s+{re_string('version')}" +re_line_import = f"from\\s+(?P<file>\\S+)\\s+import\\s+(?P<syms>{re_impname}(?:\\s*,\\s*{re_impname})*)" +re_line_num = f"num\\s+(?P<name>{re_symname})\\s*=\\s*(?P<prim>{re_priname})" +re_line_bitfield = f"bitfield\\s+(?P<name>{re_symname})\\s*=\\s*(?P<prim>{re_priname})" +re_line_bitfield_ = ( + f"bitfield\\s+(?P<name>{re_symname})\\s*\\+=\\s*{re_string('member')}" +) +re_line_struct = ( + f"struct\\s+(?P<name>{re_symname})\\s*(?P<op>\\+?=)\\s*{re_string('members')}" +) +re_line_msg = ( + f"msg\\s+(?P<name>{re_msgname})\\s*(?P<op>\\+?=)\\s*{re_string('members')}" +) +re_line_cont = f"\\s+{re_string('specs')}" # could be bitfield/struct/msg + + +def parse_file( + filename: str, get_include: typing.Callable[[str], tuple[str, list[UserType]]] +) -> tuple[str, list[UserType]]: + version: str | None = None + env: dict[str, Type] = { + "1": Primitive.u8, + "2": Primitive.u16, + "4": Primitive.u32, + "8": Primitive.u64, + } + + with open(filename, "r", encoding="utf-8") as fh: + prev: Type | None = None + for lineno, line in enumerate(fh): + try: + line = line.split("#", 1)[0].rstrip() + if not line: + continue + if m := re.fullmatch(re_line_version, line): + if version: + raise SyntaxError("must have exactly 1 version line") + version = m.group("version") + continue + if not version: + raise SyntaxError("must have exactly 1 version line") + + if m := re.fullmatch(re_line_import, line): + other_version, other_typs = get_include(m.group("file")) + for symname in m.group("syms").split(sep=","): + symname = symname.strip() + found = False + for typ in other_typs: + if symname in (typ.typname, "*"): + found = True + match typ: + case Primitive(): + pass + case Number(): + typ.in_versions.add(version) + case Bitfield(): + typ.in_versions.add(version) + for bf_bit in typ.bits: + if other_version in bf_bit.in_versions: + bf_bit.in_versions.add(version) + for bf_num in typ.nums.values(): + for bf_val in bf_num.vals.values(): + if other_version in bf_val.in_versions: + bf_val.in_versions.add(version) + for bf_mask in typ.masks.values(): + if other_version in bf_mask.in_versions: + bf_mask.in_versions.add(version) + for bf_alias in typ.aliases.values(): + if other_version in bf_alias.in_versions: + bf_alias.in_versions.add(version) + case Struct(): # and Message() + typ.in_versions.add(version) + for member in typ.members: + if other_version in member.in_versions: + member.in_versions.add(version) + if typ.typname in env and env[typ.typname] != typ: + raise ValueError( + f"duplicate type name {typ.typname!r}" + ) + env[typ.typname] = typ + if symname != "*" and not found: + raise ValueError( + f"import: {m.group('file')}: no symbol {symname!r}" + ) + elif m := re.fullmatch(re_line_num, line): + num = Number() + num.typname = m.group("name") + num.in_versions.add(version) + + prim = env[m.group("prim")] + assert isinstance(prim, Primitive) + num.prim = prim + + if num.typname in env: + raise ValueError(f"duplicate type name {num.typname!r}") + env[num.typname] = num + prev = num + elif m := re.fullmatch(re_line_bitfield, line): + prim = env[m.group("prim")] + assert isinstance(prim, Primitive) + + bf = Bitfield(m.group("name"), prim) + bf.in_versions.add(version) + + if bf.typname in env: + raise ValueError(f"duplicate type name {bf.typname!r}") + env[bf.typname] = bf + prev = bf + elif m := re.fullmatch(re_line_bitfield_, line): + bf = get_type(env, m.group("name"), Bitfield) + parse_bitspec(env, version, bf, m.group("member")) + + prev = bf + elif m := re.fullmatch(re_line_struct, line): + match m.group("op"): + case "=": + struct = Struct() + struct.typname = m.group("name") + struct.in_versions.add(version) + struct.members = [] + parse_members(version, env, struct, m.group("members")) + + if struct.typname in env: + raise ValueError( + f"duplicate type name {struct.typname!r}" + ) + env[struct.typname] = struct + prev = struct + case "+=": + struct = get_type(env, m.group("name"), Struct) + parse_members(version, env, struct, m.group("members")) + + prev = struct + elif m := re.fullmatch(re_line_msg, line): + match m.group("op"): + case "=": + msg = Message() + msg.typname = m.group("name") + msg.in_versions.add(version) + msg.members = [] + parse_members(version, env, msg, m.group("members")) + + if msg.typname in env: + raise ValueError(f"duplicate type name {msg.typname!r}") + env[msg.typname] = msg + prev = msg + case "+=": + msg = get_type(env, m.group("name"), Message) + parse_members(version, env, msg, m.group("members")) + + prev = msg + elif m := re.fullmatch(re_line_cont, line): + match prev: + case Bitfield(): + parse_bitspec(env, version, prev, m.group("specs")) + case Number(): + parse_numspec(env, version, prev, m.group("specs")) + case Struct(): # and Message() + parse_members(version, env, prev, m.group("specs")) + case _: + raise SyntaxError( + "continuation line must come after a bitfield, struct, or msg line" + ) + else: + raise SyntaxError("invalid line") + except (SyntaxError, NameError, ValueError) as e: + e2 = SyntaxError(str(e)) + e2.filename = filename + e2.lineno = lineno + 1 + e2.text = line + raise e2 from e + if not version: + raise SyntaxError("must have exactly 1 version line") + + typs: list[UserType] = [x for x in env.values() if not isinstance(x, Primitive)] + + for typ in [typ for typ in typs if isinstance(typ, Struct)]: + for member in typ.members: + if ( + not isinstance(member.typ, Primitive) + and member.typ.in_versions < member.in_versions + ): + raise ValueError( + f"{typ.typname}.{member.membname}: type {member.typ.typname} does not exist in {member.in_versions.difference(member.typ.in_versions)}" + ) + for tok in [*member.max.tokens, *member.val.tokens]: + if isinstance(tok, ExprOff) and not any( + m.membname == tok.membname for m in typ.members + ): + raise NameError( + f"{typ.typname}.{member.membname}: invalid offset: &{tok.membname}" + ) + + return version, typs + + +# Filesystem ################################################################### + + +class Parser: + cache: dict[str, tuple[str, list[UserType]]] = {} + + def parse_file(self, filename: str) -> tuple[str, list[UserType]]: + filename = os.path.normpath(filename) + if filename not in self.cache: + + def get_include(other_filename: str) -> tuple[str, list[UserType]]: + return self.parse_file(os.path.join(filename, "..", other_filename)) + + self.cache[filename] = parse_file(filename, get_include) + return self.cache[filename] + + def all(self) -> tuple[set[str], list[UserType]]: + ret_versions: set[str] = set() + ret_typs: dict[str, UserType] = {} + for version, typs in self.cache.values(): + if version in ret_versions: + raise ValueError(f"duplicate protocol version {version!r}") + ret_versions.add(version) + for typ in typs: + if typ.typname in ret_typs: + if typ != ret_typs[typ.typname]: + raise ValueError(f"duplicate type name {typ.typname!r}") + else: + ret_typs[typ.typname] = typ + msgids: set[int] = set() + for typ in ret_typs.values(): + if isinstance(typ, Message): + if typ.msgid in msgids: + raise ValueError(f"duplicate msgid {typ.msgid!r}") + msgids.add(typ.msgid) + return ret_versions, list(ret_typs.values()) diff --git a/lib9p/include/lib9p/9p.generated.h b/lib9p/include/lib9p/9p.generated.h index 25aacfe..7e901a3 100644 --- a/lib9p/include/lib9p/9p.generated.h +++ b/lib9p/include/lib9p/9p.generated.h @@ -1,4 +1,4 @@ -/* Generated by `lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */ +/* Generated by `lib9p/proto.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2003-9P2000.p9p.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2010-9P2000.L.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */ #ifndef _LIB9P_9P_H_ #error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead @@ -6,6 +6,9 @@ #include <stdint.h> /* for uint{n}_t types */ +#include <libfmt/fmt.h> /* for fmt_formatter */ +#include <libhw/generic/net.h> /* for struct iovec */ + /* config *********************************************************************/ #include "config.h" @@ -14,388 +17,1332 @@ #error config.h must define CONFIG_9P_ENABLE_9P2000 #endif +#ifndef CONFIG_9P_ENABLE_9P2000_L + #error config.h must define CONFIG_9P_ENABLE_9P2000_L +#endif + #ifndef CONFIG_9P_ENABLE_9P2000_e #error config.h must define CONFIG_9P_ENABLE_9P2000_e +#else + #if CONFIG_9P_ENABLE_9P2000_e + #ifndef CONFIG_9P_MAX_9P2000_e_WELEM + #error if CONFIG_9P_ENABLE_9P2000_e then config.h must define CONFIG_9P_MAX_9P2000_e_WELEM + #endif + static_assert(CONFIG_9P_MAX_9P2000_e_WELEM > 0); + #endif +#endif + +#ifndef CONFIG_9P_ENABLE_9P2000_p9p + #error config.h must define CONFIG_9P_ENABLE_9P2000_p9p #endif #ifndef CONFIG_9P_ENABLE_9P2000_u #error config.h must define CONFIG_9P_ENABLE_9P2000_u #endif -/* versions *******************************************************************/ +/* enum version ***************************************************************/ enum lib9p_version { LIB9P_VER_unknown = 0, /* "unknown" */ #if CONFIG_9P_ENABLE_9P2000 LIB9P_VER_9P2000, /* "9P2000" */ #endif /* CONFIG_9P_ENABLE_9P2000 */ +#if CONFIG_9P_ENABLE_9P2000_L + LIB9P_VER_9P2000_L, /* "9P2000.L" */ +#endif /* CONFIG_9P_ENABLE_9P2000_L */ #if CONFIG_9P_ENABLE_9P2000_e LIB9P_VER_9P2000_e, /* "9P2000.e" */ #endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000_p9p + LIB9P_VER_9P2000_p9p, /* "9P2000.p9p" */ +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ #if CONFIG_9P_ENABLE_9P2000_u LIB9P_VER_9P2000_u, /* "9P2000.u" */ #endif /* CONFIG_9P_ENABLE_9P2000_u */ LIB9P_VER_NUM, }; +LO_IMPLEMENTATION_H(fmt_formatter, enum lib9p_version, lib9p_version); -const char *lib9p_version_str(enum lib9p_version); +/* enum msg_type **************************************************************/ -/* non-message types **********************************************************/ +enum lib9p_msg_type { /* uint8_t */ +#if CONFIG_9P_ENABLE_9P2000_L + LIB9P_TYP_Rlerror = 7, + LIB9P_TYP_Tstatfs = 8, + LIB9P_TYP_Rstatfs = 9, + LIB9P_TYP_Tlopen = 12, + LIB9P_TYP_Rlopen = 13, + LIB9P_TYP_Tlcreate = 14, + LIB9P_TYP_Rlcreate = 15, + LIB9P_TYP_Tsymlink = 16, + LIB9P_TYP_Rsymlink = 17, + LIB9P_TYP_Tmknod = 18, + LIB9P_TYP_Rmknod = 19, + LIB9P_TYP_Trename = 20, + LIB9P_TYP_Rrename = 21, + LIB9P_TYP_Treadlink = 22, + LIB9P_TYP_Rreadlink = 23, + LIB9P_TYP_Tgetattr = 24, + LIB9P_TYP_Rgetattr = 25, + LIB9P_TYP_Tsetattr = 26, + LIB9P_TYP_Rsetattr = 27, + LIB9P_TYP_Txattrwalk = 30, + LIB9P_TYP_Rxattrwalk = 31, + LIB9P_TYP_Txattrcreate = 32, + LIB9P_TYP_Rxattrcreate = 33, + LIB9P_TYP_Treaddir = 40, + LIB9P_TYP_Rreaddir = 41, + LIB9P_TYP_Tfsync = 50, + LIB9P_TYP_Rfsync = 51, + LIB9P_TYP_Tlock = 52, + LIB9P_TYP_Rlock = 53, + LIB9P_TYP_Tgetlock = 54, + LIB9P_TYP_Rgetlock = 55, + LIB9P_TYP_Tlink = 70, + LIB9P_TYP_Rlink = 71, + LIB9P_TYP_Tmkdir = 72, + LIB9P_TYP_Rmkdir = 73, + LIB9P_TYP_Trenameat = 74, + LIB9P_TYP_Rrenameat = 75, + LIB9P_TYP_Tunlinkat = 76, + LIB9P_TYP_Runlinkat = 77, +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_p9p + LIB9P_TYP_Topenfd = 98, + LIB9P_TYP_Ropenfd = 99, +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u + LIB9P_TYP_Tversion = 100, + LIB9P_TYP_Rversion = 101, + LIB9P_TYP_Tauth = 102, + LIB9P_TYP_Rauth = 103, + LIB9P_TYP_Tattach = 104, + LIB9P_TYP_Rattach = 105, + LIB9P_TYP_Rerror = 107, + LIB9P_TYP_Tflush = 108, + LIB9P_TYP_Rflush = 109, + LIB9P_TYP_Twalk = 110, + LIB9P_TYP_Rwalk = 111, +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u + LIB9P_TYP_Topen = 112, + LIB9P_TYP_Ropen = 113, + LIB9P_TYP_Tcreate = 114, + LIB9P_TYP_Rcreate = 115, +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u + LIB9P_TYP_Tread = 116, + LIB9P_TYP_Rread = 117, + LIB9P_TYP_Twrite = 118, + LIB9P_TYP_Rwrite = 119, + LIB9P_TYP_Tclunk = 120, + LIB9P_TYP_Rclunk = 121, + LIB9P_TYP_Tremove = 122, + LIB9P_TYP_Rremove = 123, +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u + LIB9P_TYP_Tstat = 124, + LIB9P_TYP_Rstat = 125, + LIB9P_TYP_Twstat = 126, + LIB9P_TYP_Rwstat = 127, +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_e + LIB9P_TYP_Tsession = 150, + LIB9P_TYP_Rsession = 151, + LIB9P_TYP_Tsread = 152, + LIB9P_TYP_Rsread = 153, + LIB9P_TYP_Tswrite = 154, + LIB9P_TYP_Rswrite = 155, +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +}; +LO_IMPLEMENTATION_H(fmt_formatter, enum lib9p_msg_type, lib9p_msg_type); + +/* payload types **************************************************************/ -#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 2 ; max_iov = 1 ; max_copy = 2 */ typedef uint16_t lib9p_tag_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_tag_t, lib9p_tag); +#define LIB9P_TAG_NOTAG ((lib9p_tag_t)(UINT16_MAX)) +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ typedef uint32_t lib9p_fid_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_fid_t, lib9p_fid); +#define LIB9P_FID_NOFID ((lib9p_fid_t)(UINT32_MAX)) -struct lib9p_d { - uint32_t len; - char *dat; -}; - +/* min_size = 2 ; exp_size = 29 ; max_size = 65,537 ; max_iov = 2 ; max_copy = 2 */ struct lib9p_s { - uint16_t len; - char *utf8; + uint16_t len; + [[gnu::nonstring]] char *utf8; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_s, lib9p_s); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ typedef uint32_t lib9p_dm_t; - -#define LIB9P_DM_DIR ((lib9p_dm_t)(1<<31)) -#define LIB9P_DM_APPEND ((lib9p_dm_t)(1<<30)) -#define LIB9P_DM_EXCL ((lib9p_dm_t)(1<<29)) -#define _LIB9P_DM_PLAN9_MOUNT ((lib9p_dm_t)(1<<28)) -#define LIB9P_DM_AUTH ((lib9p_dm_t)(1<<27)) -#define LIB9P_DM_TMP ((lib9p_dm_t)(1<<26)) -/* unused ((lib9p_dm_t)(1<<25)) */ -/* unused ((lib9p_dm_t)(1<<24)) */ +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_dm_t, lib9p_dm); +/* bits */ +#define LIB9P_DM_DIR ((lib9p_dm_t)(UINT32_C(1)<<31)) +#define LIB9P_DM_APPEND ((lib9p_dm_t)(UINT32_C(1)<<30)) +#define LIB9P_DM_EXCL ((lib9p_dm_t)(UINT32_C(1)<<29)) +#define _LIB9P_DM_PLAN9_MOUNT ((lib9p_dm_t)(UINT32_C(1)<<28)) +#define LIB9P_DM_AUTH ((lib9p_dm_t)(UINT32_C(1)<<27)) +#define LIB9P_DM_TMP ((lib9p_dm_t)(UINT32_C(1)<<26)) +#define _LIB9P_DM_UNUSED_25 ((lib9p_dm_t)(UINT32_C(1)<<25)) +#define _LIB9P_DM_UNUSED_24 ((lib9p_dm_t)(UINT32_C(1)<<24)) #if CONFIG_9P_ENABLE_9P2000_u -# define LIB9P_DM_DEVICE ((lib9p_dm_t)(1<<23)) +# define LIB9P_DM_DEVICE ((lib9p_dm_t)(UINT32_C(1)<<23)) #endif /* CONFIG_9P_ENABLE_9P2000_u */ -/* unused ((lib9p_dm_t)(1<<22)) */ +#define _LIB9P_DM_UNUSED_22 ((lib9p_dm_t)(UINT32_C(1)<<22)) #if CONFIG_9P_ENABLE_9P2000_u -# define LIB9P_DM_NAMEDPIPE ((lib9p_dm_t)(1<<21)) -# define LIB9P_DM_SOCKET ((lib9p_dm_t)(1<<20)) -# define LIB9P_DM_SETUID ((lib9p_dm_t)(1<<19)) -# define LIB9P_DM_SETGID ((lib9p_dm_t)(1<<18)) +# define LIB9P_DM_PIPE ((lib9p_dm_t)(UINT32_C(1)<<21)) +# define LIB9P_DM_SOCKET ((lib9p_dm_t)(UINT32_C(1)<<20)) +# define LIB9P_DM_SETUID ((lib9p_dm_t)(UINT32_C(1)<<19)) +# define LIB9P_DM_SETGID ((lib9p_dm_t)(UINT32_C(1)<<18)) #endif /* CONFIG_9P_ENABLE_9P2000_u */ -/* unused ((lib9p_dm_t)(1<<17)) */ -/* unused ((lib9p_dm_t)(1<<16)) */ -/* unused ((lib9p_dm_t)(1<<15)) */ -/* unused ((lib9p_dm_t)(1<<14)) */ -/* unused ((lib9p_dm_t)(1<<13)) */ -/* unused ((lib9p_dm_t)(1<<12)) */ -/* unused ((lib9p_dm_t)(1<<11)) */ -/* unused ((lib9p_dm_t)(1<<10)) */ -/* unused ((lib9p_dm_t)(1<<9)) */ -#define LIB9P_DM_OWNER_R ((lib9p_dm_t)(1<<8)) -#define LIB9P_DM_OWNER_W ((lib9p_dm_t)(1<<7)) -#define LIB9P_DM_OWNER_X ((lib9p_dm_t)(1<<6)) -#define LIB9P_DM_GROUP_R ((lib9p_dm_t)(1<<5)) -#define LIB9P_DM_GROUP_W ((lib9p_dm_t)(1<<4)) -#define LIB9P_DM_GROUP_X ((lib9p_dm_t)(1<<3)) -#define LIB9P_DM_OTHER_R ((lib9p_dm_t)(1<<2)) -#define LIB9P_DM_OTHER_W ((lib9p_dm_t)(1<<1)) -#define LIB9P_DM_OTHER_X ((lib9p_dm_t)(1<<0)) - -#define LIB9P_DM_PERM_MASK ((lib9p_dm_t)(0777)) +#define _LIB9P_DM_UNUSED_17 ((lib9p_dm_t)(UINT32_C(1)<<17)) +#define _LIB9P_DM_UNUSED_16 ((lib9p_dm_t)(UINT32_C(1)<<16)) +#define _LIB9P_DM_UNUSED_15 ((lib9p_dm_t)(UINT32_C(1)<<15)) +#define _LIB9P_DM_UNUSED_14 ((lib9p_dm_t)(UINT32_C(1)<<14)) +#define _LIB9P_DM_UNUSED_13 ((lib9p_dm_t)(UINT32_C(1)<<13)) +#define _LIB9P_DM_UNUSED_12 ((lib9p_dm_t)(UINT32_C(1)<<12)) +#define _LIB9P_DM_UNUSED_11 ((lib9p_dm_t)(UINT32_C(1)<<11)) +#define _LIB9P_DM_UNUSED_10 ((lib9p_dm_t)(UINT32_C(1)<<10)) +#define _LIB9P_DM_UNUSED_9 ((lib9p_dm_t)(UINT32_C(1)<<9)) +#define LIB9P_DM_OWNER_R ((lib9p_dm_t)(UINT32_C(1)<<8)) +#define LIB9P_DM_OWNER_W ((lib9p_dm_t)(UINT32_C(1)<<7)) +#define LIB9P_DM_OWNER_X ((lib9p_dm_t)(UINT32_C(1)<<6)) +#define LIB9P_DM_GROUP_R ((lib9p_dm_t)(UINT32_C(1)<<5)) +#define LIB9P_DM_GROUP_W ((lib9p_dm_t)(UINT32_C(1)<<4)) +#define LIB9P_DM_GROUP_X ((lib9p_dm_t)(UINT32_C(1)<<3)) +#define LIB9P_DM_OTHER_R ((lib9p_dm_t)(UINT32_C(1)<<2)) +#define LIB9P_DM_OTHER_W ((lib9p_dm_t)(UINT32_C(1)<<1)) +#define LIB9P_DM_OTHER_X ((lib9p_dm_t)(UINT32_C(1)<<0)) +/* masks */ +#define LIB9P_DM_PERM_MASK ((lib9p_dm_t)(0b000000000000000000000111111111)) +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 1 ; max_iov = 1 ; max_copy = 1 */ typedef uint8_t lib9p_qt_t; - -#define LIB9P_QT_DIR ((lib9p_qt_t)(1<<7)) -#define LIB9P_QT_APPEND ((lib9p_qt_t)(1<<6)) -#define LIB9P_QT_EXCL ((lib9p_qt_t)(1<<5)) -#define _LIB9P_QT_PLAN9_MOUNT ((lib9p_qt_t)(1<<4)) -#define LIB9P_QT_AUTH ((lib9p_qt_t)(1<<3)) -#define LIB9P_QT_TMP ((lib9p_qt_t)(1<<2)) +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_qt_t, lib9p_qt); +/* bits */ +#define LIB9P_QT_DIR ((lib9p_qt_t)(UINT8_C(1)<<7)) +#define LIB9P_QT_APPEND ((lib9p_qt_t)(UINT8_C(1)<<6)) +#define LIB9P_QT_EXCL ((lib9p_qt_t)(UINT8_C(1)<<5)) +#define _LIB9P_QT_PLAN9_MOUNT ((lib9p_qt_t)(UINT8_C(1)<<4)) +#define LIB9P_QT_AUTH ((lib9p_qt_t)(UINT8_C(1)<<3)) +#define LIB9P_QT_TMP ((lib9p_qt_t)(UINT8_C(1)<<2)) #if CONFIG_9P_ENABLE_9P2000_u -# define LIB9P_QT_SYMLINK ((lib9p_qt_t)(1<<1)) +# define LIB9P_QT_SYMLINK ((lib9p_qt_t)(UINT8_C(1)<<1)) #endif /* CONFIG_9P_ENABLE_9P2000_u */ -/* unused ((lib9p_qt_t)(1<<0)) */ - +#define _LIB9P_QT_UNUSED_0 ((lib9p_qt_t)(UINT8_C(1)<<0)) +/* aliases */ #define LIB9P_QT_FILE ((lib9p_qt_t)(0)) -struct lib9p_qid { - lib9p_qt_t type; - uint32_t vers; - uint64_t path; +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ +typedef uint32_t lib9p_nuid_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_nuid_t, lib9p_nuid); +#define LIB9P_NUID_NONUID ((lib9p_nuid_t)(UINT32_MAX)) + +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 1 ; max_iov = 1 ; max_copy = 1 */ +typedef uint8_t lib9p_o_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_o_t, lib9p_o); +/* bits */ +#define _LIB9P_O_UNUSED_7 ((lib9p_o_t)(UINT8_C(1)<<7)) +#define LIB9P_O_RCLOSE ((lib9p_o_t)(UINT8_C(1)<<6)) +#define _LIB9P_O_RESERVED_CEXEC ((lib9p_o_t)(UINT8_C(1)<<5)) +#define LIB9P_O_TRUNC ((lib9p_o_t)(UINT8_C(1)<<4)) +#define _LIB9P_O_UNUSED_3 ((lib9p_o_t)(UINT8_C(1)<<3)) +#define _LIB9P_O_UNUSED_2 ((lib9p_o_t)(UINT8_C(1)<<2)) +/* number LIB9P_O_MODE_* ((lib9p_o_t)(UINT8_C(1)<<1)) */ +/* number LIB9P_O_MODE_* ((lib9p_o_t)(UINT8_C(1)<<0)) */ +/* masks */ +#define LIB9P_O_FLAG_MASK ((lib9p_o_t)(0b11111100)) +/* number: MODE */ +#define LIB9P_O_MODE_READ ((lib9p_o_t)(0)) +#define LIB9P_O_MODE_WRITE ((lib9p_o_t)(1)) +#define LIB9P_O_MODE_RDWR ((lib9p_o_t)(2)) +#define LIB9P_O_MODE_EXEC ((lib9p_o_t)(3)) +#define LIB9P_O_MODE_MASK ((lib9p_o_t)(0b000011)) + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ +typedef uint32_t lib9p_errno_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_errno_t, lib9p_errno); +#define LIB9P_ERRNO_NOERROR ((lib9p_errno_t)(0)) + +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ +typedef uint32_t lib9p_super_magic_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_super_magic_t, lib9p_super_magic); +#define LIB9P_SUPER_MAGIC_V9FS_MAGIC ((lib9p_super_magic_t)(16914839)) + +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ +typedef uint32_t lib9p_lo_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_lo_t, lib9p_lo); +/* bits */ +#define _LIB9P_LO_UNUSED_31 ((lib9p_lo_t)(UINT32_C(1)<<31)) +#define _LIB9P_LO_UNUSED_30 ((lib9p_lo_t)(UINT32_C(1)<<30)) +#define _LIB9P_LO_UNUSED_29 ((lib9p_lo_t)(UINT32_C(1)<<29)) +#define _LIB9P_LO_UNUSED_28 ((lib9p_lo_t)(UINT32_C(1)<<28)) +#define _LIB9P_LO_UNUSED_27 ((lib9p_lo_t)(UINT32_C(1)<<27)) +#define _LIB9P_LO_UNUSED_26 ((lib9p_lo_t)(UINT32_C(1)<<26)) +#define _LIB9P_LO_UNUSED_25 ((lib9p_lo_t)(UINT32_C(1)<<25)) +#define _LIB9P_LO_UNUSED_24 ((lib9p_lo_t)(UINT32_C(1)<<24)) +#define _LIB9P_LO_UNUSED_23 ((lib9p_lo_t)(UINT32_C(1)<<23)) +#define _LIB9P_LO_UNUSED_22 ((lib9p_lo_t)(UINT32_C(1)<<22)) +#define _LIB9P_LO_UNUSED_21 ((lib9p_lo_t)(UINT32_C(1)<<21)) +#define LIB9P_LO_SYNC ((lib9p_lo_t)(UINT32_C(1)<<20)) +#define LIB9P_LO_CLOEXEC ((lib9p_lo_t)(UINT32_C(1)<<19)) +#define LIB9P_LO_NOATIME ((lib9p_lo_t)(UINT32_C(1)<<18)) +#define LIB9P_LO_NOFOLLOW ((lib9p_lo_t)(UINT32_C(1)<<17)) +#define LIB9P_LO_DIRECTORY ((lib9p_lo_t)(UINT32_C(1)<<16)) +#define LIB9P_LO_LARGEFILE ((lib9p_lo_t)(UINT32_C(1)<<15)) +#define LIB9P_LO_DIRECT ((lib9p_lo_t)(UINT32_C(1)<<14)) +#define LIB9P_LO_BSD_FASYNC ((lib9p_lo_t)(UINT32_C(1)<<13)) +#define LIB9P_LO_DSYNC ((lib9p_lo_t)(UINT32_C(1)<<12)) +#define LIB9P_LO_NONBLOCK ((lib9p_lo_t)(UINT32_C(1)<<11)) +#define LIB9P_LO_APPEND ((lib9p_lo_t)(UINT32_C(1)<<10)) +#define LIB9P_LO_TRUNC ((lib9p_lo_t)(UINT32_C(1)<<9)) +#define LIB9P_LO_NOCTTY ((lib9p_lo_t)(UINT32_C(1)<<8)) +#define LIB9P_LO_EXCL ((lib9p_lo_t)(UINT32_C(1)<<7)) +#define LIB9P_LO_CREATE ((lib9p_lo_t)(UINT32_C(1)<<6)) +#define _LIB9P_LO_UNUSED_5 ((lib9p_lo_t)(UINT32_C(1)<<5)) +#define _LIB9P_LO_UNUSED_4 ((lib9p_lo_t)(UINT32_C(1)<<4)) +#define _LIB9P_LO_UNUSED_3 ((lib9p_lo_t)(UINT32_C(1)<<3)) +#define _LIB9P_LO_UNUSED_2 ((lib9p_lo_t)(UINT32_C(1)<<2)) +/* number LIB9P_LO_MODE_* ((lib9p_lo_t)(UINT32_C(1)<<1)) */ +/* number LIB9P_LO_MODE_* ((lib9p_lo_t)(UINT32_C(1)<<0)) */ +/* masks */ +#define LIB9P_LO_FLAG_MASK ((lib9p_lo_t)(0b000000000111111111111111000000)) +/* number: MODE */ +#define LIB9P_LO_MODE_RDONLY ((lib9p_lo_t)(0)) +#define LIB9P_LO_MODE_WRONLY ((lib9p_lo_t)(1)) +#define LIB9P_LO_MODE_RDWR ((lib9p_lo_t)(2)) +#define LIB9P_LO_MODE_NOACCESS ((lib9p_lo_t)(3)) +#define LIB9P_LO_MODE_MASK ((lib9p_lo_t)(0b000000000000000000000000000011)) + +/* size = 1 ; max_iov = 1 ; max_copy = 1 */ +typedef uint8_t lib9p_dt_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_dt_t, lib9p_dt); +#define LIB9P_DT_UNKNOWN ((lib9p_dt_t)(0)) +#define LIB9P_DT_PIPE ((lib9p_dt_t)(1)) +#define LIB9P_DT_CHAR_DEV ((lib9p_dt_t)(2)) +#define LIB9P_DT_DIRECTORY ((lib9p_dt_t)(4)) +#define LIB9P_DT_BLOCK_DEV ((lib9p_dt_t)(6)) +#define LIB9P_DT_REGULAR ((lib9p_dt_t)(8)) +#define LIB9P_DT_SYMLINK ((lib9p_dt_t)(10)) +#define LIB9P_DT_SOCKET ((lib9p_dt_t)(12)) +#define _LIB9P_DT_WHITEOUT ((lib9p_dt_t)(14)) + +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ +typedef uint32_t lib9p_mode_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_mode_t, lib9p_mode); +/* bits */ +#define _LIB9P_MODE_UNUSED_31 ((lib9p_mode_t)(UINT32_C(1)<<31)) +#define _LIB9P_MODE_UNUSED_30 ((lib9p_mode_t)(UINT32_C(1)<<30)) +#define _LIB9P_MODE_UNUSED_29 ((lib9p_mode_t)(UINT32_C(1)<<29)) +#define _LIB9P_MODE_UNUSED_28 ((lib9p_mode_t)(UINT32_C(1)<<28)) +#define _LIB9P_MODE_UNUSED_27 ((lib9p_mode_t)(UINT32_C(1)<<27)) +#define _LIB9P_MODE_UNUSED_26 ((lib9p_mode_t)(UINT32_C(1)<<26)) +#define _LIB9P_MODE_UNUSED_25 ((lib9p_mode_t)(UINT32_C(1)<<25)) +#define _LIB9P_MODE_UNUSED_24 ((lib9p_mode_t)(UINT32_C(1)<<24)) +#define _LIB9P_MODE_UNUSED_23 ((lib9p_mode_t)(UINT32_C(1)<<23)) +#define _LIB9P_MODE_UNUSED_22 ((lib9p_mode_t)(UINT32_C(1)<<22)) +#define _LIB9P_MODE_UNUSED_21 ((lib9p_mode_t)(UINT32_C(1)<<21)) +#define _LIB9P_MODE_UNUSED_20 ((lib9p_mode_t)(UINT32_C(1)<<20)) +#define _LIB9P_MODE_UNUSED_19 ((lib9p_mode_t)(UINT32_C(1)<<19)) +#define _LIB9P_MODE_UNUSED_18 ((lib9p_mode_t)(UINT32_C(1)<<18)) +#define _LIB9P_MODE_UNUSED_17 ((lib9p_mode_t)(UINT32_C(1)<<17)) +#define _LIB9P_MODE_UNUSED_16 ((lib9p_mode_t)(UINT32_C(1)<<16)) +/* number LIB9P_MODE_FMT_* ((lib9p_mode_t)(UINT32_C(1)<<15)) */ +/* number LIB9P_MODE_FMT_* ((lib9p_mode_t)(UINT32_C(1)<<14)) */ +/* number LIB9P_MODE_FMT_* ((lib9p_mode_t)(UINT32_C(1)<<13)) */ +/* number LIB9P_MODE_FMT_* ((lib9p_mode_t)(UINT32_C(1)<<12)) */ +#define LIB9P_MODE_PERM_SETGROUP ((lib9p_mode_t)(UINT32_C(1)<<11)) +#define LIB9P_MODE_PERM_SETUSER ((lib9p_mode_t)(UINT32_C(1)<<10)) +#define LIB9P_MODE_PERM_STICKY ((lib9p_mode_t)(UINT32_C(1)<<9)) +#define LIB9P_MODE_PERM_OWNER_R ((lib9p_mode_t)(UINT32_C(1)<<8)) +#define LIB9P_MODE_PERM_OWNER_W ((lib9p_mode_t)(UINT32_C(1)<<7)) +#define LIB9P_MODE_PERM_OWNER_X ((lib9p_mode_t)(UINT32_C(1)<<6)) +#define LIB9P_MODE_PERM_GROUP_R ((lib9p_mode_t)(UINT32_C(1)<<5)) +#define LIB9P_MODE_PERM_GROUP_W ((lib9p_mode_t)(UINT32_C(1)<<4)) +#define LIB9P_MODE_PERM_GROUP_X ((lib9p_mode_t)(UINT32_C(1)<<3)) +#define LIB9P_MODE_PERM_OTHER_R ((lib9p_mode_t)(UINT32_C(1)<<2)) +#define LIB9P_MODE_PERM_OTHER_W ((lib9p_mode_t)(UINT32_C(1)<<1)) +#define LIB9P_MODE_PERM_OTHER_X ((lib9p_mode_t)(UINT32_C(1)<<0)) +/* masks */ +#define LIB9P_MODE_PERM_MASK ((lib9p_mode_t)(0b000000000000000000111111111111)) +/* number: FMT */ +#define LIB9P_MODE_FMT_PIPE ((lib9p_mode_t)(LIB9P_DT_PIPE << 12)) +#define LIB9P_MODE_FMT_CHAR_DEV ((lib9p_mode_t)(LIB9P_DT_CHAR_DEV << 12)) +#define LIB9P_MODE_FMT_DIRECTORY ((lib9p_mode_t)(LIB9P_DT_DIRECTORY << 12)) +#define LIB9P_MODE_FMT_BLOCK_DEV ((lib9p_mode_t)(LIB9P_DT_BLOCK_DEV << 12)) +#define LIB9P_MODE_FMT_REGULAR ((lib9p_mode_t)(LIB9P_DT_REGULAR << 12)) +#define LIB9P_MODE_FMT_SYMLINK ((lib9p_mode_t)(LIB9P_DT_SYMLINK << 12)) +#define LIB9P_MODE_FMT_SOCKET ((lib9p_mode_t)(LIB9P_DT_SOCKET << 12)) +#define LIB9P_MODE_FMT_MASK ((lib9p_mode_t)(0b000000000000001111000000000000)) + +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ +typedef uint32_t lib9p_b4_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_b4_t, lib9p_b4); +#define LIB9P_B4_FALSE ((lib9p_b4_t)(0)) +#define LIB9P_B4_TRUE ((lib9p_b4_t)(1)) + +/* size = 8 ; max_iov = 1 ; max_copy = 8 */ +typedef uint64_t lib9p_getattr_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_getattr_t, lib9p_getattr); +/* bits */ +#define _LIB9P_GETATTR_UNUSED_63 ((lib9p_getattr_t)(UINT64_C(1)<<63)) +#define _LIB9P_GETATTR_UNUSED_62 ((lib9p_getattr_t)(UINT64_C(1)<<62)) +#define _LIB9P_GETATTR_UNUSED_61 ((lib9p_getattr_t)(UINT64_C(1)<<61)) +#define _LIB9P_GETATTR_UNUSED_60 ((lib9p_getattr_t)(UINT64_C(1)<<60)) +#define _LIB9P_GETATTR_UNUSED_59 ((lib9p_getattr_t)(UINT64_C(1)<<59)) +#define _LIB9P_GETATTR_UNUSED_58 ((lib9p_getattr_t)(UINT64_C(1)<<58)) +#define _LIB9P_GETATTR_UNUSED_57 ((lib9p_getattr_t)(UINT64_C(1)<<57)) +#define _LIB9P_GETATTR_UNUSED_56 ((lib9p_getattr_t)(UINT64_C(1)<<56)) +#define _LIB9P_GETATTR_UNUSED_55 ((lib9p_getattr_t)(UINT64_C(1)<<55)) +#define _LIB9P_GETATTR_UNUSED_54 ((lib9p_getattr_t)(UINT64_C(1)<<54)) +#define _LIB9P_GETATTR_UNUSED_53 ((lib9p_getattr_t)(UINT64_C(1)<<53)) +#define _LIB9P_GETATTR_UNUSED_52 ((lib9p_getattr_t)(UINT64_C(1)<<52)) +#define _LIB9P_GETATTR_UNUSED_51 ((lib9p_getattr_t)(UINT64_C(1)<<51)) +#define _LIB9P_GETATTR_UNUSED_50 ((lib9p_getattr_t)(UINT64_C(1)<<50)) +#define _LIB9P_GETATTR_UNUSED_49 ((lib9p_getattr_t)(UINT64_C(1)<<49)) +#define _LIB9P_GETATTR_UNUSED_48 ((lib9p_getattr_t)(UINT64_C(1)<<48)) +#define _LIB9P_GETATTR_UNUSED_47 ((lib9p_getattr_t)(UINT64_C(1)<<47)) +#define _LIB9P_GETATTR_UNUSED_46 ((lib9p_getattr_t)(UINT64_C(1)<<46)) +#define _LIB9P_GETATTR_UNUSED_45 ((lib9p_getattr_t)(UINT64_C(1)<<45)) +#define _LIB9P_GETATTR_UNUSED_44 ((lib9p_getattr_t)(UINT64_C(1)<<44)) +#define _LIB9P_GETATTR_UNUSED_43 ((lib9p_getattr_t)(UINT64_C(1)<<43)) +#define _LIB9P_GETATTR_UNUSED_42 ((lib9p_getattr_t)(UINT64_C(1)<<42)) +#define _LIB9P_GETATTR_UNUSED_41 ((lib9p_getattr_t)(UINT64_C(1)<<41)) +#define _LIB9P_GETATTR_UNUSED_40 ((lib9p_getattr_t)(UINT64_C(1)<<40)) +#define _LIB9P_GETATTR_UNUSED_39 ((lib9p_getattr_t)(UINT64_C(1)<<39)) +#define _LIB9P_GETATTR_UNUSED_38 ((lib9p_getattr_t)(UINT64_C(1)<<38)) +#define _LIB9P_GETATTR_UNUSED_37 ((lib9p_getattr_t)(UINT64_C(1)<<37)) +#define _LIB9P_GETATTR_UNUSED_36 ((lib9p_getattr_t)(UINT64_C(1)<<36)) +#define _LIB9P_GETATTR_UNUSED_35 ((lib9p_getattr_t)(UINT64_C(1)<<35)) +#define _LIB9P_GETATTR_UNUSED_34 ((lib9p_getattr_t)(UINT64_C(1)<<34)) +#define _LIB9P_GETATTR_UNUSED_33 ((lib9p_getattr_t)(UINT64_C(1)<<33)) +#define _LIB9P_GETATTR_UNUSED_32 ((lib9p_getattr_t)(UINT64_C(1)<<32)) +#define _LIB9P_GETATTR_UNUSED_31 ((lib9p_getattr_t)(UINT64_C(1)<<31)) +#define _LIB9P_GETATTR_UNUSED_30 ((lib9p_getattr_t)(UINT64_C(1)<<30)) +#define _LIB9P_GETATTR_UNUSED_29 ((lib9p_getattr_t)(UINT64_C(1)<<29)) +#define _LIB9P_GETATTR_UNUSED_28 ((lib9p_getattr_t)(UINT64_C(1)<<28)) +#define _LIB9P_GETATTR_UNUSED_27 ((lib9p_getattr_t)(UINT64_C(1)<<27)) +#define _LIB9P_GETATTR_UNUSED_26 ((lib9p_getattr_t)(UINT64_C(1)<<26)) +#define _LIB9P_GETATTR_UNUSED_25 ((lib9p_getattr_t)(UINT64_C(1)<<25)) +#define _LIB9P_GETATTR_UNUSED_24 ((lib9p_getattr_t)(UINT64_C(1)<<24)) +#define _LIB9P_GETATTR_UNUSED_23 ((lib9p_getattr_t)(UINT64_C(1)<<23)) +#define _LIB9P_GETATTR_UNUSED_22 ((lib9p_getattr_t)(UINT64_C(1)<<22)) +#define _LIB9P_GETATTR_UNUSED_21 ((lib9p_getattr_t)(UINT64_C(1)<<21)) +#define _LIB9P_GETATTR_UNUSED_20 ((lib9p_getattr_t)(UINT64_C(1)<<20)) +#define _LIB9P_GETATTR_UNUSED_19 ((lib9p_getattr_t)(UINT64_C(1)<<19)) +#define _LIB9P_GETATTR_UNUSED_18 ((lib9p_getattr_t)(UINT64_C(1)<<18)) +#define _LIB9P_GETATTR_UNUSED_17 ((lib9p_getattr_t)(UINT64_C(1)<<17)) +#define _LIB9P_GETATTR_UNUSED_16 ((lib9p_getattr_t)(UINT64_C(1)<<16)) +#define _LIB9P_GETATTR_UNUSED_15 ((lib9p_getattr_t)(UINT64_C(1)<<15)) +#define _LIB9P_GETATTR_UNUSED_14 ((lib9p_getattr_t)(UINT64_C(1)<<14)) +#define LIB9P_GETATTR_DATA_VERSION ((lib9p_getattr_t)(UINT64_C(1)<<13)) +#define LIB9P_GETATTR_GEN ((lib9p_getattr_t)(UINT64_C(1)<<12)) +#define LIB9P_GETATTR_BTIME ((lib9p_getattr_t)(UINT64_C(1)<<11)) +#define LIB9P_GETATTR_BLOCKS ((lib9p_getattr_t)(UINT64_C(1)<<10)) +#define LIB9P_GETATTR_SIZE ((lib9p_getattr_t)(UINT64_C(1)<<9)) +#define LIB9P_GETATTR_INO ((lib9p_getattr_t)(UINT64_C(1)<<8)) +#define LIB9P_GETATTR_CTIME ((lib9p_getattr_t)(UINT64_C(1)<<7)) +#define LIB9P_GETATTR_MTIME ((lib9p_getattr_t)(UINT64_C(1)<<6)) +#define LIB9P_GETATTR_ATIME ((lib9p_getattr_t)(UINT64_C(1)<<5)) +#define LIB9P_GETATTR_RDEV ((lib9p_getattr_t)(UINT64_C(1)<<4)) +#define LIB9P_GETATTR_GID ((lib9p_getattr_t)(UINT64_C(1)<<3)) +#define LIB9P_GETATTR_UID ((lib9p_getattr_t)(UINT64_C(1)<<2)) +#define LIB9P_GETATTR_NLINK ((lib9p_getattr_t)(UINT64_C(1)<<1)) +#define LIB9P_GETATTR_MODE ((lib9p_getattr_t)(UINT64_C(1)<<0)) +/* aliases */ +#define LIB9P_GETATTR_BASIC ((lib9p_getattr_t)(2047)) +#define LIB9P_GETATTR_ALL ((lib9p_getattr_t)(16383)) + +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ +typedef uint32_t lib9p_setattr_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_setattr_t, lib9p_setattr); +/* bits */ +#define _LIB9P_SETATTR_UNUSED_31 ((lib9p_setattr_t)(UINT32_C(1)<<31)) +#define _LIB9P_SETATTR_UNUSED_30 ((lib9p_setattr_t)(UINT32_C(1)<<30)) +#define _LIB9P_SETATTR_UNUSED_29 ((lib9p_setattr_t)(UINT32_C(1)<<29)) +#define _LIB9P_SETATTR_UNUSED_28 ((lib9p_setattr_t)(UINT32_C(1)<<28)) +#define _LIB9P_SETATTR_UNUSED_27 ((lib9p_setattr_t)(UINT32_C(1)<<27)) +#define _LIB9P_SETATTR_UNUSED_26 ((lib9p_setattr_t)(UINT32_C(1)<<26)) +#define _LIB9P_SETATTR_UNUSED_25 ((lib9p_setattr_t)(UINT32_C(1)<<25)) +#define _LIB9P_SETATTR_UNUSED_24 ((lib9p_setattr_t)(UINT32_C(1)<<24)) +#define _LIB9P_SETATTR_UNUSED_23 ((lib9p_setattr_t)(UINT32_C(1)<<23)) +#define _LIB9P_SETATTR_UNUSED_22 ((lib9p_setattr_t)(UINT32_C(1)<<22)) +#define _LIB9P_SETATTR_UNUSED_21 ((lib9p_setattr_t)(UINT32_C(1)<<21)) +#define _LIB9P_SETATTR_UNUSED_20 ((lib9p_setattr_t)(UINT32_C(1)<<20)) +#define _LIB9P_SETATTR_UNUSED_19 ((lib9p_setattr_t)(UINT32_C(1)<<19)) +#define _LIB9P_SETATTR_UNUSED_18 ((lib9p_setattr_t)(UINT32_C(1)<<18)) +#define _LIB9P_SETATTR_UNUSED_17 ((lib9p_setattr_t)(UINT32_C(1)<<17)) +#define _LIB9P_SETATTR_UNUSED_16 ((lib9p_setattr_t)(UINT32_C(1)<<16)) +#define _LIB9P_SETATTR_UNUSED_15 ((lib9p_setattr_t)(UINT32_C(1)<<15)) +#define _LIB9P_SETATTR_UNUSED_14 ((lib9p_setattr_t)(UINT32_C(1)<<14)) +#define _LIB9P_SETATTR_UNUSED_13 ((lib9p_setattr_t)(UINT32_C(1)<<13)) +#define _LIB9P_SETATTR_UNUSED_12 ((lib9p_setattr_t)(UINT32_C(1)<<12)) +#define _LIB9P_SETATTR_UNUSED_11 ((lib9p_setattr_t)(UINT32_C(1)<<11)) +#define _LIB9P_SETATTR_UNUSED_10 ((lib9p_setattr_t)(UINT32_C(1)<<10)) +#define _LIB9P_SETATTR_UNUSED_9 ((lib9p_setattr_t)(UINT32_C(1)<<9)) +#define LIB9P_SETATTR_MTIME_SET ((lib9p_setattr_t)(UINT32_C(1)<<8)) +#define LIB9P_SETATTR_ATIME_SET ((lib9p_setattr_t)(UINT32_C(1)<<7)) +#define LIB9P_SETATTR_CTIME ((lib9p_setattr_t)(UINT32_C(1)<<6)) +#define LIB9P_SETATTR_MTIME ((lib9p_setattr_t)(UINT32_C(1)<<5)) +#define LIB9P_SETATTR_ATIME ((lib9p_setattr_t)(UINT32_C(1)<<4)) +#define LIB9P_SETATTR_SIZE ((lib9p_setattr_t)(UINT32_C(1)<<3)) +#define LIB9P_SETATTR_GID ((lib9p_setattr_t)(UINT32_C(1)<<2)) +#define LIB9P_SETATTR_UID ((lib9p_setattr_t)(UINT32_C(1)<<1)) +#define LIB9P_SETATTR_MODE ((lib9p_setattr_t)(UINT32_C(1)<<0)) + +/* size = 1 ; max_iov = 1 ; max_copy = 1 */ +typedef uint8_t lib9p_lock_type_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_lock_type_t, lib9p_lock_type); +#define LIB9P_LOCK_TYPE_RDLCK ((lib9p_lock_type_t)(0)) +#define LIB9P_LOCK_TYPE_WRLCK ((lib9p_lock_type_t)(1)) +#define LIB9P_LOCK_TYPE_UNLCK ((lib9p_lock_type_t)(2)) + +/* size = 4 ; max_iov = 1 ; max_copy = 4 */ +typedef uint32_t lib9p_lock_flags_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_lock_flags_t, lib9p_lock_flags); +/* bits */ +#define _LIB9P_LOCK_FLAGS_UNUSED_31 ((lib9p_lock_flags_t)(UINT32_C(1)<<31)) +#define _LIB9P_LOCK_FLAGS_UNUSED_30 ((lib9p_lock_flags_t)(UINT32_C(1)<<30)) +#define _LIB9P_LOCK_FLAGS_UNUSED_29 ((lib9p_lock_flags_t)(UINT32_C(1)<<29)) +#define _LIB9P_LOCK_FLAGS_UNUSED_28 ((lib9p_lock_flags_t)(UINT32_C(1)<<28)) +#define _LIB9P_LOCK_FLAGS_UNUSED_27 ((lib9p_lock_flags_t)(UINT32_C(1)<<27)) +#define _LIB9P_LOCK_FLAGS_UNUSED_26 ((lib9p_lock_flags_t)(UINT32_C(1)<<26)) +#define _LIB9P_LOCK_FLAGS_UNUSED_25 ((lib9p_lock_flags_t)(UINT32_C(1)<<25)) +#define _LIB9P_LOCK_FLAGS_UNUSED_24 ((lib9p_lock_flags_t)(UINT32_C(1)<<24)) +#define _LIB9P_LOCK_FLAGS_UNUSED_23 ((lib9p_lock_flags_t)(UINT32_C(1)<<23)) +#define _LIB9P_LOCK_FLAGS_UNUSED_22 ((lib9p_lock_flags_t)(UINT32_C(1)<<22)) +#define _LIB9P_LOCK_FLAGS_UNUSED_21 ((lib9p_lock_flags_t)(UINT32_C(1)<<21)) +#define _LIB9P_LOCK_FLAGS_UNUSED_20 ((lib9p_lock_flags_t)(UINT32_C(1)<<20)) +#define _LIB9P_LOCK_FLAGS_UNUSED_19 ((lib9p_lock_flags_t)(UINT32_C(1)<<19)) +#define _LIB9P_LOCK_FLAGS_UNUSED_18 ((lib9p_lock_flags_t)(UINT32_C(1)<<18)) +#define _LIB9P_LOCK_FLAGS_UNUSED_17 ((lib9p_lock_flags_t)(UINT32_C(1)<<17)) +#define _LIB9P_LOCK_FLAGS_UNUSED_16 ((lib9p_lock_flags_t)(UINT32_C(1)<<16)) +#define _LIB9P_LOCK_FLAGS_UNUSED_15 ((lib9p_lock_flags_t)(UINT32_C(1)<<15)) +#define _LIB9P_LOCK_FLAGS_UNUSED_14 ((lib9p_lock_flags_t)(UINT32_C(1)<<14)) +#define _LIB9P_LOCK_FLAGS_UNUSED_13 ((lib9p_lock_flags_t)(UINT32_C(1)<<13)) +#define _LIB9P_LOCK_FLAGS_UNUSED_12 ((lib9p_lock_flags_t)(UINT32_C(1)<<12)) +#define _LIB9P_LOCK_FLAGS_UNUSED_11 ((lib9p_lock_flags_t)(UINT32_C(1)<<11)) +#define _LIB9P_LOCK_FLAGS_UNUSED_10 ((lib9p_lock_flags_t)(UINT32_C(1)<<10)) +#define _LIB9P_LOCK_FLAGS_UNUSED_9 ((lib9p_lock_flags_t)(UINT32_C(1)<<9)) +#define _LIB9P_LOCK_FLAGS_UNUSED_8 ((lib9p_lock_flags_t)(UINT32_C(1)<<8)) +#define _LIB9P_LOCK_FLAGS_UNUSED_7 ((lib9p_lock_flags_t)(UINT32_C(1)<<7)) +#define _LIB9P_LOCK_FLAGS_UNUSED_6 ((lib9p_lock_flags_t)(UINT32_C(1)<<6)) +#define _LIB9P_LOCK_FLAGS_UNUSED_5 ((lib9p_lock_flags_t)(UINT32_C(1)<<5)) +#define _LIB9P_LOCK_FLAGS_UNUSED_4 ((lib9p_lock_flags_t)(UINT32_C(1)<<4)) +#define _LIB9P_LOCK_FLAGS_UNUSED_3 ((lib9p_lock_flags_t)(UINT32_C(1)<<3)) +#define _LIB9P_LOCK_FLAGS_UNUSED_2 ((lib9p_lock_flags_t)(UINT32_C(1)<<2)) +#define LIB9P_LOCK_FLAGS_RECLAIM ((lib9p_lock_flags_t)(UINT32_C(1)<<1)) +#define LIB9P_LOCK_FLAGS_BLOCK ((lib9p_lock_flags_t)(UINT32_C(1)<<0)) + +/* size = 1 ; max_iov = 1 ; max_copy = 1 */ +typedef uint8_t lib9p_lock_status_t; +LO_IMPLEMENTATION_H(fmt_formatter, lib9p_lock_status_t, lib9p_lock_status); +#define LIB9P_LOCK_STATUS_SUCCESS ((lib9p_lock_status_t)(0)) +#define LIB9P_LOCK_STATUS_BLOCKED ((lib9p_lock_status_t)(1)) +#define LIB9P_LOCK_STATUS_ERROR ((lib9p_lock_status_t)(2)) +#define LIB9P_LOCK_STATUS_GRACE ((lib9p_lock_status_t)(3)) + +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 9 ; max_iov = 1 ; max_copy = 9 */ +struct lib9p_msg_Tflush { + lib9p_tag_t tag; + uint16_t oldtag; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tflush, lib9p_msg_Tflush); -struct lib9p_stat { - uint16_t kern_type; - uint32_t kern_dev; - struct lib9p_qid file_qid; - lib9p_dm_t file_mode; - uint32_t file_atime; - uint32_t file_mtime; - uint64_t file_size; - struct lib9p_s file_name; - struct lib9p_s file_owner_uid; - struct lib9p_s file_owner_gid; - struct lib9p_s file_last_modified_uid; -#if CONFIG_9P_ENABLE_9P2000_u - struct lib9p_s file_extension; - uint32_t file_owner_n_uid; - uint32_t file_owner_n_gid; - uint32_t file_last_modified_n_uid; -#endif /* CONFIG_9P_ENABLE_9P2000_u */ +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rflush { + lib9p_tag_t tag; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rflush, lib9p_msg_Rflush); -typedef uint8_t lib9p_o_t; +/* min_size = 11 ; exp_size = 8,203 ; max_size = 2,147,483,658 ; max_iov = 2 ; max_copy = 11 */ +struct lib9p_msg_Rread { + lib9p_tag_t tag; + uint32_t count; + [[gnu::nonstring]] char *data; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rread, lib9p_msg_Rread); -/* unused ((lib9p_o_t)(1<<7)) */ -#define LIB9P_O_RCLOSE ((lib9p_o_t)(1<<6)) -/* unused ((lib9p_o_t)(1<<5)) */ -#define LIB9P_O_TRUNC ((lib9p_o_t)(1<<4)) -/* unused ((lib9p_o_t)(1<<3)) */ -/* unused ((lib9p_o_t)(1<<2)) */ -#define LIB9P_O_mode_1 ((lib9p_o_t)(1<<1)) -#define LIB9P_O_mode_0 ((lib9p_o_t)(1<<0)) - -#define LIB9P_O_READ ((lib9p_o_t)(0)) -#define LIB9P_O_WRITE ((lib9p_o_t)(1)) -#define LIB9P_O_RDWR ((lib9p_o_t)(2)) -#define LIB9P_O_EXEC ((lib9p_o_t)(3)) -#define LIB9P_O_MODE_MASK ((lib9p_o_t)(0b00000011)) -#define LIB9P_O_FLAG_MASK ((lib9p_o_t)(0b11111100)) -#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */ - -/* messages *******************************************************************/ +/* size = 11 ; max_iov = 1 ; max_copy = 11 */ +struct lib9p_msg_Rwrite { + lib9p_tag_t tag; + uint32_t count; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rwrite, lib9p_msg_Rwrite); -enum lib9p_msg_type { /* uint8_t */ -#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u - LIB9P_TYP_Tversion = 100, - LIB9P_TYP_Rversion = 101, - LIB9P_TYP_Tauth = 102, - LIB9P_TYP_Rauth = 103, - LIB9P_TYP_Tattach = 104, - LIB9P_TYP_Rattach = 105, - LIB9P_TYP_Rerror = 107, - LIB9P_TYP_Tflush = 108, - LIB9P_TYP_Rflush = 109, - LIB9P_TYP_Twalk = 110, - LIB9P_TYP_Rwalk = 111, - LIB9P_TYP_Topen = 112, - LIB9P_TYP_Ropen = 113, - LIB9P_TYP_Tcreate = 114, - LIB9P_TYP_Rcreate = 115, - LIB9P_TYP_Tread = 116, - LIB9P_TYP_Rread = 117, - LIB9P_TYP_Twrite = 118, - LIB9P_TYP_Rwrite = 119, - LIB9P_TYP_Tclunk = 120, - LIB9P_TYP_Rclunk = 121, - LIB9P_TYP_Tremove = 122, - LIB9P_TYP_Rremove = 123, - LIB9P_TYP_Tstat = 124, - LIB9P_TYP_Rstat = 125, - LIB9P_TYP_Twstat = 126, - LIB9P_TYP_Rwstat = 127, -#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */ +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rclunk { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rclunk, lib9p_msg_Rclunk); + +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rremove { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rremove, lib9p_msg_Rremove); + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rwstat { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rwstat, lib9p_msg_Rwstat); + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rrename { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rrename, lib9p_msg_Rrename); + +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rsetattr { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rsetattr, lib9p_msg_Rsetattr); + +/* size = 15 ; max_iov = 1 ; max_copy = 15 */ +struct lib9p_msg_Rxattrwalk { + lib9p_tag_t tag; + uint64_t attr_size; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rxattrwalk, lib9p_msg_Rxattrwalk); + +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rxattrcreate { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rxattrcreate, lib9p_msg_Rxattrcreate); + +/* min_size = 11 ; exp_size = 8,203 ; max_size = 4,294,967,306 (warning: >UINT32_MAX) ; max_iov = 2 ; max_copy = 11 */ +struct lib9p_msg_Rreaddir { + lib9p_tag_t tag; + uint32_t count; + [[gnu::nonstring]] char *data; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rreaddir, lib9p_msg_Rreaddir); + +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rfsync { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rfsync, lib9p_msg_Rfsync); + +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rlink { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rlink, lib9p_msg_Rlink); + +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rrenameat { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rrenameat, lib9p_msg_Rrenameat); + +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Runlinkat { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Runlinkat, lib9p_msg_Runlinkat); + +#endif /* CONFIG_9P_ENABLE_9P2000_L */ #if CONFIG_9P_ENABLE_9P2000_e - LIB9P_TYP_Tsession = 150, - LIB9P_TYP_Rsession = 151, - LIB9P_TYP_Tsread = 152, - LIB9P_TYP_Rsread = 153, - LIB9P_TYP_Tswrite = 154, - LIB9P_TYP_Rswrite = 155, +/* size = 15 ; max_iov = 1 ; max_copy = 15 */ +struct lib9p_msg_Tsession { + lib9p_tag_t tag; + uint64_t key; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tsession, lib9p_msg_Tsession); + +/* size = 7 ; max_iov = 1 ; max_copy = 7 */ +struct lib9p_msg_Rsession { + lib9p_tag_t tag; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rsession, lib9p_msg_Rsession); + +/* min_size = 11 ; exp_size = 8,203 ; max_size = 4,294,967,306 (warning: >UINT32_MAX) ; max_iov = 2 ; max_copy = 11 */ +struct lib9p_msg_Rsread { + lib9p_tag_t tag; + uint32_t count; + [[gnu::nonstring]] char *data; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rsread, lib9p_msg_Rsread); + +/* size = 11 ; max_iov = 1 ; max_copy = 11 */ +struct lib9p_msg_Rswrite { + lib9p_tag_t tag; + uint32_t count; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rswrite, lib9p_msg_Rswrite); + #endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 23 ; max_iov = 1 ; max_copy = 23 */ +struct lib9p_msg_Tread { + lib9p_tag_t tag; + lib9p_fid_t fid; + uint64_t offset; + uint32_t count; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tread, lib9p_msg_Tread); + +/* min_size = 23 ; exp_size = 8,215 ; max_size = 2,147,483,670 ; max_iov = 2 ; max_copy = 23 */ +struct lib9p_msg_Twrite { + lib9p_tag_t tag; + lib9p_fid_t fid; + uint64_t offset; + uint32_t count; + [[gnu::nonstring]] char *data; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Twrite, lib9p_msg_Twrite); + +/* size = 11 ; max_iov = 1 ; max_copy = 11 */ +struct lib9p_msg_Tclunk { + lib9p_tag_t tag; + lib9p_fid_t fid; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tclunk, lib9p_msg_Tclunk); + +/* size = 11 ; max_iov = 1 ; max_copy = 11 */ +struct lib9p_msg_Tremove { + lib9p_tag_t tag; + lib9p_fid_t fid; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tremove, lib9p_msg_Tremove); + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 11 ; max_iov = 1 ; max_copy = 11 */ +struct lib9p_msg_Tstat { + lib9p_tag_t tag; + lib9p_fid_t fid; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tstat, lib9p_msg_Tstat); + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +/* size = 11 ; max_iov = 1 ; max_copy = 11 */ +struct lib9p_msg_Tstatfs { + lib9p_tag_t tag; + lib9p_fid_t fid; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tstatfs, lib9p_msg_Tstatfs); + +/* size = 11 ; max_iov = 1 ; max_copy = 11 */ +struct lib9p_msg_Treadlink { + lib9p_tag_t tag; + lib9p_fid_t fid; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Treadlink, lib9p_msg_Treadlink); + +/* size = 23 ; max_iov = 1 ; max_copy = 23 */ +struct lib9p_msg_Treaddir { + lib9p_tag_t tag; + lib9p_fid_t fid; + uint64_t offset; + uint32_t count; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Treaddir, lib9p_msg_Treaddir); -#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* min_size = 13 ; exp_size = 40 ; max_size = 65,548 ; max_iov = 2 ; max_copy = 13 */ struct lib9p_msg_Tversion { lib9p_tag_t tag; uint32_t max_msg_size; struct lib9p_s version; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tversion, lib9p_msg_Tversion); +/* min_size = 13 ; exp_size = 40 ; max_size = 65,548 ; max_iov = 2 ; max_copy = 13 */ struct lib9p_msg_Rversion { lib9p_tag_t tag; uint32_t max_msg_size; struct lib9p_s version; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rversion, lib9p_msg_Rversion); + +/* min_size = 17 ; exp_size = 481 ; max_size = 1,048,609 ; max_iov = 32 ; max_copy = 49 */ +struct lib9p_msg_Twalk { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_fid_t newfid; + uint16_t nwname; + struct lib9p_s *wname; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Twalk, lib9p_msg_Twalk); + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +/* min_size = 17 ; exp_size = 44 ; max_size = 65,552 ; max_iov = 2 ; max_copy = 17 */ +struct lib9p_msg_Trename { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_fid_t dfid; + struct lib9p_s name; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Trename, lib9p_msg_Trename); + +/* min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */ +struct lib9p_msg_Rreadlink { + lib9p_tag_t tag; + struct lib9p_s target; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rreadlink, lib9p_msg_Rreadlink); + +/* min_size = 17 ; exp_size = 44 ; max_size = 65,552 ; max_iov = 2 ; max_copy = 17 */ +struct lib9p_msg_Txattrwalk { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_fid_t newfid; + struct lib9p_s name; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Txattrwalk, lib9p_msg_Txattrwalk); + +/* min_size = 25 ; exp_size = 52 ; max_size = 65,560 ; max_iov = 3 ; max_copy = 25 */ +struct lib9p_msg_Txattrcreate { + lib9p_tag_t tag; + lib9p_fid_t fid; + struct lib9p_s name; + uint64_t attr_size; + uint32_t flags; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Txattrcreate, lib9p_msg_Txattrcreate); + +/* min_size = 17 ; exp_size = 44 ; max_size = 65,552 ; max_iov = 2 ; max_copy = 17 */ +struct lib9p_msg_Tlink { + lib9p_tag_t tag; + lib9p_fid_t dfid; + lib9p_fid_t fid; + struct lib9p_s name; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tlink, lib9p_msg_Tlink); + +/* min_size = 19 ; exp_size = 73 ; max_size = 131,089 ; max_iov = 4 ; max_copy = 19 */ +struct lib9p_msg_Trenameat { + lib9p_tag_t tag; + lib9p_fid_t olddirfid; + struct lib9p_s oldname; + lib9p_fid_t newdirfid; + struct lib9p_s newname; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Trenameat, lib9p_msg_Trenameat); + +/* min_size = 17 ; exp_size = 44 ; max_size = 65,552 ; max_iov = 3 ; max_copy = 17 */ +struct lib9p_msg_Tunlinkat { + lib9p_tag_t tag; + lib9p_fid_t dirfd; + struct lib9p_s name; + uint32_t flags; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tunlinkat, lib9p_msg_Tunlinkat); + +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000_e +/* min_size = 13 ; exp_size = 477 ; max_size = 4,294,967,308 (warning: >UINT32_MAX) ; max_iov = 0 + (CONFIG_9P_MAX_9P2000_e_WELEM * 2) ; max_copy = 13 + (CONFIG_9P_MAX_9P2000_e_WELEM * 2) */ +struct lib9p_msg_Tsread { + lib9p_tag_t tag; + uint32_t fid; + uint16_t nwname; + struct lib9p_s *wname; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tsread, lib9p_msg_Tsread); + +/* min_size = 17 ; exp_size = 8,673 ; max_size = 8,589,934,607 (warning: >UINT32_MAX) ; max_iov = 2 + (CONFIG_9P_MAX_9P2000_e_WELEM * 2) ; max_copy = 17 + (CONFIG_9P_MAX_9P2000_e_WELEM * 2) */ +struct lib9p_msg_Tswrite { + lib9p_tag_t tag; + uint32_t fid; + uint16_t nwname; + struct lib9p_s *wname; + uint32_t count; + [[gnu::nonstring]] char *data; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tswrite, lib9p_msg_Tswrite); +#endif /* CONFIG_9P_ENABLE_9P2000_e */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 13 ; max_iov = 1 ; max_copy = 13 */ +struct lib9p_qid { + lib9p_qt_t type; + uint32_t vers; + uint64_t path; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_qid, lib9p_qid); + +/* LIB9P_VER_9P2000 : min_size = 15 ; exp_size = 69 ; max_size = 131,085 ; max_iov = 4 ; max_copy = 15 */ +/* LIB9P_VER_9P2000_L : min_size = 19 ; exp_size = 73 ; max_size = 131,089 ; max_iov = 5 ; max_copy = 19 */ +/* LIB9P_VER_9P2000_e : min_size = 15 ; exp_size = 69 ; max_size = 131,085 ; max_iov = 4 ; max_copy = 15 */ +/* LIB9P_VER_9P2000_p9p: min_size = 15 ; exp_size = 69 ; max_size = 131,085 ; max_iov = 4 ; max_copy = 15 */ +/* LIB9P_VER_9P2000_u : min_size = 19 ; exp_size = 73 ; max_size = 131,089 ; max_iov = 5 ; max_copy = 19 */ struct lib9p_msg_Tauth { lib9p_tag_t tag; lib9p_fid_t afid; struct lib9p_s uname; struct lib9p_s aname; -#if CONFIG_9P_ENABLE_9P2000_u - uint32_t n_uname; -#endif /* CONFIG_9P_ENABLE_9P2000_u */ -}; - -struct lib9p_msg_Rauth { - lib9p_tag_t tag; - struct lib9p_qid aqid; +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + lib9p_nuid_t n_uid; +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tauth, lib9p_msg_Tauth); +/* LIB9P_VER_9P2000 : min_size = 19 ; exp_size = 73 ; max_size = 131,089 ; max_iov = 4 ; max_copy = 19 */ +/* LIB9P_VER_9P2000_L : min_size = 23 ; exp_size = 77 ; max_size = 131,093 ; max_iov = 5 ; max_copy = 23 */ +/* LIB9P_VER_9P2000_e : min_size = 19 ; exp_size = 73 ; max_size = 131,089 ; max_iov = 4 ; max_copy = 19 */ +/* LIB9P_VER_9P2000_p9p: min_size = 19 ; exp_size = 73 ; max_size = 131,089 ; max_iov = 4 ; max_copy = 19 */ +/* LIB9P_VER_9P2000_u : min_size = 23 ; exp_size = 77 ; max_size = 131,093 ; max_iov = 5 ; max_copy = 23 */ struct lib9p_msg_Tattach { lib9p_tag_t tag; lib9p_fid_t fid; lib9p_fid_t afid; struct lib9p_s uname; struct lib9p_s aname; -#if CONFIG_9P_ENABLE_9P2000_u - uint32_t n_uname; -#endif /* CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u + lib9p_nuid_t n_uid; +#endif /* CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_u */ }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tattach, lib9p_msg_Tattach); -struct lib9p_msg_Rattach { - lib9p_tag_t tag; - struct lib9p_qid qid; +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +/* min_size = 19 ; exp_size = 73 ; max_size = 131,089 ; max_iov = 5 ; max_copy = 19 */ +struct lib9p_msg_Tsymlink { + lib9p_tag_t tag; + lib9p_fid_t fid; + struct lib9p_s name; + struct lib9p_s symtgt; + lib9p_nuid_t gid; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tsymlink, lib9p_msg_Tsymlink); +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 12 ; max_iov = 1 ; max_copy = 12 */ +struct lib9p_msg_Topen { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_o_t mode; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Topen, lib9p_msg_Topen); + +/* min_size = 18 ; exp_size = 45 ; max_size = 65,553 ; max_iov = 3 ; max_copy = 18 */ +struct lib9p_msg_Tcreate { + lib9p_tag_t tag; + lib9p_fid_t fid; + struct lib9p_s name; + lib9p_dm_t perm; + lib9p_o_t mode; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tcreate, lib9p_msg_Tcreate); + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_p9p +/* size = 12 ; max_iov = 1 ; max_copy = 12 */ +struct lib9p_msg_Topenfd { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_o_t mode; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Topenfd, lib9p_msg_Topenfd); + +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* LIB9P_VER_9P2000 : min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */ +/* LIB9P_VER_9P2000_L : min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */ +/* LIB9P_VER_9P2000_e : min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */ +/* LIB9P_VER_9P2000_p9p: min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */ +/* LIB9P_VER_9P2000_u : min_size = 13 ; exp_size = 40 ; max_size = 65,548 ; max_iov = 3 ; max_copy = 13 */ struct lib9p_msg_Rerror { lib9p_tag_t tag; struct lib9p_s ename; #if CONFIG_9P_ENABLE_9P2000_u - uint32_t errno; + lib9p_errno_t errno; #endif /* CONFIG_9P_ENABLE_9P2000_u */ }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rerror, lib9p_msg_Rerror); -struct lib9p_msg_Tflush { - lib9p_tag_t tag; - uint16_t oldtag; +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_L +/* size = 11 ; max_iov = 1 ; max_copy = 11 */ +struct lib9p_msg_Rlerror { + lib9p_tag_t tag; + lib9p_errno_t ecode; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rlerror, lib9p_msg_Rlerror); -struct lib9p_msg_Rflush { +/* size = 67 ; max_iov = 1 ; max_copy = 67 */ +struct lib9p_msg_Rstatfs { + lib9p_tag_t tag; + lib9p_super_magic_t type; + uint32_t bsize; + uint64_t blocks; + uint64_t bfree; + uint64_t bavail; + uint64_t files; + uint64_t ffree; + uint64_t fsid; + uint32_t namelen; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rstatfs, lib9p_msg_Rstatfs); + +/* size = 15 ; max_iov = 1 ; max_copy = 15 */ +struct lib9p_msg_Tlopen { lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_lo_t flags; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tlopen, lib9p_msg_Tlopen); -struct lib9p_msg_Twalk { +/* min_size = 25 ; exp_size = 52 ; max_size = 65,560 ; max_iov = 3 ; max_copy = 25 */ +struct lib9p_msg_Tlcreate { lib9p_tag_t tag; lib9p_fid_t fid; - lib9p_fid_t newfid; - uint16_t nwname; - struct lib9p_s *wname; + struct lib9p_s name; + lib9p_lo_t flags; + lib9p_mode_t mode; + lib9p_nuid_t gid; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tlcreate, lib9p_msg_Tlcreate); -struct lib9p_msg_Rwalk { - lib9p_tag_t tag; - uint16_t nwqid; - struct lib9p_qid *wqid; +/* min_size = 29 ; exp_size = 56 ; max_size = 65,564 ; max_iov = 3 ; max_copy = 29 */ +struct lib9p_msg_Tmknod { + lib9p_tag_t tag; + lib9p_fid_t dfid; + struct lib9p_s name; + lib9p_mode_t mode; + uint32_t major; + uint32_t minor; + lib9p_nuid_t gid; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tmknod, lib9p_msg_Tmknod); -struct lib9p_msg_Topen { +/* min_size = 21 ; exp_size = 48 ; max_size = 65,556 ; max_iov = 3 ; max_copy = 21 */ +struct lib9p_msg_Tmkdir { + lib9p_tag_t tag; + lib9p_fid_t dfid; + struct lib9p_s name; + lib9p_mode_t mode; + lib9p_nuid_t gid; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tmkdir, lib9p_msg_Tmkdir); + +/* size = 15 ; max_iov = 1 ; max_copy = 15 */ +struct lib9p_msg_Tfsync { lib9p_tag_t tag; lib9p_fid_t fid; - lib9p_o_t mode; + lib9p_b4_t datasync; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tfsync, lib9p_msg_Tfsync); -struct lib9p_msg_Ropen { +/* size = 19 ; max_iov = 1 ; max_copy = 19 */ +struct lib9p_msg_Tgetattr { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_getattr_t request_mask; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tgetattr, lib9p_msg_Tgetattr); + +/* size = 67 ; max_iov = 1 ; max_copy = 67 */ +struct lib9p_msg_Tsetattr { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_setattr_t valid; + lib9p_mode_t mode; + lib9p_nuid_t uid; + lib9p_nuid_t gid; + uint64_t filesize; + uint64_t atime_sec; + uint64_t atime_nsec; + uint64_t mtime_sec; + uint64_t mtime_nsec; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tsetattr, lib9p_msg_Tsetattr); + +/* min_size = 34 ; exp_size = 61 ; max_size = 65,569 ; max_iov = 2 ; max_copy = 34 */ +struct lib9p_msg_Tgetlock { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_lock_type_t type; + uint64_t start; + uint64_t length; + uint32_t proc_id; + struct lib9p_s client_id; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tgetlock, lib9p_msg_Tgetlock); + +/* min_size = 30 ; exp_size = 57 ; max_size = 65,565 ; max_iov = 2 ; max_copy = 30 */ +struct lib9p_msg_Rgetlock { + lib9p_tag_t tag; + lib9p_lock_type_t type; + uint64_t start; + uint64_t length; + uint32_t proc_id; + struct lib9p_s client_id; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rgetlock, lib9p_msg_Rgetlock); + +/* min_size = 38 ; exp_size = 65 ; max_size = 65,573 ; max_iov = 2 ; max_copy = 38 */ +struct lib9p_msg_Tlock { + lib9p_tag_t tag; + lib9p_fid_t fid; + lib9p_lock_type_t type; + lib9p_lock_flags_t flags; + uint64_t start; + uint64_t length; + uint32_t proc_id; + struct lib9p_s client_id; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Tlock, lib9p_msg_Tlock); + +/* size = 8 ; max_iov = 1 ; max_copy = 8 */ +struct lib9p_msg_Rlock { + lib9p_tag_t tag; + lib9p_lock_status_t status; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rlock, lib9p_msg_Rlock); + +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* LIB9P_VER_9P2000 : min_size = 49 ; exp_size = 157 ; max_size = 262,189 ; max_iov = 8 ; max_copy = 49 */ +/* LIB9P_VER_9P2000_e : min_size = 49 ; exp_size = 157 ; max_size = 262,189 ; max_iov = 8 ; max_copy = 49 */ +/* LIB9P_VER_9P2000_p9p: min_size = 49 ; exp_size = 157 ; max_size = 262,189 ; max_iov = 8 ; max_copy = 49 */ +/* LIB9P_VER_9P2000_u : min_size = 63 ; exp_size = 198 ; max_size = 327,738 ; max_iov = 11 ; max_copy = 63 */ +struct lib9p_stat { + uint16_t kern_type; + uint32_t kern_dev; + struct lib9p_qid file_qid; + lib9p_dm_t file_mode; + uint32_t file_atime; + uint32_t file_mtime; + uint64_t file_size; + struct lib9p_s file_name; + struct lib9p_s file_owner_uid; + struct lib9p_s file_owner_gid; + struct lib9p_s file_last_modified_uid; +#if CONFIG_9P_ENABLE_9P2000_u + struct lib9p_s file_extension; + lib9p_nuid_t file_owner_n_uid; + lib9p_nuid_t file_owner_n_gid; + lib9p_nuid_t file_last_modified_n_uid; +#endif /* CONFIG_9P_ENABLE_9P2000_u */ +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_stat, lib9p_stat); + +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 20 ; max_iov = 1 ; max_copy = 20 */ +struct lib9p_msg_Rauth { + lib9p_tag_t tag; + struct lib9p_qid aqid; +}; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rauth, lib9p_msg_Rauth); + +/* size = 20 ; max_iov = 1 ; max_copy = 20 */ +struct lib9p_msg_Rattach { lib9p_tag_t tag; struct lib9p_qid qid; - uint32_t iounit; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rattach, lib9p_msg_Rattach); -struct lib9p_msg_Tcreate { - lib9p_tag_t tag; - lib9p_fid_t fid; - struct lib9p_s name; - lib9p_dm_t perm; - lib9p_o_t mode; +/* min_size = 9 ; exp_size = 217 ; max_size = 217 ; max_iov = 1 ; max_copy = 217 */ +struct lib9p_msg_Rwalk { + lib9p_tag_t tag; + uint16_t nwqid; + struct lib9p_qid *wqid; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rwalk, lib9p_msg_Rwalk); -struct lib9p_msg_Rcreate { +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* size = 24 ; max_iov = 1 ; max_copy = 24 */ +struct lib9p_msg_Ropen { lib9p_tag_t tag; struct lib9p_qid qid; uint32_t iounit; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Ropen, lib9p_msg_Ropen); -struct lib9p_msg_Tread { - lib9p_tag_t tag; - lib9p_fid_t fid; - uint64_t offset; - uint32_t count; -}; - -struct lib9p_msg_Rread { - lib9p_tag_t tag; - struct lib9p_d data; +/* size = 24 ; max_iov = 1 ; max_copy = 24 */ +struct lib9p_msg_Rcreate { + lib9p_tag_t tag; + struct lib9p_qid qid; + uint32_t iounit; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rcreate, lib9p_msg_Rcreate); -struct lib9p_msg_Twrite { - lib9p_tag_t tag; - lib9p_fid_t fid; - uint64_t offset; - struct lib9p_d data; +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ +#if CONFIG_9P_ENABLE_9P2000_p9p +/* size = 28 ; max_iov = 1 ; max_copy = 28 */ +struct lib9p_msg_Ropenfd { + lib9p_tag_t tag; + struct lib9p_qid qid; + uint32_t iounit; + uint32_t unixfd; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Ropenfd, lib9p_msg_Ropenfd); -struct lib9p_msg_Rwrite { - lib9p_tag_t tag; - uint32_t count; +#endif /* CONFIG_9P_ENABLE_9P2000_p9p */ +#if CONFIG_9P_ENABLE_9P2000_L +/* size = 24 ; max_iov = 1 ; max_copy = 24 */ +struct lib9p_msg_Rlopen { + lib9p_tag_t tag; + struct lib9p_qid qid; + uint32_t iounit; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rlopen, lib9p_msg_Rlopen); -struct lib9p_msg_Tclunk { - lib9p_tag_t tag; - lib9p_fid_t fid; +/* size = 24 ; max_iov = 1 ; max_copy = 24 */ +struct lib9p_msg_Rlcreate { + lib9p_tag_t tag; + struct lib9p_qid qid; + uint32_t iounit; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rlcreate, lib9p_msg_Rlcreate); -struct lib9p_msg_Rclunk { - lib9p_tag_t tag; +/* size = 20 ; max_iov = 1 ; max_copy = 20 */ +struct lib9p_msg_Rsymlink { + lib9p_tag_t tag; + struct lib9p_qid qid; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rsymlink, lib9p_msg_Rsymlink); -struct lib9p_msg_Tremove { - lib9p_tag_t tag; - lib9p_fid_t fid; +/* size = 20 ; max_iov = 1 ; max_copy = 20 */ +struct lib9p_msg_Rmknod { + lib9p_tag_t tag; + struct lib9p_qid qid; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rmknod, lib9p_msg_Rmknod); -struct lib9p_msg_Rremove { - lib9p_tag_t tag; +/* size = 160 ; max_iov = 1 ; max_copy = 160 */ +struct lib9p_msg_Rgetattr { + lib9p_tag_t tag; + lib9p_getattr_t valid; + struct lib9p_qid qid; + lib9p_mode_t mode; + lib9p_nuid_t uid; + lib9p_nuid_t gid; + uint64_t nlink; + uint64_t rdev; + uint64_t filesize; + uint64_t blksize; + uint64_t blocks; + uint64_t atime_sec; + uint64_t atime_nsec; + uint64_t mtime_sec; + uint64_t mtime_nsec; + uint64_t ctime_sec; + uint64_t ctime_nsec; + uint64_t btime_sec; + uint64_t btime_nsec; + uint64_t gen; + uint64_t data_version; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rgetattr, lib9p_msg_Rgetattr); -struct lib9p_msg_Tstat { - lib9p_tag_t tag; - lib9p_fid_t fid; +/* size = 20 ; max_iov = 1 ; max_copy = 20 */ +struct lib9p_msg_Rmkdir { + lib9p_tag_t tag; + struct lib9p_qid qid; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rmkdir, lib9p_msg_Rmkdir); +#endif /* CONFIG_9P_ENABLE_9P2000_L */ +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u +/* LIB9P_VER_9P2000 : min_size = 58 ; exp_size = 166 ; max_size = 262,198 ; max_iov = 8 ; max_copy = 58 */ +/* LIB9P_VER_9P2000_e : min_size = 58 ; exp_size = 166 ; max_size = 262,198 ; max_iov = 8 ; max_copy = 58 */ +/* LIB9P_VER_9P2000_p9p: min_size = 58 ; exp_size = 166 ; max_size = 262,198 ; max_iov = 8 ; max_copy = 58 */ +/* LIB9P_VER_9P2000_u : min_size = 72 ; exp_size = 207 ; max_size = 327,747 ; max_iov = 11 ; max_copy = 72 */ struct lib9p_msg_Rstat { lib9p_tag_t tag; struct lib9p_stat stat; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rstat, lib9p_msg_Rstat); +/* LIB9P_VER_9P2000 : min_size = 62 ; exp_size = 170 ; max_size = 262,202 ; max_iov = 8 ; max_copy = 62 */ +/* LIB9P_VER_9P2000_e : min_size = 62 ; exp_size = 170 ; max_size = 262,202 ; max_iov = 8 ; max_copy = 62 */ +/* LIB9P_VER_9P2000_p9p: min_size = 62 ; exp_size = 170 ; max_size = 262,202 ; max_iov = 8 ; max_copy = 62 */ +/* LIB9P_VER_9P2000_u : min_size = 76 ; exp_size = 211 ; max_size = 327,751 ; max_iov = 11 ; max_copy = 76 */ struct lib9p_msg_Twstat { lib9p_tag_t tag; lib9p_fid_t fid; struct lib9p_stat stat; }; +LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Twstat, lib9p_msg_Twstat); +#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */ -struct lib9p_msg_Rwstat { - lib9p_tag_t tag; -}; +/* containers *****************************************************************/ -#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */ -#if CONFIG_9P_ENABLE_9P2000_e -struct lib9p_msg_Tsession { - lib9p_tag_t tag; - uint64_t key; -}; +#define _LIB9P_MAX(a, b) ((a) > (b)) ? (a) : (b) -struct lib9p_msg_Rsession { - lib9p_tag_t tag; -}; +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u + #if CONFIG_9P_ENABLE_9P2000_e + #define LIB9P_TMSG_MAX_IOV _LIB9P_MAX(32, 2 + (CONFIG_9P_MAX_9P2000_e_WELEM * 2)) + #else + #define LIB9P_TMSG_MAX_IOV 32 + #endif +#endif -struct lib9p_msg_Tsread { - lib9p_tag_t tag; - uint32_t fid; - uint16_t nwname; - struct lib9p_s *wname; -}; +#if CONFIG_9P_ENABLE_9P2000_u + #if CONFIG_9P_ENABLE_9P2000_e + #define LIB9P_TMSG_MAX_COPY _LIB9P_MAX(76, 17 + (CONFIG_9P_MAX_9P2000_e_WELEM * 2)) + #else + #define LIB9P_TMSG_MAX_COPY 76 + #endif +#elif CONFIG_9P_ENABLE_9P2000_L + #if CONFIG_9P_ENABLE_9P2000_e + #define LIB9P_TMSG_MAX_COPY _LIB9P_MAX(67, 17 + (CONFIG_9P_MAX_9P2000_e_WELEM * 2)) + #else + #define LIB9P_TMSG_MAX_COPY 67 + #endif +#elif CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p + #if CONFIG_9P_ENABLE_9P2000_e + #define LIB9P_TMSG_MAX_COPY _LIB9P_MAX(62, 17 + (CONFIG_9P_MAX_9P2000_e_WELEM * 2)) + #else + #define LIB9P_TMSG_MAX_COPY 62 + #endif +#endif -struct lib9p_msg_Rsread { - lib9p_tag_t tag; - struct lib9p_d data; -}; +#if CONFIG_9P_ENABLE_9P2000_u + #define LIB9P_RMSG_MAX_IOV 11 +#elif CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p + #define LIB9P_RMSG_MAX_IOV 8 +#elif CONFIG_9P_ENABLE_9P2000_L + #define LIB9P_RMSG_MAX_IOV 2 +#endif -struct lib9p_msg_Tswrite { - lib9p_tag_t tag; - uint32_t fid; - uint16_t nwname; - struct lib9p_s *wname; - struct lib9p_d data; +#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u + #define LIB9P_RMSG_MAX_COPY 217 +#endif + +struct lib9p_Tmsg_send_buf { + size_t iov_cnt; + struct iovec iov[LIB9P_TMSG_MAX_IOV]; + uint8_t copied[LIB9P_TMSG_MAX_COPY]; }; -struct lib9p_msg_Rswrite { - lib9p_tag_t tag; - uint32_t count; +struct lib9p_Rmsg_send_buf { + size_t iov_cnt; + struct iovec iov[LIB9P_RMSG_MAX_IOV]; + uint8_t copied[LIB9P_RMSG_MAX_COPY]; }; -#endif /* CONFIG_9P_ENABLE_9P2000_e */ diff --git a/lib9p/include/lib9p/9p.h b/lib9p/include/lib9p/9p.h index fb1f97d..5919260 100644 --- a/lib9p/include/lib9p/9p.h +++ b/lib9p/include/lib9p/9p.h @@ -1,6 +1,6 @@ /* lib9p/9p.h - Base 9P protocol definitions for both clients and servers * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -18,11 +18,25 @@ #ifndef CONFIG_9P_MAX_ERR_SIZE #error config.h must define CONFIG_9P_MAX_ERR_SIZE #endif +static_assert(CONFIG_9P_MAX_ERR_SIZE <= UINT16_MAX); -/******************************************************************************/ +/* constants ******************************************************************/ -#define LIB9P_NOTAG ((uint16_t)~0U) -#define LIB9P_NOFID ((uint32_t)~0U) +enum { + LIB9P_DEFAULT_PORT_9FS = 564, + LIB9P_DEFAULT_PORT_STYX = 6666, +}; + +/* strings ********************************************************************/ + +const char *lib9p_version_str(enum lib9p_version); +const char *lib9p_msgtype_str(enum lib9p_version, enum lib9p_msg_type); + +struct lib9p_s lib9p_str(char *s); +struct lib9p_s lib9p_strn(char *s, size_t maxlen); +struct lib9p_s lib9p_str_slice(struct lib9p_s s, uint16_t beg, uint16_t end); +#define lib9p_str_sliceleft(s, beg) lib9p_str_slice(s, beg, (s).len) +bool lib9p_str_eq(struct lib9p_s a, struct lib9p_s b); /* ctx ************************************************************************/ @@ -33,9 +47,9 @@ struct lib9p_ctx { /* state */ #ifdef CONFIG_9P_ENABLE_9P2000_u - uint32_t err_num; + lib9p_errno_t err_num; #endif - char err_msg[CONFIG_9P_MAX_ERR_SIZE]; + [[gnu::nonstring]] char err_msg[CONFIG_9P_MAX_ERR_SIZE]; }; void lib9p_ctx_clear_error(struct lib9p_ctx *ctx); @@ -43,21 +57,25 @@ void lib9p_ctx_clear_error(struct lib9p_ctx *ctx); bool lib9p_ctx_has_error(struct lib9p_ctx *ctx); /** Write an static error into ctx, return -1. */ -int lib9p_error(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *msg); +int lib9p_error(struct lib9p_ctx *ctx, lib9p_errno_t linux_errno, char const *msg); /** Write a printf-style error into ctx, return -1. */ -int lib9p_errorf(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *fmt, ...); +int lib9p_errorf(struct lib9p_ctx *ctx, lib9p_errno_t linux_errno, char const *fmt, ...) [[gnu::format(printf, 3, 4)]]; + +/* misc utilities *************************************************************/ + +uint32_t lib9p_version_min_msg_size(enum lib9p_version); -const char *lib9p_msg_type_str(struct lib9p_ctx *, enum lib9p_msg_type); +lo_interface fmt_formatter lo_box_lib9p_msg_as_fmt_formatter(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body); -/* main message functions *****************************************************/ +/* main T-message functions ***************************************************/ /** - * Validate a message's structure; it's size, string encodings, enums, + * Validate a message's structure; its size, string encodings, enums, * and bitfields. * * Return how much space the message will take when unmarshaled. This * number may be larger than net_bytes due to (1) struct padding, (2) - * nul-terminator bytes for strings. + * array pointers. * * Emits an error (return -1, set ctx->err_num and ctx->err_msg) if * either the message type is unknown, or if net_bytes is too short @@ -68,39 +86,37 @@ const char *lib9p_msg_type_str(struct lib9p_ctx *, enum lib9p_msg_type); * * @return required size, or -1 on error * + * @errno LINUX_EOPNOTSUPP: message is an R-message * @errno LINUX_EOPNOTSUPP: message has unknown type * @errno LINUX_EBADMSG: message is wrong size for content * @errno LINUX_EBADMSG: message contains invalid UTF-8 * @errno LINUX_EBADMSG: message contains a bitfield with unknown bits * @errno LINUX_EMSGSIZE: would-be return value overflows SSIZE_MAX */ -ssize_t lib9p_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes); +ssize_t lib9p_Tmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes); /** * Unmarshal the 9P message `net_bytes` into the C struct `ret_body`. * - * Emits an error (return 0, set ctx->err_num and ctx->err_msg) if a - * string contains invalid UTF-8 or a nul-byte. + * lib9p_Tmsg_unmarshal does no validation; you must run + * lib9p_Tmsg_validate() first. * - * lib9p_unmarshal does no validation; you must run lib9p_validate() - * first. - * - * @param ctx : negotiated protocol parameters, where to record errors + * @param ctx : negotiated protocol parameters * @param net_bytes : the complete message, starting with the "size[4]" * * @return ret_typ : the mesage type - * @return ret_body : the message body, must be at least lib9p_validate() bytes + * @return ret_body : the message body, must be at least lib9p_Tmsg_validate() bytes */ -void lib9p_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, - enum lib9p_msg_type *ret_typ, void *ret_body); +void lib9p_Tmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, + enum lib9p_msg_type *ret_typ, void *ret_body); /** * Marshal a `struct lib9p_msg_{typ}` structure into a byte-array. * - * lib9p_marshal does no validation; it trusts that the programmer - * won't give it garbage input. However, just as it doesn't marshal - * struct fields that aren't in ctx->version, it won't marshal - * bitfield bits that aren't in ctx->version; it applies a + * lib9p_Tmsg_marshal does no validation; it trusts that the + * programmer won't give it garbage input. However, just as it + * doesn't marshal struct fields that aren't in ctx->version, it won't + * marshal bitfield bits that aren't in ctx->version; it applies a * version-specific mask to bitfields. * * @param ctx : negotiated protocol parameters, where to record errors @@ -112,47 +128,79 @@ void lib9p_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, * * @errno LINUX_ERANGE: reply does not fit in ctx->max_msg_size */ -bool lib9p_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, - uint8_t *ret_bytes); +bool lib9p_Tmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, + struct lib9p_Tmsg_send_buf *ret); + +/* main R-message functions ***************************************************/ + +/** Same as above, but for R-messages instead of T-messages. */ + +ssize_t lib9p_Rmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes); +void lib9p_Rmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, + enum lib9p_msg_type *ret_typ, void *ret_body); +bool lib9p_Rmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, + struct lib9p_Rmsg_send_buf *ret); + /* `struct lib9p_stat` helpers ************************************************/ /** Assert that a `struct lib9p_stat` object looks valid. */ -static inline void lib9p_assert_stat(struct lib9p_stat stat) { - assert( ((bool)(stat.file_mode & LIB9P_DM_DIR )) == ((bool)(stat.file_qid.type & LIB9P_QT_DIR )) ); - assert( ((bool)(stat.file_mode & LIB9P_DM_APPEND )) == ((bool)(stat.file_qid.type & LIB9P_QT_APPEND )) ); - assert( ((bool)(stat.file_mode & LIB9P_DM_EXCL )) == ((bool)(stat.file_qid.type & LIB9P_QT_EXCL )) ); - assert( ((bool)(stat.file_mode & _LIB9P_DM_PLAN9_MOUNT)) == ((bool)(stat.file_qid.type & _LIB9P_QT_PLAN9_MOUNT)) ); - assert( ((bool)(stat.file_mode & LIB9P_DM_AUTH )) == ((bool)(stat.file_qid.type & LIB9P_QT_AUTH )) ); - assert( ((bool)(stat.file_mode & LIB9P_DM_TMP )) == ((bool)(stat.file_qid.type & LIB9P_QT_TMP )) ); +static inline void lib9p_stat_assert(struct lib9p_stat stat) { + assert( ((bool)(stat.file_mode & LIB9P_DM_DIR )) == ((bool)(stat.file_qid.type & LIB9P_QT_DIR )) ); + assert( ((bool)(stat.file_mode & LIB9P_DM_APPEND)) == ((bool)(stat.file_qid.type & LIB9P_QT_APPEND)) ); + assert( ((bool)(stat.file_mode & LIB9P_DM_EXCL )) == ((bool)(stat.file_qid.type & LIB9P_QT_EXCL )) ); + assert( ((bool)(stat.file_mode & LIB9P_DM_AUTH )) == ((bool)(stat.file_qid.type & LIB9P_QT_AUTH )) ); + assert( ((bool)(stat.file_mode & LIB9P_DM_TMP )) == ((bool)(stat.file_qid.type & LIB9P_QT_TMP )) ); assert( (stat.file_size == 0) || !(stat.file_mode & LIB9P_DM_DIR) ); } /** - * TODO + * Validate a message's `stat` structure. + * + * @param ctx : negotiated protocol parameters, where to record errors + * @param net_bytes : network-encoded stat structure + * @param net_size : the number of net_bytes that may be read * - * @return ret_net_size: number of bytes consumed; <=net_size - * @return ret_host_size: number of bytes that lib9p_unmarshal_stat would take + * @return ret_net_size : number of bytes consumed; <=net_size + * @return ret_host_size : number of bytes that lib9p_stat_unmarshal would take * @return whether there was an error */ -bool lib9p_validate_stat(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, - uint32_t *ret_net_size, ssize_t *ret_host_size); +bool lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, + uint32_t *ret_net_size, ssize_t *ret_host_size); /** - * TODO + * Unmarshal the 9P `net_bytes` into the C struct `ret_obj`. * - * @return ret_obj : where to put the stat object itself - * @return ret_extra : where to put strings for the stat object - * @return consumed net_bytes + * lib9p_stat_unmarshal does no validation; you must run + * lib9p_stat_validate() first. + * + * @param ctx : negotiated protocol parameters + * @param net_bytes : network-encoded stat structure + * + * @return ret : the stat object, must be at least lib9p_stat_validate()->ret_net_size bytes */ -uint32_t lib9p_unmarshal_stat(struct lib9p_ctx *ctx, uint8_t *net_bytes, - struct lib9p_stat *ret_obj, void *ret_extra); +void lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, + struct lib9p_stat *ret); /** + * Marhsal a `struct lib9p_stat` structure into a byte-array. + * + * lib9p_Tmsg_marshal does no validation; it trusts that the + * programmer won't give it garbage input. However, just as it + * doesn't marshal struct fields that aren't in ctx->version, it won't + * marshal bitfield bits that aren't in ctx->version; it applies a + * version-specific mask to bitfields. + * + * @param ctx : negotiated protocol parameters, where to record errors + * @param max_net_size : the maximum network-encoded size to allow + * @param obj : the message to encode + * * @return ret_bytes: the buffer to encode into * @return the number of bytes written, or 0 if the stat object does not fit in max_net_size + * + * @errno LINUX_ERANGE: reply does not fit in max_net_size */ -uint32_t lib9p_marshal_stat(struct lib9p_ctx *ctx, uint32_t max_net_size, struct lib9p_stat *obj, +uint32_t lib9p_stat_marshal(struct lib9p_ctx *ctx, uint32_t max_net_size, struct lib9p_stat *obj, uint8_t *ret_bytes); #endif /* _LIB9P_9P_H_ */ diff --git a/lib9p/include/lib9p/linux-errno.h.gen b/lib9p/include/lib9p/linux-errno.h.gen index 8f4e0c8..2c736a2 100755 --- a/lib9p/include/lib9p/linux-errno.h.gen +++ b/lib9p/include/lib9p/linux-errno.h.gen @@ -1,7 +1,7 @@ #!/usr/bin/env python # lib9p/linux-errno.h.gen - Generate a C header from a list of errno numbers # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later import sys @@ -13,7 +13,7 @@ def print_errnos() -> None: ) errnos: dict[str, tuple[int, str]] = {} for txtlist in sys.argv[1:]: - with open(txtlist, "r") as fh: + with open(txtlist, "r", encoding="utf-8") as fh: for line in fh: if line.startswith("#"): print(f"/* {line[1:].strip()} */") @@ -26,12 +26,10 @@ def print_errnos() -> None: print("#ifndef _LIB9P_LINUX_ERRNO_H_") print("#define _LIB9P_LINUX_ERRNO_H_") print() - namelen = max(len(name) for name in errnos.keys()) + namelen = max(len(name) for name in errnos) numlen = max(len(str(num)) for (num, desc) in errnos.values()) - for name in errnos: - print( - f"#define LINUX_{name.ljust(namelen)} {str(errnos[name][0]).rjust(numlen)} /* {errnos[name][1]} */" - ) + for name, [num, msg] in errnos.items(): + print(f"#define LINUX_{name:<{namelen}} {num:>{numlen}} /* {msg} */") print() print("#endif /* _LIB9P_LINUX_ERRNO_H_ */") diff --git a/lib9p/include/lib9p/srv.h b/lib9p/include/lib9p/srv.h index eb0e4f4..ec47142 100644 --- a/lib9p/include/lib9p/srv.h +++ b/lib9p/include/lib9p/srv.h @@ -1,6 +1,6 @@ /* lib9p/srv.h - 9P server * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -11,22 +11,24 @@ #include <libcr_ipc/rpc.h> #include <libcr_ipc/chan.h> #include <libhw/generic/net.h> +#include <libmisc/assert.h> #include <libmisc/private.h> +#include <libobj/obj.h> #include <lib9p/9p.h> /* context ********************************************************************/ -CR_CHAN_DECLARE(_lib9p_srv_flushch, bool) +CR_CHAN_DECLARE(_lib9p_srv_flushch, bool); struct lib9p_srv_ctx { struct lib9p_ctx basectx; uint32_t uid; - char *uname; + struct lib9p_s uname; - BEGIN_PRIVATE(LIB9P_SRV_H) + BEGIN_PRIVATE(LIB9P_SRV_H); _lib9p_srv_flushch_t _flushch; - END_PRIVATE(LIB9P_SRV_H) + END_PRIVATE(LIB9P_SRV_H); }; bool lib9p_srv_flush_requested(struct lib9p_srv_ctx *ctx); @@ -35,86 +37,143 @@ int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx); /* interface definitions ******************************************************/ -struct lib9p_srv_file_vtable; - -struct __lib9p_srv_file; -typedef struct __lib9p_srv_file { - struct lib9p_srv_file_vtable *vtable; - - BEGIN_PRIVATE(LIB9P_SRV_H) - /* Managed by srv.c, but should be cloned by ->vtable->clone(). */ - struct __lib9p_srv_file *_parent_dir; /* clone this - - /* Managed by srv.c, but should be initialized to 0 by ->vtable->clone(). */ - /* ref type 1: an entry in fidmap - * ref type 2: ->_parent_dir of another file */ - unsigned int _refcount; - END_PRIVATE(LIB9P_SRV_H) -} implements_lib9p_srv_file; - -struct lib9p_srv_file_vtable { - /* all - resource management */ - implements_lib9p_srv_file *(*clone )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *); - void (*free )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *); - - /* all - syscalls */ - uint32_t (*io )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, - lib9p_o_t flags); - struct lib9p_stat (*stat )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *); - void (*wstat )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, - struct lib9p_stat new); - void (*remove )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *); - - /* directories - base */ - implements_lib9p_srv_file *(*dopen )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, - char *childname); - implements_lib9p_srv_file *(*dcreate)(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, - char *childname, - lib9p_dm_t perm, lib9p_o_t flags); - - /* directories - once opened */ - size_t /* <- obj cnt */ (*dread )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, - uint8_t *buf, - uint32_t byte_count, /* <- num bytes */ - size_t obj_offset); /* <- starting at this object */ - - /* non-directories - once opened */ - uint32_t (*pread )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, - void *buf, - uint32_t byte_count, - uint64_t byte_offset); - uint32_t (*pwrite )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, - void *buf, - uint32_t byte_count, - uint64_t byte_offset); -}; +lo_interface lib9p_srv_fio; +lo_interface lib9p_srv_dio; + +/* FIXME: I don't like that the pointers returned by stat() and + * pread() have to remain live after they return. Perhaps a + * `respond()`-callback? But that just reads as gross in C. + * + * FIXME: It would be nice if pread() could return more than 1 iovec. + */ +#define lib9p_srv_file_LO_IFACE \ + /* resource management **********************************************/ \ + \ + /** \ + * free() is be called when all FIDs associated with the file are \ + * clunked. \ + * \ + * free() MUST NOT error. \ + */ \ + LO_FUNC(void , free ) \ + \ + /** \ + * qid() is called frequently and returns the current QID of the file. \ + * The .path field MUST never change, the .type field may change in \ + * response to wstat() calls (but the QT_DIR bit MUST NOT change), and \ + * the .vers field may change frequently in response to any number of \ + * things (wstat(), write(), or non-9P events). \ + * \ + * qid() MUST NOT error. \ + */ \ + LO_FUNC(struct lib9p_qid , qid ) \ + \ + /* non-"opened" generic I/O *****************************************/ \ + \ + LO_FUNC(struct lib9p_stat , stat , struct lib9p_srv_ctx *) \ + LO_FUNC(void , wstat , struct lib9p_srv_ctx *, \ + struct lib9p_stat new) \ + LO_FUNC(void , remove , struct lib9p_srv_ctx *) \ + \ + /* non-"opened" directory I/O ***************************************/ \ + \ + LO_FUNC(lo_interface lib9p_srv_file, dwalk , struct lib9p_srv_ctx *, \ + struct lib9p_s childname) \ + LO_FUNC(lo_interface lib9p_srv_file, dcreate, struct lib9p_srv_ctx *, \ + struct lib9p_s childname, \ + lib9p_dm_t perm, \ + lib9p_o_t flags) \ + \ + /* open() for I/O ***************************************************/ \ + \ + LO_FUNC(lo_interface lib9p_srv_fio , fopen , struct lib9p_srv_ctx *, \ + bool rd, bool wr, \ + bool trunc) \ + LO_FUNC(lo_interface lib9p_srv_dio , dopen , struct lib9p_srv_ctx *) +LO_INTERFACE(lib9p_srv_file); + +#define lib9p_srv_fio_LO_IFACE \ + LO_FUNC(struct lib9p_qid , qid ) \ + LO_FUNC(void , iofree ) \ + LO_FUNC(uint32_t , iounit ) \ + LO_FUNC(void , pread , struct lib9p_srv_ctx *, \ + uint32_t byte_count, \ + uint64_t byte_offset, \ + struct iovec *ret) \ + LO_FUNC(uint32_t , pwrite , struct lib9p_srv_ctx *, \ + void *buf, \ + uint32_t byte_count, \ + uint64_t byte_offset) +LO_INTERFACE(lib9p_srv_fio); + +/* FIXME: The dio interface just feels clunky. I'm not in a rush to + * change it because util9p_static_dir is already implemented and I + * don't anticipate the sbc-harness needing another dio + * implementation. But if I wanted lib9p to be used outside of + * sbc-harness, this is one of the first things that I'd want to + * change. + */ +#define lib9p_srv_dio_LO_IFACE \ + LO_FUNC(struct lib9p_qid , qid ) \ + LO_FUNC(void , iofree ) \ + LO_FUNC(size_t /* <- obj cnt */ , dread , struct lib9p_srv_ctx *, \ + uint8_t *buf, \ + /* num bytes -> */ uint32_t byte_count, \ + /* starting at this object -> */ size_t obj_offset) +LO_INTERFACE(lib9p_srv_dio); + +#define LIB9P_SRV_NOTDIR(TYP, NAM) \ + static lo_interface lib9p_srv_file NAM##_dwalk (TYP *, struct lib9p_srv_ctx *, struct lib9p_s) { assert_notreached("not a directory"); } \ + static lo_interface lib9p_srv_file NAM##_dcreate(TYP *, struct lib9p_srv_ctx *, struct lib9p_s, lib9p_dm_t, lib9p_o_t) { assert_notreached("not a directory"); } \ + static lo_interface lib9p_srv_dio NAM##_dopen (TYP *, struct lib9p_srv_ctx *) { assert_notreached("not a directory"); } + +#define LIB9P_SRV_NOTFILE(TYP, NAM) \ + static lo_interface lib9p_srv_fio NAM##_fopen (TYP *, struct lib9p_srv_ctx *, bool, bool, bool) { assert_notreached("not a file"); } /* main server entrypoints ****************************************************/ -CR_RPC_DECLARE(_lib9p_srv_reqch, struct _lib9p_srv_req *, bool) +CR_RPC_DECLARE(_lib9p_srv_reqch, struct _lib9p_srv_req *, bool); struct lib9p_srv { /* Things you provide */ - void /*TODO*/ (*auth )(struct lib9p_srv_ctx *, char *treename); /* optional */ - implements_lib9p_srv_file *(*rootdir)(struct lib9p_srv_ctx *, char *treename); + void /*TODO*/ (*auth )(struct lib9p_srv_ctx *, struct lib9p_s treename); /* optional */ + lo_interface lib9p_srv_file (*rootdir)(struct lib9p_srv_ctx *, struct lib9p_s treename); /* For internal use */ - BEGIN_PRIVATE(LIB9P_SRV_H) + BEGIN_PRIVATE(LIB9P_SRV_H); + unsigned int readers; + unsigned int writers; _lib9p_srv_reqch_t _reqch; - END_PRIVATE(LIB9P_SRV_H) + END_PRIVATE(LIB9P_SRV_H); }; /** + * In an infinite loop, accept a connection and read messages from it until + * close; dispatching requests to a pool of lib9p_srv_write_cr() coroutines + * with the same `srv`. + * * Will just close the connection if a T-message has a size[4] <7. * + * @param srv: The server configuration and state; has an associated pool of + * lib9p_srv_write_cr() coroutines. + * @param listener: The listener object to accept connections from. + * * @errno LINUX_EMSGSIZE T-message has size[4] bigger than max_msg_size * @errno LINUX_EDOM Tversion specified an impossibly small max_msg_size * @errno LINUX_EOPNOTSUPP T-message has an R-message type, or an unrecognized T-message type * @errno LINUX_EBADMSG T-message has wrong size[4] for its content, or has invalid UTF-8 * @errno LINUX_ERANGE R-message does not fit into max_msg_size */ +[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener); -[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, implements_net_stream_listener *listener); +/** + * Service requests to the `struct lib9p_srv *srv` argument that have been + * read by lib9p_srv_read_cr(). + * + * @param struct lib9p_srv *srv: The server configuration and state; has an + * associated pool of lib9p_srv_read_cr() + * coroutines. + */ COROUTINE lib9p_srv_write_cr(void *_srv); #endif /* _LIB9P_SRV_H_ */ diff --git a/lib9p/internal.h b/lib9p/internal.h deleted file mode 100644 index 57f7aa7..0000000 --- a/lib9p/internal.h +++ /dev/null @@ -1,179 +0,0 @@ -/* lib9p/internal.h - TODO - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _LIB9P_INTERNAL_H_ -#define _LIB9P_INTERNAL_H_ - -#include <stddef.h> /* for size_t */ -#include <limits.h> /* for SSIZE_MAX */ - -#include <lib9p/9p.h> - -/* configuration **************************************************************/ - -#include "config.h" - -#ifndef CONFIG_9P_MAX_MSG_SIZE - #error config.h must define CONFIG_9P_MAX_MSG_SIZE -#endif -#ifndef CONFIG_9P_MAX_HOSTMSG_SIZE - #error config.h must define CONFIG_9P_MAX_HOSTMSG_SIZE -#endif -#ifndef CONFIG_9P_MAX_FIDS - #error config.h must define CONFIG_9P_MAX_FIDS -#endif -#ifndef CONFIG_9P_MAX_REQS - #error config.h must define CONFIG_9P_MAX_REQS -#endif -#ifndef CONFIG_9P_MAX_ERR_SIZE - #error config.h must define CONFIG_9P_MAX_ERR_SIZE -#endif - -static_assert(CONFIG_9P_MAX_ERR_SIZE <= UINT16_MAX); -static_assert(CONFIG_9P_MAX_MSG_SIZE <= CONFIG_9P_MAX_HOSTMSG_SIZE); -static_assert(CONFIG_9P_MAX_HOSTMSG_SIZE <= SSIZE_MAX); - -/* C language *****************************************************************/ - -#define UNUSED(name) -#define ALWAYS_INLINE [[gnu::always_inline]] inline -#define FLATTEN [[gnu::flatten]] -#define ARRAY_LEN(arr) (sizeof(arr)/sizeof((arr)[0])) -#define CAT2(a, b) a##b -#define CAT3(a, b, c) a##b##c - -/* specialized contexts *******************************************************/ - -struct _validate_ctx { - struct lib9p_ctx *ctx; - uint32_t net_size; - uint8_t *net_bytes; - - uint32_t net_offset; - /* Increment `host_extra` to pre-allocate space that is - * "extra" beyond sizeof(). */ - size_t host_extra; -}; -typedef bool (*_validate_fn_t)(struct _validate_ctx *ctx); - -struct _unmarshal_ctx { - struct lib9p_ctx *ctx; - uint8_t *net_bytes; - - uint32_t net_offset; - /* `extra` points to the beginning of unallocated space. */ - void *extra; -}; -typedef void (*_unmarshal_fn_t)(struct _unmarshal_ctx *ctx, void *out); - -struct _marshal_ctx { - struct lib9p_ctx *ctx; - - uint8_t *net_bytes; - uint32_t net_offset; -}; -typedef bool (*_marshal_fn_t)(struct _marshal_ctx *ctx, void *host_val); - -/* tables *********************************************************************/ - -struct _table_msg { - char *name; - size_t basesize; - _validate_fn_t validate; - _unmarshal_fn_t unmarshal; - _marshal_fn_t marshal; -}; - -struct _table_version { - struct _table_msg msgs[0x100]; -}; - -extern struct _table_version _lib9p_versions[LIB9P_VER_NUM]; - -bool _lib9p_validate_stat(struct _validate_ctx *ctx); -void _lib9p_unmarshal_stat(struct _unmarshal_ctx *ctx, struct lib9p_stat *out); -bool _lib9p_marshal_stat(struct _marshal_ctx *ctx, struct lib9p_stat *val); - -/* unmarshal utilities ********************************************************/ - -ALWAYS_INLINE static uint8_t decode_u8le(uint8_t *in) { - return in[0]; -} -ALWAYS_INLINE static uint16_t decode_u16le(uint8_t *in) { - return (((uint16_t)(in[0])) << 0) - | (((uint16_t)(in[1])) << 8) - ; -} -ALWAYS_INLINE static uint32_t decode_u32le(uint8_t *in) { - return (((uint32_t)(in[0])) << 0) - | (((uint32_t)(in[1])) << 8) - | (((uint32_t)(in[2])) << 16) - | (((uint32_t)(in[3])) << 24) - ; -} -ALWAYS_INLINE static uint64_t decode_u64le(uint8_t *in) { - return (((uint64_t)(in[0])) << 0) - | (((uint64_t)(in[1])) << 8) - | (((uint64_t)(in[2])) << 16) - | (((uint64_t)(in[3])) << 24) - | (((uint64_t)(in[4])) << 32) - | (((uint64_t)(in[5])) << 40) - | (((uint64_t)(in[6])) << 48) - | (((uint64_t)(in[7])) << 56) - ; -} - -static inline bool _is_valid_utf8(uint8_t *str, size_t len, bool forbid_nul) { - uint32_t ch; - uint8_t chlen; - assert(str); - for (size_t pos = 0; pos < len;) { - if ((str[pos] & 0b10000000) == 0b00000000) { ch = str[pos] & 0b01111111; chlen = 1; } - else if ((str[pos] & 0b11100000) == 0b11000000) { ch = str[pos] & 0b00011111; chlen = 2; } - else if ((str[pos] & 0b11110000) == 0b11100000) { ch = str[pos] & 0b00001111; chlen = 3; } - else if ((str[pos] & 0b11111000) == 0b11110000) { ch = str[pos] & 0b00000111; chlen = 4; } - else return false; - if ((ch == 0 && (chlen != 1 || forbid_nul)) || pos + chlen > len) return false; - for (uint8_t i = 1; i < chlen; i++) { - if ((str[pos+i] & 0b11000000) != 0b10000000) return false; - ch = (ch << 6) | (str[pos+i] & 0b00111111); - } - if (ch > 0x10FFFF) return false; - pos += chlen; - } - return true; -} - -#define is_valid_utf8(str, len) _is_valid_utf8(str, len, false) -#define is_valid_utf8_without_nul(str, len) _is_valid_utf8(str, len, true) - -/* marshal utilities **********************************************************/ - -ALWAYS_INLINE static void encode_u8le(uint8_t in, uint8_t *out) { - out[0] = in; -} -ALWAYS_INLINE static void encode_u16le(uint16_t in, uint8_t *out) { - out[0] = (uint8_t)((in >> 0) & 0xFF); - out[1] = (uint8_t)((in >> 8) & 0xFF); -} -ALWAYS_INLINE static void encode_u32le(uint32_t in, uint8_t *out) { - out[0] = (uint8_t)((in >> 0) & 0xFF); - out[1] = (uint8_t)((in >> 8) & 0xFF); - out[2] = (uint8_t)((in >> 16) & 0xFF); - out[3] = (uint8_t)((in >> 24) & 0xFF); -} -ALWAYS_INLINE static void encode_u64le(uint64_t in, uint8_t *out) { - out[0] = (uint8_t)((in >> 0) & 0xFF); - out[1] = (uint8_t)((in >> 8) & 0xFF); - out[2] = (uint8_t)((in >> 16) & 0xFF); - out[3] = (uint8_t)((in >> 24) & 0xFF); - out[4] = (uint8_t)((in >> 32) & 0xFF); - out[5] = (uint8_t)((in >> 40) & 0xFF); - out[6] = (uint8_t)((in >> 48) & 0xFF); - out[7] = (uint8_t)((in >> 56) & 0xFF); -} - -#endif /* _LIB9P_INTERNAL_H_ */ diff --git a/lib9p/map.h b/lib9p/map.h index f42bb2c..c5eab0f 100644 --- a/lib9p/map.h +++ b/lib9p/map.h @@ -1,11 +1,9 @@ -/* lib9p/map.h - A really dumb map/dict data structur +/* lib9p/map.h - A really dumb map/dict data structure * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include "internal.h" - /** * `#define` `NAME`, `KEY_T`, `VAL_T`, and `CAP`; then `#include * "map.h". @@ -25,10 +23,10 @@ #endif #ifndef MAP_KEY -#define MAP_KV(TNAME) CAT3(_,TNAME,_kv) -#define MAP_METHOD(TNAME, MNAME) CAT3(TNAME,_,MNAME) +#define MAP_KV(TNAME) LM_CAT3(_,TNAME,_kv) +#define MAP_METHOD(TNAME, MNAME) LM_CAT3(TNAME,_,MNAME) #define MAP_FOREACH(m, k, v) \ - for (size_t i = 0; i < ARRAY_LEN((m)->items); i++) \ + for (size_t i = 0; i < LM_ARRAY_LEN((m)->items); i++) \ if ( ({ k = (m)->items[i].key; v = &(m)->items[i].val; (m)->items[i].set; }) ) #endif @@ -58,7 +56,7 @@ struct NAME { static VAL_T *MAP_METHOD(NAME,load)(struct NAME *m, KEY_T k) { if (!m->len) return NULL; - for (size_t i = 0; i < ARRAY_LEN(m->items); i++) + for (size_t i = 0; i < LM_ARRAY_LEN(m->items); i++) if (m->items[i].set && m->items[i].key == k) return &(m->items[i].val); return NULL; @@ -74,9 +72,9 @@ static VAL_T *MAP_METHOD(NAME,store)(struct NAME *m, KEY_T k, VAL_T v) { *old = v; return old; } - if (m->len == ARRAY_LEN(m->items)) + if (m->len == LM_ARRAY_LEN(m->items)) return NULL; - for (size_t i = 0; i < ARRAY_LEN(m->items); i++) + for (size_t i = 0; i < LM_ARRAY_LEN(m->items); i++) if (!m->items[i].set) { m->len++; m->items[i].set = true; @@ -94,7 +92,7 @@ static VAL_T *MAP_METHOD(NAME,store)(struct NAME *m, KEY_T k, VAL_T v) { static bool MAP_METHOD(NAME,del)(struct NAME *m, KEY_T k) { if (!m->len) return NULL; - for (size_t i = 0; i < ARRAY_LEN(m->items); i++) + for (size_t i = 0; i < LM_ARRAY_LEN(m->items); i++) if (m->items[i].set && m->items[i].key == k) { m->items[i].set = false; m->len--; diff --git a/lib9p/proto.gen b/lib9p/proto.gen new file mode 100755 index 0000000..60f1347 --- /dev/null +++ b/lib9p/proto.gen @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# lib9p/proto.gen - Generate C marshalers/unmarshalers for .9p files +# defining 9P protocol variants. +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import os.path +import sys + +sys.path.insert(0, os.path.normpath(os.path.join(__file__, ".."))) +import protogen # pylint: disable=wrong-import-position + +if __name__ == "__main__": + protogen.main() diff --git a/lib9p/protogen/__init__.py b/lib9p/protogen/__init__.py new file mode 100644 index 0000000..c2c6173 --- /dev/null +++ b/lib9p/protogen/__init__.py @@ -0,0 +1,57 @@ +# lib9p/protogen/__init__.py - Generate C marshalers/unmarshalers for +# .9p files defining 9P protocol variants +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import os.path +import sys +import typing + +import idl + +from . import c, h + +# pylint: disable=unused-variable +__all__ = ["main"] + + +def main() -> None: + if typing.TYPE_CHECKING: + + class ANSIColors: + MAGENTA = "\x1b[35m" + RED = "\x1b[31m" + RESET = "\x1b[0m" + + else: + from _colorize import ANSIColors # Present in Python 3.13+ + + if len(sys.argv) < 2: + raise ValueError("requires at least 1 .9p filename") + parser = idl.Parser() + for txtname in sys.argv[1:]: + try: + parser.parse_file(txtname) + except SyntaxError as e: + print( + f"{ANSIColors.RED}{e.filename}{ANSIColors.RESET}:{ANSIColors.MAGENTA}{e.lineno}{ANSIColors.RESET}: {e.msg}", + file=sys.stderr, + ) + assert e.text + print(f"\t{e.text}", file=sys.stderr) + text_suffix = e.text.lstrip() + text_prefix = e.text[: -len(text_suffix)] + print( + f"\t{text_prefix}{ANSIColors.RED}{'~'*len(text_suffix)}{ANSIColors.RESET}", + file=sys.stderr, + ) + sys.exit(2) + versions, typs = parser.all() + outdir = os.path.normpath(os.path.join(sys.argv[0], "..")) + with open( + os.path.join(outdir, "include/lib9p/9p.generated.h"), "w", encoding="utf-8" + ) as fh: + fh.write(h.gen_h(versions, typs)) + with open(os.path.join(outdir, "9p.generated.c"), "w", encoding="utf-8") as fh: + fh.write(c.gen_c(versions, typs)) diff --git a/lib9p/protogen/c.py b/lib9p/protogen/c.py new file mode 100644 index 0000000..a6824ce --- /dev/null +++ b/lib9p/protogen/c.py @@ -0,0 +1,201 @@ +# lib9p/protogen/c.py - Generate 9p.generated.c +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import sys + +import idl + +from . import c9util, c_format, c_marshal, c_unmarshal, c_validate, cutil + +# This strives to be "general-purpose" in that it just acts on the +# *.9p inputs; but (unfortunately?) there are a few special-cases in +# this script, marked with "SPECIAL". + + +# pylint: disable=unused-variable +__all__ = ["gen_c"] + + +def gen_c(versions: set[str], typs: list[idl.UserType]) -> str: + cutil.ifdef_init() + + ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ + +#include <stdbool.h> +#include <stddef.h> /* for size_t */ +#include <inttypes.h> /* for PRI* macros */ +#include <string.h> /* for memset() */ + +#include <libmisc/assert.h> +#include <libmisc/endian.h> + +#include <lib9p/9p.h> + +#include "tables.h" +#include "utf8.h" +""" + # libobj vtables ########################################################### + ret += """ +/* libobj vtables *************************************************************/ +""" + for typ in typs: + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + ret += f"LO_IMPLEMENTATION_C(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)}, static);\n" + ret += cutil.ifdef_pop(0) + + # utilities ################################################################ + ret += """ +/* utilities ******************************************************************/ +""" + + id2typ: dict[int, idl.Message] = {} + for msg in [msg for msg in typs if isinstance(msg, idl.Message)]: + id2typ[msg.msgid] = msg + + for v in sorted(versions): + ret += f"#if CONFIG_9P_ENABLE_{v.replace('.', '_')}\n" + ret += ( + f"\t#define _is_ver_{v.replace('.', '_')}(v) (v == {c9util.ver_enum(v)})\n" + ) + ret += "#else\n" + ret += f"\t#define _is_ver_{v.replace('.', '_')}(v) false\n" + ret += "#endif\n" + ret += "\n" + ret += "/**\n" + ret += f" * is_ver(ctx, ver) is essentially `(ctx->version == {c9util.Ident('VER_')}##ver)`, but\n" + ret += f" * compiles correctly (to `false`) even if `{c9util.Ident('VER_')}##ver` isn't defined\n" + ret += " * (because `!CONFIG_9P_ENABLE_##ver`). This is useful when `||`ing\n" + ret += " * several version checks together.\n" + ret += " */\n" + ret += "#define is_ver(ctx, ver) _is_ver_##ver((ctx)->version)\n" + + # bitmasks ################################################################# + ret += """ +/* bitmasks *******************************************************************/ +""" + for typ in typs: + if not isinstance(typ, idl.Bitfield): + continue + ret += "\n" + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + ret += f"static const {c9util.typename(typ)} {typ.typname}_masks[{c9util.ver_enum('NUM')}] = {{\n" + verwidth = max(len(ver) for ver in versions) + for ver in sorted(versions): + ret += cutil.ifdef_push(2, c9util.ver_ifdef({ver})) + ret += ( + f"\t[{c9util.ver_enum(ver)}]{' '*(verwidth-len(ver))} = 0b" + + "".join( + ( + "1" + if (bit.cat == "USED" or isinstance(bit.cat, idl.BitNum)) + and ver in bit.in_versions + else "0" + ) + for bit in reversed(typ.bits) + ) + + ",\n" + ) + ret += cutil.ifdef_pop(1) + ret += "};\n" + ret += cutil.ifdef_pop(0) + + # validate_* ############################################################### + ret += c_validate.gen_c_validate(versions, typs) + + # unmarshal_* ############################################################## + ret += c_unmarshal.gen_c_unmarshal(versions, typs) + + # marshal_* ################################################################ + ret += c_marshal.gen_c_marshal(versions, typs) + + # *_format ################################################################# + ret += c_format.gen_c_format(versions, typs) + + # tables.h ################################################################# + ret += """ +/* tables.h *******************************************************************/ +""" + + ret += "\n" + ret += f"const struct {c9util.ident('_ver_tentry')} {c9util.ident('_table_ver')}[{c9util.ver_enum('NUM')}] = {{\n" + rerror = next(typ for typ in typs if typ.typname == "Rerror") + for ver in ["unknown", *sorted(versions)]: + if ver == "unknown": + min_msg_size = rerror.min_size("9P2000") # SPECIAL (initialization) + else: + ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) + min_msg_size = rerror.min_size(ver) + ret += f'\t[{c9util.ver_enum(ver)}] = {{.name="{ver}", .min_msg_size={min_msg_size}}},\n' + ret += cutil.ifdef_pop(0) + ret += "};\n" + + def msg_table( + cstruct: str, cname: str, each: str, _range: tuple[int, int, int] + ) -> str: + ret = f"const struct {c9util.ident(cstruct)} {c9util.ident(cname)}[{c9util.ver_enum('NUM')}][{hex(len(range(*_range)))}] = {{\n" + for ver in ["unknown", *sorted(versions)]: + if ver != "unknown": + ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) + ret += f"\t[{c9util.ver_enum(ver)}] = {{\n" + for n in range(*_range): + xmsg: idl.Message | None = id2typ.get(n, None) + if xmsg: + if ver == "unknown": # SPECIAL (initialization) + if xmsg.typname not in ["Tversion", "Rversion", "Rerror"]: + xmsg = None + else: + if ver not in xmsg.in_versions: + xmsg = None + if xmsg: + ret += f"\t\t{each}({xmsg.typname}),\n" + ret += "\t},\n" + ret += cutil.ifdef_pop(0) + ret += "};\n" + return ret + + ret += "\n" + ret += cutil.macro( + f"#define _MSG(typ) [{c9util.Ident('TYP_')}##typ] = {{\n" + f"\t\t.name = #typ,\n" + f"\t\t.box_as_fmt_formatter = (_box_as_fmt_formatter_fn_t)lo_box_{c9util.ident('msg_')}##typ##_as_fmt_formatter,\n" + f"\t}}\n" + ) + ret += msg_table("_msg_tentry", "_table_msg", "_MSG", (0, 0x100, 1)) + + ret += "\n" + ret += cutil.macro( + f"#define _MSG_RECV(typ) [{c9util.Ident('TYP_')}##typ/2] = {{\n" + f"\t\t.validate = validate_##typ,\n" + f"\t\t.unmarshal = (_unmarshal_fn_t)unmarshal_##typ,\n" + f"\t}}\n" + ) + ret += cutil.macro( + f"#define _MSG_SEND(typ) [{c9util.Ident('TYP_')}##typ/2] = {{\n" + f"\t\t.marshal = (_marshal_fn_t)marshal_##typ,\n" + f"\t}}\n" + ) + ret += "\n" + ret += msg_table("_recv_tentry", "_table_Tmsg_recv", "_MSG_RECV", (0, 0x100, 2)) + ret += "\n" + ret += msg_table("_recv_tentry", "_table_Rmsg_recv", "_MSG_RECV", (1, 0x100, 2)) + ret += "\n" + ret += msg_table("_send_tentry", "_table_Tmsg_send", "_MSG_SEND", (0, 0x100, 2)) + ret += "\n" + ret += msg_table("_send_tentry", "_table_Rmsg_send", "_MSG_SEND", (1, 0x100, 2)) + + ret += f""" +LM_FLATTEN ssize_t {c9util.ident('_stat_validate')}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size) {{ +\treturn validate_stat(ctx, net_size, net_bytes, ret_net_size); +}} +LM_FLATTEN void {c9util.ident('_stat_unmarshal')}(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out) {{ +\tunmarshal_stat(ctx, net_bytes, out); +}} +LM_FLATTEN bool {c9util.ident('_stat_marshal')}(struct lib9p_ctx *ctx, struct {c9util.ident('stat')} *val, struct _marshal_ret *ret) {{ +\treturn marshal_stat(ctx, val, ret); +}} +""" + + ############################################################################ + return ret diff --git a/lib9p/protogen/c9util.py b/lib9p/protogen/c9util.py new file mode 100644 index 0000000..cf91951 --- /dev/null +++ b/lib9p/protogen/c9util.py @@ -0,0 +1,134 @@ +# lib9p/protogen/c9util.py - Utilities for generating lib9p-specific C +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import re +import typing + +import idl + +# This strives to be "general-purpose" in that it just acts on the +# *.9p inputs; but (unfortunately?) there are a few special-cases in +# this script, marked with "SPECIAL". + +# pylint: disable=unused-variable +__all__ = [ + "add_prefix", + "ident", + "Ident", + "IDENT", + "ver_enum", + "ver_ifdef", + "ver_cond", + "typename", + "idl_expr", +] + +# idents ####################################################################### + + +def add_prefix(p: str, s: str) -> str: + if s.startswith("_"): + return "_" + p + s[1:] + return p + s + + +def _ident(p: str, s: str) -> str: + return add_prefix(p, s.replace(".", "_")) + + +def ident(s: str) -> str: + return _ident("lib9p_", s) + + +def Ident(s: str) -> str: + return _ident("lib9p_".upper(), s) + + +def IDENT(s: str) -> str: + return _ident("lib9p_", s).upper() + + +# versions ##################################################################### + + +def ver_enum(ver: str) -> str: + return Ident("VER_" + ver) + + +def ver_ifdef(versions: typing.Collection[str]) -> str: + return " || ".join( + f"CONFIG_9P_ENABLE_{v.replace('.', '_')}" for v in sorted(versions) + ) + + +def ver_cond(versions: typing.Collection[str]) -> str: + if len(versions) == 1: + v = next(v for v in versions) + return f"is_ver(ctx, {v.replace('.', '_')})" + return "( " + (" || ".join(ver_cond({v}) for v in sorted(versions))) + " )" + + +# misc ######################################################################### + + +def basename(typ: idl.UserType) -> str: + match typ: + case idl.Number(): + return ident(typ.typname) + case idl.Bitfield(): + return ident(typ.typname) + case idl.Message(): + return ident(f"msg_{typ.typname}") + case idl.Struct(): + return ident(typ.typname) + case _: + raise ValueError(f"not a defined type: {typ.__class__.__name__}") + + +def typename(typ: idl.Type, parent: idl.StructMember | None = None) -> str: + match typ: + case idl.Primitive(): + if typ.value == 1 and parent and parent.cnt: # SPECIAL (string) + return "[[gnu::nonstring]] char" + return f"uint{typ.value*8}_t" + case idl.Number(): + return f"{basename(typ)}_t" + case idl.Bitfield(): + return f"{basename(typ)}_t" + case idl.Message(): + return f"struct {basename(typ)}" + case idl.Struct(): + return f"struct {basename(typ)}" + case _: + raise ValueError(f"not a type: {typ.__class__.__name__}") + + +def idl_expr( + expr: idl.Expr, lookup_sym: typing.Callable[[str], str], bitwidth: int = 0 +) -> str: + ret: list[str] = [] + for tok in expr.tokens: + match tok: + case idl.ExprOp(): + ret.append(tok.op) + case idl.ExprLit(): + if bitwidth: + ret.append(f"{tok.val:#0{bitwidth}b}") + else: + ret.append(str(tok.val)) + case idl.ExprSym(): + if m := re.fullmatch(r"^u(8|16|32|64)_max$", tok.symname): + ret.append(f"UINT{m.group(1)}_MAX") + elif m := re.fullmatch(r"^s(8|16|32|64)_max$", tok.symname): + ret.append(f"INT{m.group(1)}_MAX") + else: + ret.append(lookup_sym(tok.symname)) + case idl.ExprOff(): + ret.append(lookup_sym("&" + tok.membname)) + case idl.ExprNum(): + ret.append(Ident(add_prefix(f"{tok.numname}_".upper(), tok.valname))) + case _: + assert False + return " ".join(ret) diff --git a/lib9p/protogen/c_format.py b/lib9p/protogen/c_format.py new file mode 100644 index 0000000..a1bcbf3 --- /dev/null +++ b/lib9p/protogen/c_format.py @@ -0,0 +1,142 @@ +# lib9p/protogen/c_format.py - Generate C pretty-print functions +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + + +import idl + +from . import c9util, cutil + +# This strives to be "general-purpose" in that it just acts on the +# *.9p inputs; but (unfortunately?) there are a few special-cases in +# this script, marked with "SPECIAL". + +# pylint: disable=unused-variable +__all__ = ["gen_c_format"] + + +def bf_numname(typ: idl.Bitfield, num: idl.BitNum, base: str) -> str: + prefix = f"{typ.typname}_{num.numname}_".upper() + return c9util.Ident(c9util.add_prefix(prefix, base)) + + +def gen_c_format(versions: set[str], typs: list[idl.UserType]) -> str: + ret = """ +/* *_format *******************************************************************/ +""" + for typ in typs: + ret += "\n" + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + ret += f"static void {c9util.basename(typ)}_format({c9util.typename(typ)} *self, struct fmt_state *state) {{\n" + match typ: + case idl.Number(): + if typ.vals: + ret += "\tswitch (*self) {\n" + for name in typ.vals: + ret += f"\tcase {c9util.Ident(c9util.add_prefix(f'{typ.typname}_'.upper(), name))}:\n" + ret += f'\t\tfmt_state_puts(state, "{name}");\n' + ret += "\t\tbreak;\n" + ret += "\tdefault:\n" + ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, *self);\n' + ret += "\t}\n" + else: + ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, *self);\n' + case idl.Bitfield(): + val = "*self" + if typ.typname == "dm": # SPECIAL (pretty file permissions) + val = f"(*self & ~(({c9util.typename(typ)})0777))" + ret += "\tbool empty = true;\n" + ret += "\tfmt_state_putchar(state, '(');\n" + nums: set[str] = set() + + for bit in reversed(typ.bits): + match bit.cat: + case "UNUSED" | "USED" | "RESERVED": + if bit.cat == "UNUSED": + bitname = f"1<<{bit.num}" + else: + bitname = bit.bitname + ret += f"\tif ({val} & (UINT{typ.static_size*8}_C(1)<<{bit.num})) {{\n" + ret += "\t\tif (!empty)\n" + ret += "\t\t\tfmt_state_putchar(state, '|');\n" + ret += f'\t\tfmt_state_puts(state, "{bitname}");\n' + ret += "\t\tempty = false;\n" + ret += "\t}\n" + case idl.BitNum(): + if bit.cat.numname in nums: + continue + ret += f"\tswitch ({val} & {bf_numname(typ, bit.cat, 'MASK')}) {{\n" + for name in bit.cat.vals: + ret += f"\tcase {bf_numname(typ, bit.cat, name)}:\n" + bitname = c9util.add_prefix( + f"{bit.cat.numname}_".upper(), name + ) + ret += "\t\tif (!empty)\n" + ret += "\t\t\tfmt_state_putchar(state, '|');\n" + ret += f'\t\tfmt_state_puts(state, "{bitname}");\n' + ret += "\t\tempty = false;\n" + ret += "\t\tbreak;\n" + ret += "\tdefault:\n" + ret += "\t\tif (!empty)\n" + ret += "\t\t\tfmt_state_putchar(state, '|');\n" + ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, {val} & {bf_numname(typ, bit.cat, 'MASK')});\n' + ret += "\t\tempty = false;\n" + ret += "\t}\n" + nums.add(bit.cat.numname) + if typ.typname == "dm": # SPECIAL (pretty file permissions) + ret += "\tif (!empty)\n" + ret += "\t\tfmt_state_putchar(state, '|');\n" + ret += f'\tfmt_state_printf(state, "%#04"PRIo{typ.static_size*8}, *self & 0777);\n' + else: + ret += "\tif (empty)\n" + ret += "\t\tfmt_state_putchar(state, '0');\n" + ret += "\tfmt_state_putchar(state, ')');\n" + case idl.Struct(typname="s"): # SPECIAL(string) + ret += "\t/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */\n" + ret += "#pragma GCC diagnostic push\n" + ret += '#pragma GCC diagnostic ignored "-Wformat"\n' + ret += '#pragma GCC diagnostic ignored "-Wformat-extra-args"\n' + ret += '\tfmt_state_printf(state, "%.*q", self->len, self->utf8);\n' + ret += "#pragma GCC diagnostic pop\n" + case idl.Struct(): # and idl.Message(): + if isinstance(typ, idl.Message): + ret += f'\tfmt_state_puts(state, "{typ.typname} {{");\n' + else: + ret += "\tfmt_state_putchar(state, '{');\n" + for member in typ.members: + if member.val: + continue + ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions)) + if member.cnt: + if member.typ.static_size == 1: # SPECIAL (data) + ret += f'\tfmt_state_puts(state, " {member.membname}=<bytedata>");\n' + continue + if isinstance(member.cnt, int): + cnt_str = str(member.cnt) + cnt_typ = "size_t" + else: + cnt_str = f"self->{member.cnt.membname}" + cnt_typ = c9util.typename(member.cnt.typ) + ret += f'\tfmt_state_puts(state, " {member.membname}=[");\n' + ret += f"\tfor ({cnt_typ} i = 0; i < {cnt_str}; i++) {{\n" + ret += "\t\tif (i)\n" + ret += '\t\t\tfmt_state_puts(state, ", ");\n' + if isinstance(member.typ, idl.Primitive): + ret += f'\t\tfmt_state_printf(state, "%"PRIu{member.typ.static_size*8}, self->{member.membname}[i]);\n' + else: + ret += f"\t\t{c9util.basename(member.typ)}_format(&self->{member.membname}[i], state);\n" + ret += "\t}\n" + ret += '\tfmt_state_puts(state, " ]");\n' + else: + ret += f'\tfmt_state_puts(state, " {member.membname}=");\n' + if isinstance(member.typ, idl.Primitive): + ret += f'\tfmt_state_printf(state, "%"PRIu{member.typ.static_size*8}, self->{member.membname});\n' + else: + ret += f"\t{c9util.basename(member.typ)}_format(&self->{member.membname}, state);\n" + ret += cutil.ifdef_pop(1) + ret += '\tfmt_state_puts(state, " }");\n' + ret += "}\n" + ret += cutil.ifdef_pop(0) + + return ret diff --git a/lib9p/protogen/c_marshal.py b/lib9p/protogen/c_marshal.py new file mode 100644 index 0000000..4dab864 --- /dev/null +++ b/lib9p/protogen/c_marshal.py @@ -0,0 +1,403 @@ +# lib9p/protogen/c_marshal.py - Generate C marshal functions +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import typing + +import idl + +from . import c9util, cutil, idlutil + +# This strives to be "general-purpose" in that it just acts on the +# *.9p inputs; but (unfortunately?) there are a few special-cases in +# this script, marked with "SPECIAL". + + +# pylint: disable=unused-variable +__all__ = ["gen_c_marshal"] + +# get_offset_expr() ############################################################ + + +class OffsetExpr: + static: int + cond: dict[frozenset[str], "OffsetExpr"] + rep: list[tuple[idlutil.Path | int, "OffsetExpr"]] + + def __init__(self) -> None: + self.static = 0 + self.rep = [] + self.cond = {} + + def add(self, other: "OffsetExpr") -> None: + self.static += other.static + self.rep += other.rep + for k, v in other.cond.items(): + if k in self.cond: + self.cond[k].add(v) + else: + self.cond[k] = v + + def gen_c( + self, + dsttyp: str, + dstvar: str, + root: str, + indent_depth: int, + loop_depth: int, + ) -> str: + oneline: list[str] = [] + multiline = "" + if self.static: + oneline.append(str(self.static)) + for cnt, sub in self.rep: + if isinstance(cnt, int): + cnt_str = str(cnt) + cnt_typ = "size_t" + else: + cnt_str = cnt.c_str(root) + cnt_typ = c9util.typename(cnt.elems[-1].typ) + if not sub.cond and not sub.rep: + if sub.static == 1: + oneline.append(cnt_str) + else: + oneline.append(f"({cnt_str})*{sub.static}") + continue + loopvar = chr(ord("i") + loop_depth) + multiline += f"{'\t'*indent_depth}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n" + multiline += sub.gen_c("", dstvar, root, indent_depth + 1, loop_depth + 1) + multiline += f"{'\t'*indent_depth}}}\n" + for vers, sub in self.cond.items(): + multiline += cutil.ifdef_push(indent_depth + 1, c9util.ver_ifdef(vers)) + multiline += f"{'\t'*indent_depth}if {c9util.ver_cond(vers)} {{\n" + multiline += sub.gen_c("", dstvar, root, indent_depth + 1, loop_depth) + multiline += f"{'\t'*indent_depth}}}\n" + multiline += cutil.ifdef_pop(indent_depth) + ret = "" + if dsttyp: + if not oneline: + oneline.append("0") + ret += f"{'\t'*indent_depth}{dsttyp} {dstvar} = {' + '.join(oneline)};\n" + elif oneline: + ret += f"{'\t'*indent_depth}{dstvar} += {' + '.join(oneline)};\n" + ret += multiline + return ret + + +type OffsetExprRecursion = typing.Callable[[idlutil.Path], idlutil.WalkCmd] + + +def get_offset_expr(typ: idl.UserType, recurse: OffsetExprRecursion) -> OffsetExpr: + if not isinstance(typ, idl.Struct): + assert typ.static_size + ret = OffsetExpr() + ret.static = typ.static_size + return ret + + class ExprStackItem(typing.NamedTuple): + path: idlutil.Path + expr: OffsetExpr + pop: typing.Callable[[], None] + + expr_stack: list[ExprStackItem] + + def pop_root() -> None: + assert False + + def pop_cond() -> None: + nonlocal expr_stack + key = frozenset(expr_stack[-1].path.elems[-1].in_versions) + if key in expr_stack[-2].expr.cond: + expr_stack[-2].expr.cond[key].add(expr_stack[-1].expr) + else: + expr_stack[-2].expr.cond[key] = expr_stack[-1].expr + expr_stack = expr_stack[:-1] + + def pop_rep() -> None: + nonlocal expr_stack + member_path = expr_stack[-1].path + member = member_path.elems[-1] + assert member.cnt + cnt: idlutil.Path | int + if isinstance(member.cnt, int): + cnt = member.cnt + else: + cnt = member_path.parent().add(member.cnt) + expr_stack[-2].expr.rep.append((cnt, expr_stack[-1].expr)) + expr_stack = expr_stack[:-1] + + def handle( + path: idlutil.Path, + ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None] | None]: + nonlocal recurse + + ret = recurse(path) + if ret != idlutil.WalkCmd.KEEP_GOING: + return ret, None + + nonlocal expr_stack + expr_stack_len = len(expr_stack) + + def pop() -> None: + nonlocal expr_stack + nonlocal expr_stack_len + while len(expr_stack) > expr_stack_len: + expr_stack[-1].pop() + + if path.elems: + child = path.elems[-1] + parent = path.elems[-2].typ if len(path.elems) > 1 else path.root + if child.in_versions < parent.in_versions: + expr_stack.append( + ExprStackItem(path=path, expr=OffsetExpr(), pop=pop_cond) + ) + if child.cnt: + expr_stack.append( + ExprStackItem(path=path, expr=OffsetExpr(), pop=pop_rep) + ) + if not isinstance(child.typ, idl.Struct): + assert child.typ.static_size + expr_stack[-1].expr.static += child.typ.static_size + return ret, pop + + expr_stack = [ + ExprStackItem(path=idlutil.Path(typ), expr=OffsetExpr(), pop=pop_root) + ] + idlutil.walk(typ, handle) + return expr_stack[0].expr + + +def go_to_end(path: idlutil.Path) -> idlutil.WalkCmd: + return idlutil.WalkCmd.KEEP_GOING + + +def go_to_tok(name: str) -> typing.Callable[[idlutil.Path], idlutil.WalkCmd]: + def ret(path: idlutil.Path) -> idlutil.WalkCmd: + if len(path.elems) == 1 and path.elems[0].membname == name: + return idlutil.WalkCmd.ABORT + return idlutil.WalkCmd.KEEP_GOING + + return ret + + +# Generate .c ################################################################## + + +def gen_c_marshal(versions: set[str], typs: list[idl.UserType]) -> str: + ret = """ +/* marshal_* ******************************************************************/ + +""" + ret += cutil.macro( + "#define MARSHAL_BYTES_ZEROCOPY(ctx, data, len)\n" + "\tif (ret->net_iov[ret->net_iov_cnt-1].iov_len)\n" + "\t\tret->net_iov_cnt++;\n" + "\tret->net_iov[ret->net_iov_cnt-1].iov_base = data;\n" + "\tret->net_iov[ret->net_iov_cnt-1].iov_len = len;\n" + "\tret->net_iov_cnt++;\n" + ) + ret += cutil.macro( + "#define MARSHAL_BYTES(ctx, data, len)\n" + "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" + "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" + "\tmemcpy(&ret->net_copied[ret->net_copied_size], data, len);\n" + "\tret->net_copied_size += len;\n" + "\tret->net_iov[ret->net_iov_cnt-1].iov_len += len;\n" + ) + ret += cutil.macro( + "#define MARSHAL_U8LE(ctx, val)\n" + "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" + "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" + "\tret->net_copied[ret->net_copied_size] = val;\n" + "\tret->net_copied_size += 1;\n" + "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 1;\n" + ) + ret += cutil.macro( + "#define MARSHAL_U16LE(ctx, val)\n" + "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" + "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" + "\tuint16le_encode(&ret->net_copied[ret->net_copied_size], val);\n" + "\tret->net_copied_size += 2;\n" + "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 2;\n" + ) + ret += cutil.macro( + "#define MARSHAL_U32LE(ctx, val)\n" + "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" + "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" + "\tuint32le_encode(&ret->net_copied[ret->net_copied_size], val);\n" + "\tret->net_copied_size += 4;\n" + "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 4;\n" + ) + ret += cutil.macro( + "#define MARSHAL_U64LE(ctx, val)\n" + "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" + "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" + "\tuint64le_encode(&ret->net_copied[ret->net_copied_size], val);\n" + "\tret->net_copied_size += 8;\n" + "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 8;\n" + ) + + class IndentLevel(typing.NamedTuple): + ifdef: bool # whether this is both `{` and `#if`, or just `{` + + indent_stack: list[IndentLevel] + + def ifdef_lvl() -> int: + return sum(1 if lvl.ifdef else 0 for lvl in indent_stack) + + def indent_lvl() -> int: + return len(indent_stack) + + max_size: int + + def handle( + path: idlutil.Path, + ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]: + nonlocal ret + nonlocal indent_stack + nonlocal max_size + indent_stack_len = len(indent_stack) + + def pop() -> None: + nonlocal ret + nonlocal indent_stack + nonlocal indent_stack_len + while len(indent_stack) > indent_stack_len: + if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef: + break + ret += f"{'\t'*(indent_lvl()-1)}}}\n" + if indent_stack.pop().ifdef: + ret += cutil.ifdef_pop(ifdef_lvl()) + + loopdepth = sum(1 for elem in path.elems if elem.cnt) + struct = path.elems[-1].typ if path.elems else path.root + if isinstance(struct, idl.Struct): + offsets: list[str] = [] + for member in struct.members: + if not member.val: + continue + for tok in member.val.tokens: + match tok: + case idl.ExprSym(symname="end"): + if tok.symname not in offsets: + offsets.append(tok.symname) + case idl.ExprOff(): + if f"&{tok.membname}" not in offsets: + offsets.append(f"&{tok.membname}") + for name in offsets: + name_prefix = f"offsetof{''.join('_'+m.membname for m in path.elems)}_" + if name == "end": + if not path.elems: + if max_size > cutil.UINT32_MAX: + ret += f"{'\t'*indent_lvl()}uint32_t {name_prefix}end = (uint32_t)needed_size;\n" + else: + ret += f"{'\t'*indent_lvl()}uint32_t {name_prefix}end = needed_size;\n" + continue + recurse: OffsetExprRecursion = go_to_end + else: + assert name.startswith("&") + name = name[1:] + recurse = go_to_tok(name) + expr = get_offset_expr(struct, recurse) + expr_prefix = path.c_str("val->", loopdepth) + if not expr_prefix.endswith(">"): + expr_prefix += "." + ret += expr.gen_c( + "uint32_t", + name_prefix + name, + expr_prefix, + indent_lvl(), + loopdepth, + ) + if not path.elems: + return idlutil.WalkCmd.KEEP_GOING, pop + + child = path.elems[-1] + parent = path.elems[-2].typ if len(path.elems) > 1 else path.root + if child.in_versions < parent.in_versions: + if line := cutil.ifdef_push( + ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions) + ): + ret += line + ret += ( + f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n" + ) + indent_stack.append(IndentLevel(ifdef=True)) + if child.cnt: + if isinstance(child.cnt, int): + cnt_str = str(child.cnt) + cnt_typ = "size_t" + else: + cnt_str = path.parent().add(child.cnt).c_str("val->") + cnt_typ = c9util.typename(child.cnt.typ) + if child.typ.static_size == 1: # SPECIAL (zerocopy) + if path.root.typname == "stat": # SPECIAL (stat) + ret += f"{'\t'*indent_lvl()}MARSHAL_BYTES(ctx, {path.c_str('val->')[:-3]}, {cnt_str});\n" + else: + ret += f"{'\t'*indent_lvl()}MARSHAL_BYTES_ZEROCOPY(ctx, {path.c_str('val->')[:-3]}, {cnt_str});\n" + return idlutil.WalkCmd.KEEP_GOING, pop + loopvar = chr(ord("i") + loopdepth - 1) + ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n" + indent_stack.append(IndentLevel(ifdef=False)) + if not isinstance(child.typ, idl.Struct): + if child.val: + + def lookup_sym(sym: str) -> str: + nonlocal path + if sym.startswith("&"): + sym = sym[1:] + return f"offsetof{''.join('_'+m.membname for m in path.elems[:-1])}_{sym}" + + val = c9util.idl_expr(child.val, lookup_sym) + else: + val = path.c_str("val->") + if isinstance(child.typ, idl.Bitfield): + val += f" & {child.typ.typname}_masks[ctx->version]" + ret += f"{'\t'*indent_lvl()}MARSHAL_U{child.typ.static_size*8}LE(ctx, {val});\n" + return idlutil.WalkCmd.KEEP_GOING, pop + + for typ in typs: + if not ( + isinstance(typ, idl.Message) or typ.typname == "stat" + ): # SPECIAL (include stat) + continue + assert isinstance(typ, idl.Struct) + ret += "\n" + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + ret += f"static bool marshal_{typ.typname}(struct lib9p_ctx *ctx, {c9util.typename(typ)} *val, struct _marshal_ret *ret) {{\n" + + # Pass 1 - check size + max_size = max(typ.max_size(v) for v in typ.in_versions) + + if max_size > cutil.UINT32_MAX: # SPECIAL (9P2000.e) + ret += get_offset_expr(typ, go_to_end).gen_c( + "uint64_t", "needed_size", "val->", 1, 0 + ) + ret += "\tif (needed_size > (uint64_t)(ctx->max_msg_size)) {\n" + else: + ret += get_offset_expr(typ, go_to_end).gen_c( + "uint32_t", "needed_size", "val->", 1, 0 + ) + ret += "\tif (needed_size > ctx->max_msg_size) {\n" + if isinstance(typ, idl.Message): # SPECIAL (disable for stat) + ret += '\t\tlib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")",\n' + ret += f'\t\t\t"{typ.typname}",\n' + ret += f'\t\t\tctx->version ? "negotiated" : "{'client' if typ.msgid % 2 == 0 else 'server'}",\n' + ret += "\t\t\tctx->max_msg_size);\n" + ret += "\t\treturn true;\n" + ret += "\t}\n" + + # Pass 2 - write data + indent_stack = [IndentLevel(ifdef=True)] + idlutil.walk(typ, handle) + while len(indent_stack) > 1: + ret += f"{'\t'*(indent_lvl()-1)}}}\n" + if indent_stack.pop().ifdef: + ret += cutil.ifdef_pop(ifdef_lvl()) + + # Return + ret += "\treturn false;\n" + ret += "}\n" + ret += cutil.ifdef_pop(0) + return ret diff --git a/lib9p/protogen/c_unmarshal.py b/lib9p/protogen/c_unmarshal.py new file mode 100644 index 0000000..34635f9 --- /dev/null +++ b/lib9p/protogen/c_unmarshal.py @@ -0,0 +1,138 @@ +# lib9p/protogen/c_unmarshal.py - Generate C unmarshal functions +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import typing + +import idl + +from . import c9util, cutil, idlutil + +# This strives to be "general-purpose" in that it just acts on the +# *.9p inputs; but (unfortunately?) there are a few special-cases in +# this script, marked with "SPECIAL". + + +# pylint: disable=unused-variable +__all__ = ["gen_c_unmarshal"] + + +def gen_c_unmarshal(versions: set[str], typs: list[idl.UserType]) -> str: + ret = """ +/* unmarshal_* ****************************************************************/ + +""" + ret += cutil.macro( + "#define UNMARSHAL_BYTES(ctx, data_lvalue, len)\n" + "\tdata_lvalue = (char *)&net_bytes[net_offset];\n" + "\tnet_offset += len;\n" + ) + ret += cutil.macro( + "#define UNMARSHAL_U8LE(ctx, val_lvalue)\n" + "\tval_lvalue = net_bytes[net_offset];\n" + "\tnet_offset += 1;\n" + ) + ret += cutil.macro( + "#define UNMARSHAL_U16LE(ctx, val_lvalue)\n" + "\tval_lvalue = uint16le_decode(&net_bytes[net_offset]);\n" + "\tnet_offset += 2;\n" + ) + ret += cutil.macro( + "#define UNMARSHAL_U32LE(ctx, val_lvalue)\n" + "\tval_lvalue = uint32le_decode(&net_bytes[net_offset]);\n" + "\tnet_offset += 4;\n" + ) + ret += cutil.macro( + "#define UNMARSHAL_U64LE(ctx, val_lvalue)\n" + "\tval_lvalue = uint64le_decode(&net_bytes[net_offset]);\n" + "\tnet_offset += 8;\n" + ) + + class IndentLevel(typing.NamedTuple): + ifdef: bool # whether this is both `{` and `#if`, or just `{` + + indent_stack: list[IndentLevel] + + def ifdef_lvl() -> int: + return sum(1 if lvl.ifdef else 0 for lvl in indent_stack) + + def indent_lvl() -> int: + return len(indent_stack) + + def handle( + path: idlutil.Path, + ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]: + nonlocal ret + nonlocal indent_stack + indent_stack_len = len(indent_stack) + + def pop() -> None: + nonlocal ret + nonlocal indent_stack + nonlocal indent_stack_len + while len(indent_stack) > indent_stack_len: + if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef: + break + ret += f"{'\t'*(indent_lvl()-1)}}}\n" + if indent_stack.pop().ifdef: + ret += cutil.ifdef_pop(ifdef_lvl()) + + if not path.elems: + return idlutil.WalkCmd.KEEP_GOING, pop + + child = path.elems[-1] + parent = path.elems[-2].typ if len(path.elems) > 1 else path.root + if child.in_versions < parent.in_versions: + if line := cutil.ifdef_push( + ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions) + ): + ret += line + ret += ( + f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n" + ) + indent_stack.append(IndentLevel(ifdef=True)) + if child.cnt: + if isinstance(child.cnt, int): + cnt_str = str(child.cnt) + cnt_typ = "size_t" + else: + cnt_str = path.parent().add(child.cnt).c_str("out->") + cnt_typ = c9util.typename(child.cnt.typ) + if child.typ.static_size == 1: # SPECIAL (zerocopy) + ret += f"{'\t'*indent_lvl()}UNMARSHAL_BYTES(ctx, {path.c_str('out->')[:-3]}, {cnt_str});\n" + return idlutil.WalkCmd.KEEP_GOING, pop + ret += f"{'\t'*indent_lvl()}{path.c_str('out->')[:-3]} = extra;\n" + ret += f"{'\t'*indent_lvl()}extra += sizeof({path.c_str('out->')[:-3]}[0]) * {cnt_str};\n" + loopdepth = sum(1 for elem in path.elems if elem.cnt) + loopvar = chr(ord("i") + loopdepth - 1) + ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n" + indent_stack.append(IndentLevel(ifdef=False)) + if not isinstance(child.typ, idl.Struct): + if child.val: + ret += f"{'\t'*indent_lvl()}net_offset += {child.typ.static_size};\n" + else: + ret += f"{'\t'*indent_lvl()}UNMARSHAL_U{child.typ.static_size*8}LE(ctx, {path.c_str('out->')});\n" + return idlutil.WalkCmd.KEEP_GOING, pop + + for typ in typs: + if not ( + isinstance(typ, idl.Message) or typ.typname == "stat" + ): # SPECIAL (include stat) + continue + assert isinstance(typ, idl.Struct) + ret += "\n" + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + ret += f"static void unmarshal_{typ.typname}([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) {{\n" + ret += f"\t{c9util.typename(typ)} *out = out_buf;\n" + ret += "\t[[gnu::unused]] void *extra = &out[1];\n" + ret += "\tuint32_t net_offset = 0;\n" + + indent_stack = [IndentLevel(ifdef=True)] + idlutil.walk(typ, handle) + while len(indent_stack) > 0: + ret += f"{'\t'*(indent_lvl()-1)}}}\n" + if indent_stack.pop().ifdef and indent_stack: + ret += cutil.ifdef_pop(ifdef_lvl()) + ret += cutil.ifdef_pop(0) + return ret diff --git a/lib9p/protogen/c_validate.py b/lib9p/protogen/c_validate.py new file mode 100644 index 0000000..535a750 --- /dev/null +++ b/lib9p/protogen/c_validate.py @@ -0,0 +1,299 @@ +# lib9p/protogen/c_validate.py - Generate C validation functions +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import typing + +import idl + +from . import c9util, cutil, idlutil + +# This strives to be "general-purpose" in that it just acts on the +# *.9p inputs; but (unfortunately?) there are a few special-cases in +# this script, marked with "SPECIAL". + + +# pylint: disable=unused-variable +__all__ = ["gen_c_validate"] + + +def should_save_offset(parent: idl.Struct, child: idl.StructMember) -> bool: + if child.val or child.max or isinstance(child.typ, idl.Bitfield): + return True + for sibling in parent.members: + if sibling.val: + for tok in sibling.val.tokens: + if isinstance(tok, idl.ExprOff) and tok.membname == child.membname: + return True + if sibling.max: + for tok in sibling.max.tokens: + if isinstance(tok, idl.ExprOff) and tok.membname == child.membname: + return True + return False + + +def should_save_end_offset(struct: idl.Struct) -> bool: + for memb in struct.members: + if memb.val: + for tok in memb.val.tokens: + if isinstance(tok, idl.ExprSym) and tok.symname == "end": + return True + if memb.max: + for tok in memb.max.tokens: + if isinstance(tok, idl.ExprSym) and tok.symname == "end": + return True + return False + + +def gen_c_validate(versions: set[str], typs: list[idl.UserType]) -> str: + ret = """ +/* validate_* *****************************************************************/ + +""" + ret += cutil.macro( + "#define VALIDATE_NET_BYTES(n)\n" + "\tif (__builtin_add_overflow(net_offset, n, &net_offset))\n" + "\t\t/* If needed-net-size overflowed uint32_t, then\n" + "\t\t * there's no way that actual-net-size will live up to\n" + "\t\t * that. */\n" + '\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message is too short for content");\n' + "\tif (net_offset > net_size)\n" + '\t\treturn lib9p_errorf(ctx, LINUX_EBADMSG, "message is too short for content (%"PRIu32" > %"PRIu32") @ %d", net_offset, net_size, __LINE__);\n' + ) + ret += cutil.macro( + "#define VALIDATE_NET_UTF8(n)\n" + "\t{\n" + "\t\tsize_t len = n;\n" + "\t\tVALIDATE_NET_BYTES(len);\n" + "\t\tif (!is_valid_utf8_without_nul(&net_bytes[net_offset-len], len))\n" + '\t\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message contains invalid UTF-8");\n' + "\t}\n" + ) + ret += cutil.macro( + "#define RESERVE_HOST_BYTES(n)\n" + "\tif (__builtin_add_overflow(host_size, n, &host_size))\n" + "\t\t/* If needed-host-size overflowed ssize_t, then there's\n" + "\t\t * no way that actual-net-size will live up to\n" + "\t\t * that. */\n" + '\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message is too short for content");\n' + ) + + ret += "#define GET_U8LE(off) (net_bytes[off])\n" + ret += "#define GET_U16LE(off) uint16le_decode(&net_bytes[off])\n" + ret += "#define GET_U32LE(off) uint32le_decode(&net_bytes[off])\n" + ret += "#define GET_U64LE(off) uint64le_decode(&net_bytes[off])\n" + + ret += "#define LAST_U8LE() GET_U8LE(net_offset-1)\n" + ret += "#define LAST_U16LE() GET_U16LE(net_offset-2)\n" + ret += "#define LAST_U32LE() GET_U32LE(net_offset-4)\n" + ret += "#define LAST_U64LE() GET_U64LE(net_offset-8)\n" + + class IndentLevel(typing.NamedTuple): + ifdef: bool # whether this is both `{` and `#if`, or just `{` + + indent_stack: list[IndentLevel] + + def ifdef_lvl() -> int: + return sum(1 if lvl.ifdef else 0 for lvl in indent_stack) + + def indent_lvl() -> int: + return len(indent_stack) + + incr_buf: int + + def incr_flush() -> None: + nonlocal ret + nonlocal incr_buf + if incr_buf: + ret += f"{'\t'*indent_lvl()}VALIDATE_NET_BYTES({incr_buf});\n" + incr_buf = 0 + + def gen_validate_size(path: idlutil.Path) -> None: + nonlocal ret + nonlocal incr_buf + nonlocal indent_stack + + assert path.elems + child = path.elems[-1] + parent = path.elems[-2].typ if len(path.elems) > 1 else path.root + assert isinstance(parent, idl.Struct) + + if child.in_versions < parent.in_versions: + if line := cutil.ifdef_push( + ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions) + ): + incr_flush() + ret += line + ret += ( + f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n" + ) + indent_stack.append(IndentLevel(ifdef=True)) + if should_save_offset(parent, child): + ret += f"{'\t'*indent_lvl()}uint32_t offsetof{''.join('_'+m.membname for m in path.elems)} = net_offset + {incr_buf};\n" + if child.cnt: + if isinstance(child.cnt, int): + cnt_str = str(child.cnt) + cnt_typ = "size_t" + else: + assert child.cnt.typ.static_size + incr_flush() + cnt_str = f"LAST_U{child.cnt.typ.static_size*8}LE()" + cnt_typ = c9util.typename(child.cnt.typ) + if child.membname == "utf8": # SPECIAL (string) + assert child.typ.static_size == 1 + # Yes, this is content-validation and "belongs" in + # gen_validate_content(), not here. But it's just + # easier this way. + incr_flush() + ret += f"{'\t'*indent_lvl()}VALIDATE_NET_UTF8({cnt_str});\n" + return + if child.typ.static_size == 1: # SPECIAL (zerocopy) + if isinstance(child.cnt, int): + incr_buf += child.cnt + return + incr_flush() + ret += f"{'\t'*indent_lvl()}VALIDATE_NET_BYTES({cnt_str});\n" + return + loopdepth = sum(1 for elem in path.elems if elem.cnt) + loopvar = chr(ord("i") + loopdepth - 1) + incr_flush() + ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0, cnt = {cnt_str}; {loopvar} < cnt; {loopvar}++) {{\n" + indent_stack.append(IndentLevel(ifdef=False)) + ret += f"{'\t'*indent_lvl()}RESERVE_HOST_BYTES(sizeof({c9util.typename(child.typ)}));\n" + if not isinstance(child.typ, idl.Struct): + incr_buf += child.typ.static_size + + def gen_validate_content(path: idlutil.Path) -> None: + nonlocal ret + nonlocal incr_buf + nonlocal indent_stack + + assert path.elems + child = path.elems[-1] + parent = path.elems[-2].typ if len(path.elems) > 1 else path.root + assert isinstance(parent, idl.Struct) + + def lookup_sym(sym: str) -> str: + if sym.startswith("&"): + sym = sym[1:] + return f"offsetof{''.join('_'+m.membname for m in path.elems[:-1])}_{sym}" + + if child.val: + incr_flush() + assert child.typ.static_size + nbits = child.typ.static_size * 8 + nbits = child.typ.static_size * 8 + if nbits < 32 and any( + isinstance(tok, idl.ExprSym) + and (tok.symname == "end" or tok.symname.startswith("&")) + for tok in child.val.tokens + ): + nbits = 32 + act = f"(uint{nbits}_t)GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})" + exp = f"(uint{nbits}_t)({c9util.idl_expr(child.val, lookup_sym)})" + ret += f"{'\t'*indent_lvl()}if ({act} != {exp})\n" + ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "{path} value is wrong: actual: %"PRIu{nbits}" != correct:%"PRIu{nbits},\n' + ret += f"{'\t'*(indent_lvl()+2)}{act}, {exp});\n" + if child.max: + incr_flush() + assert child.typ.static_size + nbits = child.typ.static_size * 8 + if nbits < 32 and any( + isinstance(tok, idl.ExprSym) + and (tok.symname == "end" or tok.symname.startswith("&")) + for tok in child.max.tokens + ): + nbits = 32 + act = f"(uint{nbits}_t)GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})" + exp = f"(uint{nbits}_t)({c9util.idl_expr(child.max, lookup_sym)})" + ret += f"{'\t'*indent_lvl()}if ({act} > {exp})\n" + ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "{path} value is too large: %"PRIu{nbits}" > %"PRIu{nbits},\n' + ret += f"{'\t'*(indent_lvl()+2)}{act}, {exp});\n" + if isinstance(child.typ, idl.Bitfield): + incr_flush() + nbytes = child.typ.static_size + nbits = nbytes * 8 + act = f"GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})" + ret += f"{'\t'*indent_lvl()}if ({act} & ~{child.typ.typname}_masks[ctx->version])\n" + ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in {child.typ.typname} bitfield: %#0{nbytes*2}"PRIx{nbits},\n' + ret += f"{'\t'*(indent_lvl()+2)}{act} & ~{child.typ.typname}_masks[ctx->version]);\n" + + def handle( + path: idlutil.Path, + ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]: + nonlocal ret + nonlocal incr_buf + nonlocal indent_stack + indent_stack_len = len(indent_stack) + pop_struct = path.elems[-1].typ if path.elems else path.root + pop_path = path + pop_indent_stack_len: int + + def pop() -> None: + nonlocal ret + nonlocal indent_stack + nonlocal indent_stack_len + nonlocal pop_struct + nonlocal pop_path + nonlocal pop_indent_stack_len + if isinstance(pop_struct, idl.Struct): + while len(indent_stack) > pop_indent_stack_len: + incr_flush() + ret += f"{'\t'*(indent_lvl()-1)}}}\n" + if indent_stack.pop().ifdef: + ret += cutil.ifdef_pop(ifdef_lvl()) + parent = pop_struct + path = pop_path + if should_save_end_offset(parent): + ret += f"{'\t'*indent_lvl()}uint32_t offsetof{''.join('_'+m.membname for m in path.elems)}_end = net_offset + {incr_buf};\n" + for child in parent.members: + gen_validate_content(pop_path.add(child)) + while len(indent_stack) > indent_stack_len: + if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef: + break + incr_flush() + ret += f"{'\t'*(indent_lvl()-1)}}}\n" + if indent_stack.pop().ifdef: + ret += cutil.ifdef_pop(ifdef_lvl()) + + if path.elems: + gen_validate_size(path) + + pop_indent_stack_len = len(indent_stack) + + return idlutil.WalkCmd.KEEP_GOING, pop + + for typ in typs: + if not ( + isinstance(typ, idl.Message) or typ.typname == "stat" + ): # SPECIAL (include stat) + continue + assert isinstance(typ, idl.Struct) + ret += "\n" + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + if typ.typname == "stat": # SPECIAL (stat) + ret += f"static ssize_t validate_{typ.typname}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size) {{\n" + else: + ret += f"static ssize_t validate_{typ.typname}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) {{\n" + + ret += "\tuint32_t net_offset = 0;\n" + ret += f"\tssize_t host_size = sizeof({c9util.typename(typ)});\n" + + incr_buf = 0 + indent_stack = [IndentLevel(ifdef=True)] + idlutil.walk(typ, handle) + while len(indent_stack) > 1: + incr_flush() + ret += f"{'\t'*(indent_lvl()-1)}}}\n" + if indent_stack.pop().ifdef: + ret += cutil.ifdef_pop(ifdef_lvl()) + + incr_flush() + if typ.typname == "stat": # SPECIAL (stat) + ret += "\tif (ret_net_size)\n" + ret += "\t\t*ret_net_size = net_offset;\n" + ret += "\treturn (ssize_t)host_size;\n" + ret += "}\n" + ret += cutil.ifdef_pop(0) + return ret diff --git a/lib9p/protogen/cutil.py b/lib9p/protogen/cutil.py new file mode 100644 index 0000000..8df6db9 --- /dev/null +++ b/lib9p/protogen/cutil.py @@ -0,0 +1,84 @@ +# lib9p/protogen/cutil.py - Utilities for generating C code +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +# pylint: disable=unused-variable +__all__ = [ + "UINT32_MAX", + "UINT64_MAX", + "macro", + "ifdef_init", + "ifdef_push", + "ifdef_pop", + "ifdef_leaf_is_noop", +] + +UINT32_MAX = (1 << 32) - 1 +UINT64_MAX = (1 << 64) - 1 + + +def tab_ljust(s: str, width: int) -> str: + cur = len(s.expandtabs(tabsize=8)) + if cur >= width: + return s + return s + " " * (width - cur) + + +def macro(full: str) -> str: + full = full.rstrip() + assert "\n" in full + lines = [l.rstrip() for l in full.split("\n")] + width = max(len(l.expandtabs(tabsize=8)) for l in lines[:-1]) + lines = [tab_ljust(l, width) for l in lines] + return " \\\n".join(lines).rstrip() + "\n" + + +_ifdef_stack: list[str | None] = [] + + +def ifdef_init() -> None: + global _ifdef_stack + _ifdef_stack = [] + + +def ifdef_push(n: int, _newval: str) -> str: + # Grow the stack as needed + while len(_ifdef_stack) < n: + _ifdef_stack.append(None) + + # Set some variables + parentval: str | None = None + for x in _ifdef_stack[:-1]: + if x is not None: + parentval = x + oldval = _ifdef_stack[-1] + newval: str | None = _newval + if newval == parentval: + newval = None + + # Put newval on the stack. + _ifdef_stack[-1] = newval + + # Build output. + ret = "" + if newval != oldval: + if oldval is not None: + ret += f"#endif /* {oldval} */\n" + if newval is not None: + ret += f"#if {newval}\n" + return ret + + +def ifdef_pop(n: int) -> str: + global _ifdef_stack + ret = "" + while len(_ifdef_stack) > n: + if _ifdef_stack[-1] is not None: + ret += f"#endif /* {_ifdef_stack[-1]} */\n" + _ifdef_stack = _ifdef_stack[:-1] + return ret + + +def ifdef_leaf_is_noop() -> bool: + return not _ifdef_stack[-1] diff --git a/lib9p/protogen/h.py b/lib9p/protogen/h.py new file mode 100644 index 0000000..3b33419 --- /dev/null +++ b/lib9p/protogen/h.py @@ -0,0 +1,535 @@ +# lib9p/protogen/h.py - Generate 9p.generated.h +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import sys +import typing + +import idl + +from . import c9util, cutil, idlutil + +# This strives to be "general-purpose" in that it just acts on the +# *.9p inputs; but (unfortunately?) there are a few special-cases in +# this script, marked with "SPECIAL". + +# pylint: disable=unused-variable +__all__ = ["gen_h"] + +# get_buffer_size() ############################################################ + + +class BufferSize(typing.NamedTuple): + min_size: int # really just here to sanity-check against typ.min_size(version) + exp_size: int # "expected" or max-reasonable size + max_size: int # really just here to sanity-check against typ.max_size(version) + max_copy: int + max_copy_extra: str + max_iov: int + max_iov_extra: str + + +class TmpBufferSize: + min_size: int + exp_size: int + max_size: int + max_copy: int + max_copy_extra: str + max_iov: int + max_iov_extra: str + + tmp_starts_with_copy: bool + tmp_ends_with_copy: bool + + def __init__(self) -> None: + self.min_size = 0 + self.exp_size = 0 + self.max_size = 0 + self.max_copy = 0 + self.max_copy_extra = "" + self.max_iov = 0 + self.max_iov_extra = "" + self.tmp_starts_with_copy = False + self.tmp_ends_with_copy = False + + +def _get_buffer_size(typ: idl.Type, version: str) -> TmpBufferSize: + assert isinstance(typ, idl.Primitive) or (version in typ.in_versions) + + ret = TmpBufferSize() + + if not isinstance(typ, idl.Struct): + assert typ.static_size + ret.min_size = typ.static_size + ret.exp_size = typ.static_size + ret.max_size = typ.static_size + ret.max_copy = typ.static_size + ret.max_iov = 1 + ret.tmp_starts_with_copy = True + ret.tmp_ends_with_copy = True + return ret + + def handle(path: idlutil.Path) -> tuple[idlutil.WalkCmd, None]: + nonlocal ret + if path.elems: + child = path.elems[-1] + if version not in child.in_versions: + return idlutil.WalkCmd.DONT_RECURSE, None + if child.cnt: + if child.typ.static_size == 1: # SPECIAL (zerocopy) + ret.max_iov += 1 + # HEURISTIC: 27 for strings (max-strlen from 9P1), 8KiB for other data + ret.exp_size += 27 if child.membname == "utf8" else 8192 + ret.max_size += child.max_cnt + ret.tmp_ends_with_copy = False + return idlutil.WalkCmd.DONT_RECURSE, None + sub = _get_buffer_size(child.typ, version) + ret.exp_size += sub.exp_size * 16 # HEURISTIC: MAXWELEM + ret.max_size += sub.max_size * child.max_cnt + if child.membname == "wname" and path.root.typname in ( + "Tsread", + "Tswrite", + ): # SPECIAL (9P2000.e) + assert ret.tmp_ends_with_copy + assert sub.tmp_starts_with_copy + assert not sub.tmp_ends_with_copy + ret.max_copy_extra = ( + f" + (CONFIG_9P_MAX_9P2000_e_WELEM * {sub.max_copy})" + ) + ret.max_iov_extra = ( + f" + (CONFIG_9P_MAX_9P2000_e_WELEM * {sub.max_iov})" + ) + ret.max_iov -= 1 + else: + ret.max_copy += sub.max_copy * child.max_cnt + if sub.max_iov == 1 and sub.tmp_starts_with_copy: # is purely copy + ret.max_iov += 1 + else: # contains zero-copy segments + ret.max_iov += sub.max_iov * child.max_cnt + if ret.tmp_ends_with_copy and sub.tmp_starts_with_copy: + # we can merge this one + ret.max_iov -= 1 + if ( + sub.tmp_ends_with_copy + and sub.tmp_starts_with_copy + and sub.max_iov > 1 + ): + # we can merge these + ret.max_iov -= child.max_cnt - 1 + ret.tmp_ends_with_copy = sub.tmp_ends_with_copy + return idlutil.WalkCmd.DONT_RECURSE, None + if not isinstance(child.typ, idl.Struct): + assert child.typ.static_size + if not ret.tmp_ends_with_copy: + if ret.max_size == 0: + ret.tmp_starts_with_copy = True + ret.max_iov += 1 + ret.tmp_ends_with_copy = True + ret.min_size += child.typ.static_size + ret.exp_size += child.typ.static_size + ret.max_size += child.typ.static_size + ret.max_copy += child.typ.static_size + return idlutil.WalkCmd.KEEP_GOING, None + + idlutil.walk(typ, handle) + assert ret.min_size == typ.min_size(version) + assert ret.max_size == typ.max_size(version) + return ret + + +def get_buffer_size(typ: idl.Type, version: str) -> BufferSize: + tmp = _get_buffer_size(typ, version) + return BufferSize( + min_size=tmp.min_size, + exp_size=tmp.exp_size, + max_size=tmp.max_size, + max_copy=tmp.max_copy, + max_copy_extra=tmp.max_copy_extra, + max_iov=tmp.max_iov, + max_iov_extra=tmp.max_iov_extra, + ) + + +# Generate .h ################################################################## + + +def gen_h(versions: set[str], typs: list[idl.UserType]) -> str: + cutil.ifdef_init() + + ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ + +#ifndef _LIB9P_9P_H_ +\t#error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead +#endif + +#include <stdint.h> /* for uint{{n}}_t types */ + +#include <libfmt/fmt.h> /* for fmt_formatter */ +#include <libhw/generic/net.h> /* for struct iovec */ +""" + + id2typ: dict[int, idl.Message] = {} + for msg in [msg for msg in typs if isinstance(msg, idl.Message)]: + id2typ[msg.msgid] = msg + + ret += """ +/* config *********************************************************************/ + +#include "config.h" +""" + for ver in sorted(versions): + ret += "\n" + ret += f"#ifndef {c9util.ver_ifdef({ver})}\n" + ret += f"\t#error config.h must define {c9util.ver_ifdef({ver})}\n" + if ver == "9P2000.e": # SPECIAL (9P2000.e) + ret += "#else\n" + ret += f"\t#if {c9util.ver_ifdef({ver})}\n" + ret += "\t\t#ifndef CONFIG_9P_MAX_9P2000_e_WELEM\n" + ret += f"\t\t\t#error if {c9util.ver_ifdef({ver})} then config.h must define CONFIG_9P_MAX_9P2000_e_WELEM\n" + ret += "\t\t#endif\n" + ret += "\t\tstatic_assert(CONFIG_9P_MAX_9P2000_e_WELEM > 0);\n" + ret += "\t#endif\n" + ret += "#endif\n" + + ret += f""" +/* enum version ***************************************************************/ + +enum {c9util.ident('version')} {{ +""" + fullversions = ["unknown = 0", *sorted(versions)] + verwidth = max(len(v) for v in fullversions) + for ver in fullversions: + if ver in versions: + ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) + ret += f"\t{c9util.ver_enum(ver)}," + ret += (" " * (verwidth - len(ver))) + ' /* "' + ver.split()[0] + '" */\n' + ret += cutil.ifdef_pop(0) + ret += f"\t{c9util.ver_enum('NUM')},\n" + ret += "};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, enum {c9util.ident('version')}, {c9util.ident('version')});\n" + + ret += """ +/* enum msg_type **************************************************************/ + +""" + ret += f"enum {c9util.ident('msg_type')} {{ /* uint8_t */\n" + namewidth = max(len(msg.typname) for msg in typs if isinstance(msg, idl.Message)) + for n in range(0x100): + if n not in id2typ: + continue + msg = id2typ[n] + ret += cutil.ifdef_push(1, c9util.ver_ifdef(msg.in_versions)) + ret += f"\t{c9util.Ident(f'TYP_{msg.typname:<{namewidth}}')} = {msg.msgid},\n" + ret += cutil.ifdef_pop(0) + ret += "};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, enum {c9util.ident('msg_type')}, {c9util.ident('msg_type')});\n" + + ret += """ +/* payload types **************************************************************/ +""" + + def per_version_comment( + typ: idl.UserType, fn: typing.Callable[[idl.UserType, str], str] + ) -> str: + lines: dict[str, str] = {} + for version in sorted(typ.in_versions): + lines[version] = fn(typ, version) + if len(set(lines.values())) == 1: + for _, line in lines.items(): + return f"/* {line} */\n" + assert False + else: + ret = "" + v_width = max(len(c9util.ver_enum(v)) for v in typ.in_versions) + for version, line in lines.items(): + ret += f"/* {c9util.ver_enum(version):<{v_width}}: {line} */\n" + return ret + + for typ in idlutil.topo_sorted(typs): + ret += "\n" + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + + def sum_size(typ: idl.UserType, version: str) -> str: + sz = get_buffer_size(typ, version) + assert ( + sz.min_size <= sz.exp_size + and sz.exp_size <= sz.max_size + and sz.max_size < cutil.UINT64_MAX + ) + ret = "" + if sz.min_size == sz.max_size: + ret += f"size = {sz.min_size:,}" + else: + ret += f"min_size = {sz.min_size:,} ; exp_size = {sz.exp_size:,} ; max_size = {sz.max_size:,}" + if sz.max_size > cutil.UINT32_MAX: + ret += " (warning: >UINT32_MAX)" + ret += f" ; max_iov = {sz.max_iov:,}{sz.max_iov_extra} ; max_copy = {sz.max_copy:,}{sz.max_copy_extra}" + return ret + + ret += per_version_comment(typ, sum_size) + + match typ: + case idl.Number(): + ret += gen_number(typ) + case idl.Bitfield(): + ret += gen_bitfield(typ) + case idl.Struct(): # and idl.Message(): + ret += gen_struct(typ) + ret += cutil.ifdef_pop(0) + + ret += """ +/* containers *****************************************************************/ +""" + ret += "\n" + ret += f"#define {c9util.IDENT('_MAX')}(a, b) ((a) > (b)) ? (a) : (b)\n" + + tmsg_max_iov: dict[str, int] = {} + tmsg_max_copy: dict[str, int] = {} + rmsg_max_iov: dict[str, int] = {} + rmsg_max_copy: dict[str, int] = {} + for typ in typs: + if not isinstance(typ, idl.Message): + continue + if typ.typname in ("Tsread", "Tswrite"): # SPECIAL (9P2000.e) + continue + max_iov = tmsg_max_iov if typ.msgid % 2 == 0 else rmsg_max_iov + max_copy = tmsg_max_copy if typ.msgid % 2 == 0 else rmsg_max_copy + for version in typ.in_versions: + if version not in max_iov: + max_iov[version] = 0 + max_copy[version] = 0 + sz = get_buffer_size(typ, version) + if sz.max_iov > max_iov[version]: + max_iov[version] = sz.max_iov + if sz.max_copy > max_copy[version]: + max_copy[version] = sz.max_copy + + for name, table in [ + ("tmsg_max_iov", tmsg_max_iov), + ("tmsg_max_copy", tmsg_max_copy), + ("rmsg_max_iov", rmsg_max_iov), + ("rmsg_max_copy", rmsg_max_copy), + ]: + inv: dict[int, set[str]] = {} + for version, maxval in table.items(): + if maxval not in inv: + inv[maxval] = set() + inv[maxval].add(version) + + ret += "\n" + directive = "if" + seen_e = False # SPECIAL (9P2000.e) + for maxval in sorted(inv, reverse=True): + ret += f"#{directive} {c9util.ver_ifdef(inv[maxval])}\n" + indent = 1 + if name.startswith("tmsg") and not seen_e: # SPECIAL (9P2000.e) + typ = next(typ for typ in typs if typ.typname == "Tswrite") + sz = get_buffer_size(typ, "9P2000.e") + match name: + case "tmsg_max_iov": + maxexpr = f"{sz.max_iov}{sz.max_iov_extra}" + case "tmsg_max_copy": + maxexpr = f"{sz.max_copy}{sz.max_copy_extra}" + case _: + assert False + ret += f"\t#if {c9util.ver_ifdef({"9P2000.e"})}\n" + ret += f"\t\t#define {c9util.IDENT(name)} {c9util.IDENT('_MAX')}({maxval}, {maxexpr})\n" + ret += "\t#else\n" + indent += 1 + ret += f"{'\t'*indent}#define {c9util.IDENT(name)} {maxval}\n" + if name.startswith("tmsg") and not seen_e: # SPECIAL (9P2000.e) + ret += "\t#endif\n" + if "9P2000.e" in inv[maxval]: + seen_e = True + directive = "elif" + ret += "#endif\n" + + ret += "\n" + ret += f"struct {c9util.ident('Tmsg_send_buf')} {{\n" + ret += "\tsize_t iov_cnt;\n" + ret += f"\tstruct iovec iov[{c9util.IDENT('TMSG_MAX_IOV')}];\n" + ret += f"\tuint8_t copied[{c9util.IDENT('TMSG_MAX_COPY')}];\n" + ret += "};\n" + + ret += "\n" + ret += f"struct {c9util.ident('Rmsg_send_buf')} {{\n" + ret += "\tsize_t iov_cnt;\n" + ret += f"\tstruct iovec iov[{c9util.IDENT('RMSG_MAX_IOV')}];\n" + ret += f"\tuint8_t copied[{c9util.IDENT('RMSG_MAX_COPY')}];\n" + ret += "};\n" + + return ret + + +def gen_number(typ: idl.Number) -> str: + ret = f"typedef {c9util.typename(typ.prim)} {c9util.typename(typ)};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n" + + def lookup_sym(sym: str) -> str: + assert False + + def cname(base: str) -> str: + prefix = f"{typ.typname}_".upper() + return c9util.Ident(c9util.add_prefix(prefix, base)) + + namewidth = max(len(cname(name)) for name in typ.vals) + for name, val in typ.vals.items(): + c_name = cname(name) + c_val = c9util.idl_expr(val, lookup_sym) + ret += f"#define {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val}))\n" + return ret + + +def gen_bitfield(typ: idl.Bitfield) -> str: + ret = f"typedef {c9util.typename(typ.prim)} {c9util.typename(typ)};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n" + + def lookup_sym(sym: str) -> str: + assert False + + # There are 4 parts here: bits, aliases, masks, and numbers. + + # 1. bits + + def bitname(bit: idl.Bit) -> str: + prefix = f"{typ.typname}_".upper() + base = bit.bitname + match bit: + case idl.Bit(cat="RESERVED"): + base = "_RESERVED_" + base + case idl.Bit(cat=idl.BitNum()): + base += "_*" + case idl.Bit(cat="UNUSED"): + base = f"_UNUSED_{bit.num}" + return c9util.Ident(c9util.add_prefix(prefix, base)) + + namewidth = max(len(bitname(bit)) for bit in typ.bits) + + ret += "/* bits */\n" + for bit in reversed(typ.bits): + vers = bit.in_versions + if bit.cat == "UNUSED": + vers = typ.in_versions + ret += cutil.ifdef_push(2, c9util.ver_ifdef(vers)) + + # It is important all of the `beg` strings have + # the same length. + end = "" + match bit.cat: + case "USED" | "RESERVED" | "UNUSED": + if cutil.ifdef_leaf_is_noop(): + beg = "#define " + else: + beg = "# define" + case idl.BitNum(): + beg = "/* number" + end = " */" + + c_name = bitname(bit) + c_val = f"UINT{typ.static_size*8}_C(1)<<{bit.num}" + ret += ( + f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + ) + ret += cutil.ifdef_pop(1) + + # 2. aliases + if typ.aliases: + + def aliasname(alias: idl.BitAlias) -> str: + prefix = f"{typ.typname}_".upper() + base = alias.bitname + return c9util.Ident(c9util.add_prefix(prefix, base)) + + ret += "/* aliases */\n" + for alias in typ.aliases.values(): + ret += cutil.ifdef_push(2, c9util.ver_ifdef(alias.in_versions)) + + end = "" + if cutil.ifdef_leaf_is_noop(): + beg = "#define " + else: + beg = "# define" + + c_name = aliasname(alias) + c_val = c9util.idl_expr(alias.val, lookup_sym) + ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + + ret += cutil.ifdef_pop(1) + + # 3. masks + if typ.masks: + + def maskname(mask: idl.BitAlias) -> str: + prefix = f"{typ.typname}_".upper() + base = mask.bitname + return c9util.Ident(c9util.add_prefix(prefix, base) + "_MASK") + + ret += "/* masks */\n" + for mask in typ.masks.values(): + ret += cutil.ifdef_push(2, c9util.ver_ifdef(mask.in_versions)) + + end = "" + if cutil.ifdef_leaf_is_noop(): + beg = "#define " + else: + beg = "# define" + + c_name = maskname(mask) + c_val = c9util.idl_expr(mask.val, lookup_sym, bitwidth=typ.static_size * 8) + ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + + ret += cutil.ifdef_pop(1) + + # 4. numbers + def numname(num: idl.BitNum, base: str) -> str: + prefix = f"{typ.typname}_{num.numname}_".upper() + return c9util.Ident(c9util.add_prefix(prefix, base)) + + for num in typ.nums.values(): + namewidth = max( + len(numname(num, base)) + for base in [ + *[alias.bitname for alias in num.vals.values()], + "MASK", + ] + ) + ret += f"/* number: {num.numname} */\n" + for alias in num.vals.values(): + ret += cutil.ifdef_push(2, c9util.ver_ifdef(alias.in_versions)) + + end = "" + if cutil.ifdef_leaf_is_noop(): + beg = "#define " + else: + beg = "# define" + + c_name = numname(num, alias.bitname) + c_val = c9util.idl_expr(alias.val, lookup_sym) + ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + ret += cutil.ifdef_pop(1) + c_name = numname(num, "MASK") + c_val = f"{num.mask:#0{typ.static_size*8}b}" + ret += ( + f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + ) + + return ret + + +def gen_struct(typ: idl.Struct) -> str: # and idl.Message + ret = c9util.typename(typ) + " {" + if typ.members: + ret += "\n" + + typewidth = max(len(c9util.typename(m.typ, m)) for m in typ.members) + + for member in typ.members: + if member.val: + continue + ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions)) + ret += f"\t{c9util.typename(member.typ, member):<{typewidth}} {'*' if member.cnt else ' '}{member.membname};\n" + ret += cutil.ifdef_pop(1) + ret += "};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n" + return ret diff --git a/lib9p/protogen/idlutil.py b/lib9p/protogen/idlutil.py new file mode 100644 index 0000000..dc4d012 --- /dev/null +++ b/lib9p/protogen/idlutil.py @@ -0,0 +1,112 @@ +# lib9p/protogen/idlutil.py - Utilities for working with the 9P idl package +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import enum +import graphlib +import typing + +import idl + +# pylint: disable=unused-variable +__all__ = [ + "topo_sorted", + "Path", + "WalkCmd", + "WalkHandler", + "walk", +] + +# topo_sorted() ################################################################ + + +def topo_sorted(typs: list[idl.UserType]) -> typing.Iterable[idl.UserType]: + ts: graphlib.TopologicalSorter[idl.UserType] = graphlib.TopologicalSorter() + for typ in typs: + match typ: + case idl.Number(): + ts.add(typ) + case idl.Bitfield(): + ts.add(typ) + case idl.Struct(): # and idl.Message(): + deps = [ + member.typ + for member in typ.members + if not isinstance(member.typ, idl.Primitive) + ] + ts.add(typ, *deps) + return ts.static_order() + + +# walk() ####################################################################### + + +class Path: + root: idl.Type + elems: list[idl.StructMember] + + def __init__( + self, root: idl.Type, elems: list[idl.StructMember] | None = None + ) -> None: + self.root = root + self.elems = elems if elems is not None else [] + + def add(self, elem: idl.StructMember) -> "Path": + return Path(self.root, self.elems + [elem]) + + def parent(self) -> "Path": + return Path(self.root, self.elems[:-1]) + + def c_str(self, base: str, loopdepth: int = 0) -> str: + ret = base + for i, elem in enumerate(self.elems): + if i > 0: + ret += "." + ret += elem.membname + if elem.cnt: + ret += f"[{chr(ord('i')+loopdepth)}]" + loopdepth += 1 + return ret + + def __str__(self) -> str: + return self.c_str(self.root.typname + "->") + + +class WalkCmd(enum.Enum): + KEEP_GOING = 1 + DONT_RECURSE = 2 + ABORT = 3 + + +type WalkHandler = typing.Callable[ + [Path], tuple[WalkCmd, typing.Callable[[], None] | None] +] + + +def _walk(path: Path, handle: WalkHandler) -> WalkCmd: + typ = path.elems[-1].typ if path.elems else path.root + + ret, atexit = handle(path) + + if isinstance(typ, idl.Struct): + match ret: + case WalkCmd.KEEP_GOING: + for member in typ.members: + if _walk(path.add(member), handle) == WalkCmd.ABORT: + ret = WalkCmd.ABORT + break + case WalkCmd.DONT_RECURSE: + ret = WalkCmd.KEEP_GOING + case WalkCmd.ABORT: + ret = WalkCmd.ABORT + case _: + assert False, f"invalid cmd: {ret}" + + if atexit: + atexit() + return ret + + +def walk(typ: idl.Type, handle: WalkHandler) -> None: + _walk(Path(typ), handle) diff --git a/lib9p/srv.c b/lib9p/srv.c index 2fb2120..60a1bb0 100644 --- a/lib9p/srv.c +++ b/lib9p/srv.c @@ -1,26 +1,54 @@ /* lib9p/srv.c - 9P server * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #include <alloca.h> #include <inttypes.h> /* for PRI* */ -#include <string.h> /* for strerror() */ +#include <limits.h> /* for SSIZE_MAX, not set by newlib */ +#include <stddef.h> /* for size_t */ +#include <stdlib.h> /* for malloc() */ +#include <string.h> /* for memcpy() */ +#ifndef SSIZE_MAX +#define SSIZE_MAX (SIZE_MAX >> 1) +#endif #include <libcr/coroutine.h> #include <libcr_ipc/chan.h> #include <libcr_ipc/mutex.h> -#include <libcr_ipc/select.h> #include <libmisc/assert.h> -#include <libmisc/vcall.h> +#include <libmisc/endian.h> +#include <libhw/generic/net.h> #define LOG_NAME 9P_SRV #include <libmisc/log.h> #define IMPLEMENTATION_FOR_LIB9P_SRV_H YES #include <lib9p/srv.h> -#include "internal.h" + +/* config *********************************************************************/ + +#include "config.h" + +#ifndef CONFIG_9P_SRV_MAX_FIDS + #error config.h must define CONFIG_9P_SRV_MAX_FIDS +#endif +#ifndef CONFIG_9P_SRV_MAX_REQS + #error config.h must define CONFIG_9P_SRV_MAX_REQS +#endif +#ifndef CONFIG_9P_SRV_MAX_DEPTH + /* 1=just the root dir, 2=just files in the root dir, 3=1 subdir, ... */ + #error config.h must define CONFIG_9P_SRV_MAX_DEPTH +#endif +#ifndef CONFIG_9P_SRV_MAX_MSG_SIZE + #error config.h must define CONFIG_9P_SRV_MAX_MSG_SIZE +#endif +#ifndef CONFIG_9P_SRV_MAX_HOSTMSG_SIZE + #error config.h must define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE +#endif +static_assert(CONFIG_9P_SRV_MAX_MSG_SIZE <= CONFIG_9P_SRV_MAX_HOSTMSG_SIZE); +static_assert(CONFIG_9P_SRV_MAX_HOSTMSG_SIZE <= SSIZE_MAX); /* context ********************************************************************/ @@ -39,29 +67,56 @@ int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) { /* structs ********************************************************************/ +typedef typeof( ((struct lib9p_qid){}).path ) srv_path_t; + +struct srv_pathinfo { + lo_interface lib9p_srv_file file; + srv_path_t parent_dir; + + /* References from other srv_pathinfos (via .parent_dir) or + * from FIDs. */ + unsigned int gc_refcount; + /* References from fids with FIDFLAG_OPEN_R/FIDFLAG_OPEN_W. */ + unsigned int io_refcount; +}; + +#define NAME pathmap +#define KEY_T srv_path_t +#define VAL_T struct srv_pathinfo +/* ( naive ) + ( working space for walk() ) */ +#define CAP ( (CONFIG_9P_SRV_MAX_FIDS*CONFIG_9P_SRV_MAX_DEPTH) + (CONFIG_9P_SRV_MAX_REQS*2) ) +#include "map.h" + #define FIDFLAG_OPEN_R (1<<0) #define FIDFLAG_OPEN_W (1<<1) #define FIDFLAG_RCLOSE (1<<2) -#define FIDFLAG_ISDIR (1<<3) #define FIDFLAG_OPEN (FIDFLAG_OPEN_R|FIDFLAG_OPEN_W) struct _srv_fidinfo { - implements_lib9p_srv_file *file; - uint8_t flags; - size_t dir_idx; - uint32_t dir_off; + srv_path_t path; + uint8_t flags; + union { + struct { + lo_interface lib9p_srv_fio io; + } file; + struct { + lo_interface lib9p_srv_dio io; + size_t idx; + uint64_t off; + } dir; + }; }; #define NAME fidmap -#define KEY_T uint32_t +#define KEY_T lib9p_fid_t #define VAL_T struct _srv_fidinfo -#define CAP CONFIG_9P_MAX_FIDS +#define CAP CONFIG_9P_SRV_MAX_FIDS #include "map.h" #define NAME reqmap -#define KEY_T uint32_t +#define KEY_T lib9p_tag_t #define VAL_T struct _lib9p_srv_req * -#define CAP CONFIG_9P_MAX_REQS +#define CAP CONFIG_9P_SRV_MAX_REQS #include "map.h" /* The hierarchy of concepts is: @@ -75,7 +130,7 @@ struct _srv_fidinfo { struct _srv_conn { /* immutable */ struct lib9p_srv *parent_srv; - implements_net_stream_conn *fd; + lo_interface net_stream_conn fd; cid_t reader; /* the lib9p_srv_read_cr() coroutine */ /* mutable */ cr_mutex_t writelock; @@ -90,16 +145,17 @@ struct _srv_sess { /* mutable */ bool initialized; bool closing; - struct reqmap reqs; - struct fidmap fids; + struct pathmap paths; /* srv_path_t => lib9p_srv_file + metadata */ + struct fidmap fids; /* lib9p_fid_t => lib9p_srv_{fio,dio} + metadata */ + struct reqmap reqs; /* lib9p_tag_t => *_lib9p_srv_req */ }; struct _lib9p_srv_req { /* immutable */ struct _srv_sess *parent_sess; uint16_t tag; + uint8_t *net_bytes; /* mutable */ - uint8_t *net_bytes; /* CONFIG_9P_MAX_MSG_SIZE-sized */ struct lib9p_srv_ctx ctx; }; @@ -107,31 +163,12 @@ struct _lib9p_srv_req { #define nonrespond_errorf errorf -static uint32_t rerror_overhead_for_version(enum lib9p_version version, - uint8_t *scratch) { - struct lib9p_ctx empty_ctx = { - .version = version, - .max_msg_size = CONFIG_9P_MAX_MSG_SIZE, - }; - struct lib9p_msg_Rerror empty_error = { 0 }; - bool e; - - e = lib9p_marshal(&empty_ctx, LIB9P_TYP_Rerror, - &empty_error, /* host_body */ - scratch); /* net_bytes */ - assert(!e); - - uint32_t min_msg_size = decode_u32le(scratch); - - /* Assert that min_msg_size + biggest_possible_MAX_ERR_SIZE - * won't overflow uint32... because using - * __builtin_add_overflow in respond_error() would be a bit - * much. */ - assert(min_msg_size < (UINT32_MAX - UINT16_MAX)); - /* Assert that min_msg_size doesn't overflow MAX_MSG_SIZE. */ - assert(CONFIG_9P_MAX_MSG_SIZE >= min_msg_size); - - return min_msg_size; +static ssize_t write_Rmsg(struct _lib9p_srv_req *req, struct lib9p_Rmsg_send_buf *resp) { + ssize_t r; + cr_mutex_lock(&req->parent_sess->parent_conn->writelock); + r = io_writev(req->parent_sess->parent_conn->fd, resp->iov, resp->iov_cnt); + cr_mutex_unlock(&req->parent_sess->parent_conn->writelock); + return r; } static void respond_error(struct _lib9p_srv_req *req) { @@ -143,11 +180,8 @@ static void respond_error(struct _lib9p_srv_req *req) { ssize_t r; struct lib9p_msg_Rerror host = { .tag = req->tag, - .ename = { - .len = strnlen(req->ctx.basectx.err_msg, - CONFIG_9P_MAX_ERR_SIZE), - .utf8 = req->ctx.basectx.err_msg, - }, + .ename = lib9p_strn(req->ctx.basectx.err_msg, + CONFIG_9P_MAX_ERR_SIZE), #if CONFIG_9P_ENABLE_9P2000_u .errno = req->ctx.basectx.err_num, #endif @@ -156,33 +190,36 @@ static void respond_error(struct _lib9p_srv_req *req) { struct _srv_sess *sess = req->parent_sess; /* Truncate the error-string if necessary to avoid needing to - * return LINUX_ERANGE. The assert() in - * rerror_overhead_for_version() has checked that this - * addition doesn't overflow. */ + * return LINUX_ERANGE. */ if (((uint32_t)host.ename.len) + sess->rerror_overhead > sess->max_msg_size) host.ename.len = sess->max_msg_size - sess->rerror_overhead; - lib9p_marshal(&req->ctx.basectx, LIB9P_TYP_Rerror, - &host, req->net_bytes); + struct lib9p_Rmsg_send_buf net; - cr_mutex_lock(&sess->parent_conn->writelock); - r = VCALL(sess->parent_conn->fd, write, - req->net_bytes, decode_u32le(req->net_bytes)); - cr_mutex_unlock(&sess->parent_conn->writelock); + lib9p_Rmsg_marshal(&req->ctx.basectx, + LIB9P_TYP_Rerror, &host, + &net); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + infof("< %v", lo_box_lib9p_msg_Rerror_as_fmt_formatter(&host)); +#pragma GCC diagnostic pop + r = write_Rmsg(req, &net); if (r < 0) - nonrespond_errorf("write: %s", strerror(-r)); + nonrespond_errorf("write: %s", net_strerror(-r)); } /* read coroutine *************************************************************/ -static bool read_at_least(implements_net_stream_conn *fd, uint8_t *buf, size_t goal, size_t *done) { +static bool read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t goal, size_t *done) { assert(buf); assert(goal); assert(done); while (*done < goal) { - ssize_t r = VCALL(fd, read, &buf[*done], CONFIG_9P_MAX_MSG_SIZE - *done); + ssize_t r = io_read(fd, &buf[*done], goal - *done); if (r < 0) { - nonrespond_errorf("read: %s", strerror(-r)); + nonrespond_errorf("read: %s", net_strerror(-r)); return true; } else if (r == 0) { if (*done != 0) @@ -196,30 +233,34 @@ static bool read_at_least(implements_net_stream_conn *fd, uint8_t *buf, size_t g static void handle_message(struct _lib9p_srv_req *ctx); -[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, implements_net_stream_listener *listener) { - uint8_t buf[CONFIG_9P_MAX_MSG_SIZE]; - +[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener) { assert(srv); assert(srv->rootdir); - assert(listener); + assert(!LO_IS_NULL(listener)); - uint32_t initial_rerror_overhead = rerror_overhead_for_version(0, buf); + srv->readers++; + + uint32_t initial_rerror_overhead = lib9p_version_min_msg_size(LIB9P_VER_unknown); for (;;) { struct _srv_conn conn = { .parent_srv = srv, - .fd = VCALL(listener, accept), + .fd = LO_CALL(listener, accept), .reader = cr_getcid(), }; - if (!conn.fd) { + if (LO_IS_NULL(conn.fd)) { nonrespond_errorf("accept: error"); - continue; + srv->readers--; + if (srv->readers == 0) + while (srv->writers > 0) + _lib9p_srv_reqch_send_req(&srv->_reqch, NULL); + cr_exit(); } struct _srv_sess sess = { .parent_conn = &conn, .version = LIB9P_VER_unknown, - .max_msg_size = CONFIG_9P_MAX_MSG_SIZE, + .max_msg_size = CONFIG_9P_SRV_MAX_MSG_SIZE, .rerror_overhead = initial_rerror_overhead, .initialized = false, }; @@ -227,18 +268,19 @@ static void handle_message(struct _lib9p_srv_req *ctx); nextmsg: /* Read the message. */ size_t done = 0; - if (read_at_least(conn.fd, buf, 4, &done)) + uint8_t buf[7]; + if (read_exactly(conn.fd, buf, 4, &done)) goto close; - size_t goal = decode_u32le(buf); + size_t goal = uint32le_decode(buf); if (goal < 7) { nonrespond_errorf("T-message is impossibly small"); goto close; } - if (read_at_least(conn.fd, buf, 7, &done)) + if (read_exactly(conn.fd, buf, 7, &done)) goto close; struct _lib9p_srv_req req = { .parent_sess = &sess, - .tag = decode_u16le(&buf[5]), + .tag = uint16le_decode(&buf[5]), .net_bytes = buf, .ctx = { .basectx = { @@ -256,11 +298,16 @@ static void handle_message(struct _lib9p_srv_req *ctx); respond_error(&req); goto nextmsg; } - if (read_at_least(conn.fd, buf, goal, &done)) + req.net_bytes = malloc(goal); + assert(req.net_bytes); + memcpy(req.net_bytes, buf, done); + if (read_exactly(conn.fd, req.net_bytes, goal, &done)) { + free(req.net_bytes); goto close; + } /* Handle the message... */ - if (buf[4] == LIB9P_TYP_Tversion) + if (req.net_bytes[4] == LIB9P_TYP_Tversion) /* ...in this coroutine for Tversion, */ handle_message(&req); else @@ -268,12 +315,14 @@ static void handle_message(struct _lib9p_srv_req *ctx); _lib9p_srv_reqch_send_req(&srv->_reqch, &req); } close: - VCALL(conn.fd, close, true, sess.reqs.len == 0); - if (sess.reqs.len) { + if (sess.reqs.len == 0) + io_close(conn.fd); + else { + io_close_read(conn.fd); sess.closing = true; cr_pause_and_yield(); assert(sess.reqs.len == 0); - VCALL(conn.fd, close, true, true); + io_close_write(conn.fd); } } } @@ -281,7 +330,6 @@ static void handle_message(struct _lib9p_srv_req *ctx); /* write coroutine ************************************************************/ COROUTINE lib9p_srv_write_cr(void *_srv) { - uint8_t net[CONFIG_9P_MAX_MSG_SIZE]; struct _lib9p_srv_req req; _lib9p_srv_reqch_req_t rpc_handle; @@ -290,14 +338,19 @@ COROUTINE lib9p_srv_write_cr(void *_srv) { assert(srv->rootdir); cr_begin(); + srv->writers++; + for (;;) { /* Receive the request from the reader coroutine. ************/ rpc_handle = _lib9p_srv_reqch_recv_req(&srv->_reqch); - /* Deep-copy the request from the reader coroutine's + if (!rpc_handle.req) { + srv->writers--; + _lib9p_srv_reqch_send_resp(rpc_handle, 0); + cr_exit(); + } + /* Copy the request from the reader coroutine's * stack to our stack. */ req = *rpc_handle.req; - memcpy(net, req.net_bytes, decode_u32le(req.net_bytes)); - req.net_bytes = net; /* Record that we have it. */ reqmap_store(&req.parent_sess->reqs, req.tag, &req); /* Notify the reader coroutine that we're done with @@ -365,28 +418,23 @@ static tmessage_handler tmessage_handlers[0x100] = { }; static void handle_message(struct _lib9p_srv_req *ctx) { - uint8_t host_req[CONFIG_9P_MAX_HOSTMSG_SIZE]; - uint8_t host_resp[CONFIG_9P_MAX_HOSTMSG_SIZE]; + uint8_t *host_req = NULL; + uint8_t host_resp[CONFIG_9P_SRV_MAX_HOSTMSG_SIZE]; /* Unmarshal it. */ - enum lib9p_msg_type typ = ctx->net_bytes[4]; - if (typ % 2 != 0) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "expected a T-message but got an R-message: message_type=%s", - lib9p_msg_type_str(&ctx->ctx.basectx, typ)); - goto write; - } - ssize_t host_size = lib9p_validate(&ctx->ctx.basectx, ctx->net_bytes); + ssize_t host_size = lib9p_Tmsg_validate(&ctx->ctx.basectx, ctx->net_bytes); if (host_size < 0) goto write; - if ((size_t)host_size > sizeof(host_req)) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EMSGSIZE, "unmarshalled payload larger than server limit (%zu > %zu)", - host_size, sizeof(host_req)); - goto write; - } - lib9p_unmarshal(&ctx->ctx.basectx, ctx->net_bytes, + host_req = calloc(1, host_size); + assert(host_req); + enum lib9p_msg_type typ; + lib9p_Tmsg_unmarshal(&ctx->ctx.basectx, ctx->net_bytes, &typ, host_req); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + infof("> %v", lo_box_lib9p_msg_as_fmt_formatter(&ctx->ctx.basectx, typ, host_req)); +#pragma GCC diagnostic pop /* Handle it. */ tmessage_handlers[typ](ctx, (void *)host_req, (void *)host_resp); @@ -395,15 +443,21 @@ static void handle_message(struct _lib9p_srv_req *ctx) { if (lib9p_ctx_has_error(&ctx->ctx.basectx)) respond_error(ctx); else { - if (lib9p_marshal(&ctx->ctx.basectx, typ+1, host_resp, - ctx->net_bytes)) + struct lib9p_Rmsg_send_buf net_resp; + if (lib9p_Rmsg_marshal(&ctx->ctx.basectx, + typ+1, host_resp, + &net_resp)) goto write; - - cr_mutex_lock(&ctx->parent_sess->parent_conn->writelock); - VCALL(ctx->parent_sess->parent_conn->fd, write, - ctx->net_bytes, decode_u32le(ctx->net_bytes)); - cr_mutex_unlock(&ctx->parent_sess->parent_conn->writelock); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + infof("< %v", lo_box_lib9p_msg_as_fmt_formatter(&ctx->ctx.basectx, typ+1, &host_resp)); +#pragma GCC diagnostic pop + write_Rmsg(ctx, &net_resp); } + if (host_req) + free(host_req); + free(ctx->net_bytes); } #define util_handler_common(ctx, req, resp) do { \ @@ -413,7 +467,7 @@ static void handle_message(struct _lib9p_srv_req *ctx) { resp->tag = req->tag; \ } while (0) -static inline bool util_check_perm(struct lib9p_srv_ctx *ctx, struct lib9p_stat *stat, uint8_t action) { +static inline bool srv_util_check_perm(struct _lib9p_srv_req *ctx, struct lib9p_stat *stat, uint8_t action) { assert(ctx); assert(stat); assert(action); @@ -424,17 +478,107 @@ static inline bool util_check_perm(struct lib9p_srv_ctx *ctx, struct lib9p_stat return mode & action; } -static inline bool util_release(struct lib9p_srv_ctx *ctx, implements_lib9p_srv_file *file) { - assert(file); - file->_refcount--; - if (file->_refcount == 0) { - if (file->_parent_dir != file) - util_release(ctx, file->_parent_dir); - VCALL(file, free, ctx); +/** + * Ensures that `file` is saved into the pathmap, and increments the + * gc_refcount by 1 (for presumptive insertion into the fidmap). + * parent_path's gc_refcount is also incremented as appropriate. + * + * Returns a pointer to the stored pathinfo. + */ +static inline struct srv_pathinfo *srv_util_pathsave(struct _lib9p_srv_req *ctx, + lo_interface lib9p_srv_file file, + srv_path_t parent_path) { + assert(ctx); + assert(!LO_IS_NULL(file)); + + struct lib9p_qid qid = LO_CALL(file, qid); + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, qid.path); + if (pathinfo) + assert(LO_EQ(pathinfo->file, file)); + else { + pathinfo = pathmap_store(&ctx->parent_sess->paths, qid.path, + (struct srv_pathinfo){ + .file = file, + .parent_dir = parent_path, + .gc_refcount = 0, + .io_refcount = 0, + }); + assert(pathinfo); + if (parent_path != qid.path) { + struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, parent_path); + assert(parent); + parent->gc_refcount++; + } + } + pathinfo->gc_refcount++; + return pathinfo; +} + +/** + * Decrement the path's gc_refcount, and trigger garbage collection as + * appropriate. + */ +static inline void srv_util_pathfree(struct _lib9p_srv_req *ctx, srv_path_t path) { + assert(ctx); + + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, path); + assert(pathinfo); + pathinfo->gc_refcount--; + if (pathinfo->gc_refcount == 0) { + if (pathinfo->parent_dir != path) + srv_util_pathfree(ctx, pathinfo->parent_dir); + LO_CALL(pathinfo->file, free); + pathmap_del(&ctx->parent_sess->paths, path); + } +} + +static inline bool srv_util_pathisdir(struct srv_pathinfo *pathinfo) { + assert(pathinfo); + return LO_CALL(pathinfo->file, qid).type & LIB9P_QT_DIR; +} + +/** + * Store fid as pointing to pathinfo. Assumes that + * pathinfo->gc_refcount has already been incremented; does *not* + * decrement it on failure. + */ +static inline struct _srv_fidinfo *srv_util_fidsave(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, struct srv_pathinfo *pathinfo, bool overwrite) { + assert(ctx); + assert(fid != LIB9P_FID_NOFID); + assert(pathinfo); + + struct lib9p_qid qid = LO_CALL(pathinfo->file, qid); + + struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid); + if (fidinfo) { + if (overwrite) { + struct srv_pathinfo *old_pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(old_pathinfo); + if (srv_util_pathisdir(old_pathinfo)) + LO_CALL(fidinfo->dir.io, iofree); + else + LO_CALL(fidinfo->file.io, iofree); + srv_util_pathfree(ctx, fidinfo->path); + } else { + lib9p_error(&ctx->ctx.basectx, + LINUX_EBADF, "FID already in use"); + return NULL; + } + } else { + fidinfo = fidmap_store(&ctx->parent_sess->fids, fid, (struct _srv_fidinfo){}); + if (!fidinfo) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EMFILE, "too many open files"); + return NULL; + } } - return lib9p_ctx_has_error(&ctx->basectx); + *fidinfo = (struct _srv_fidinfo){ + .path = qid.path, + }; + return fidinfo; } + static void handle_Tversion(struct _lib9p_srv_req *ctx, struct lib9p_msg_Tversion *req, struct lib9p_msg_Rversion *resp) { @@ -449,33 +593,29 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx, '0' <= req->version.utf8[3] && req->version.utf8[3] <= '9' && '0' <= req->version.utf8[4] && req->version.utf8[4] <= '9' && '0' <= req->version.utf8[5] && req->version.utf8[5] <= '9' && - (req->version.utf8[6] == '\0' || req->version.utf8[6] == '.')) { + (req->version.len == 6 || req->version.utf8[6] == '.')) { version = LIB9P_VER_9P2000; #if CONFIG_9P_ENABLE_9P2000_u - if (strcmp(&req->version.utf8[6], ".u") == 0) + if (lib9p_str_eq(lib9p_str_sliceleft(req->version, 6), lib9p_str(".u"))) version = LIB9P_VER_9P2000_u; #endif #if CONFIG_9P_ENABLE_9P2000_e - if (strcmp(&req->version.utf8[6], ".e") == 0) + if (lib9p_str_eq(lib9p_str_sliceleft(req->version, 6), lib9p_str(".e"))) version = LIB9P_VER_9P2000_e; #endif } - uint32_t min_msg_size = rerror_overhead_for_version(version, ctx->net_bytes); + uint32_t min_msg_size = lib9p_version_min_msg_size(version); if (req->max_msg_size < min_msg_size) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")", - version, req->max_msg_size, min_msg_size); + lib9p_version_str(version), req->max_msg_size, min_msg_size); return; } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" - resp->version.utf8 = lib9p_version_str(version); -#pragma GCC diagnostic pop - resp->version.len = strlen(resp->version.utf8); - resp->max_msg_size = (CONFIG_9P_MAX_MSG_SIZE < req->max_msg_size) - ? CONFIG_9P_MAX_MSG_SIZE + resp->version = lib9p_str((char *)lib9p_version_str(version)); /* cast to discard "const" qualifier */ + resp->max_msg_size = (CONFIG_9P_SRV_MAX_MSG_SIZE < req->max_msg_size) + ? CONFIG_9P_SRV_MAX_MSG_SIZE : req->max_msg_size; /* Close the old session. */ @@ -516,8 +656,8 @@ static void handle_Tauth(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rauth *resp) { util_handler_common(ctx, req, resp); - ctx->ctx.uid = req->n_uname; - ctx->ctx.uname = req->uname.utf8; + ctx->ctx.uid = req->n_uid; + ctx->ctx.uname = req->uname; struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv; if (!srv->auth) { @@ -526,7 +666,7 @@ static void handle_Tauth(struct _lib9p_srv_req *ctx, return; } - srv->auth(&ctx->ctx, req->aname.utf8); + srv->auth(&ctx->ctx, req->aname); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "TODO: auth not implemented"); } @@ -536,8 +676,8 @@ static void handle_Tattach(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rattach *resp) { util_handler_common(ctx, req, resp); - ctx->ctx.uid = req->n_uname; - ctx->ctx.uname = req->uname.utf8; + ctx->ctx.uid = req->n_uid; + ctx->ctx.uname = req->uname; struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv; if (srv->auth) { @@ -549,14 +689,16 @@ static void handle_Tattach(struct _lib9p_srv_req *ctx, else if (fh->type != FH_AUTH) lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "FID provided as auth-file is not an auth-file"); - else if (strcmp(fh->data.auth.uname, req->uname.utf8) != 0) + else if (!lib9p_str_eq(fh->data.auth.uname, req->uname)) lib9p_errorf(&ctx->ctx.basectx, - LINUX_EACCES, "FID provided as auth-file is for user=\"%s\" and cannot be used for user=\"%s\"", - fh->data.auth.uname, req->uname.utf8); - else if (strcmp(fh->data.auth.aname, req->aname.utf8) != 0) + LINUX_EACCES, "FID provided as auth-file is for user=\"%.*s\" and cannot be used for user=\"%.*s\"", + fh->data.auth.uname.len, fh->data.auth.uname.utf8, + req->uname.len, req->uname.utf8); + else if (!lib9p_str_eq(fh->data.auth.aname, req->aname)) lib9p_errorf(&ctx->ctx.basectx, - LINUX_EACCES, "FID provided as auth-file is for tree=\"%s\" and cannot be used for tree=\"%s\"", - fh->data.auth.aname, req->aname.utf8); + LINUX_EACCES, "FID provided as auth-file is for tree=\"%.*s\" and cannot be used for tree=\"%.*s\"", + fh->data.auth.aname.len, fh->data.auth.aname.utf8, + req->aname.len, req->aname.utf8); else if (!fh->data.auth.authenticated) lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "FID provided as auth-file has not completed authentication"); @@ -565,51 +707,41 @@ static void handle_Tattach(struct _lib9p_srv_req *ctx, return; */ lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "TODO: auth not implemented"); + LINUX_EOPNOTSUPP, "TODO: auth not (yet?) implemented"); return; } else { - if (req->afid != LIB9P_NOFID) { + if (req->afid != LIB9P_FID_NOFID) { lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "FID provided as auth-file, but no auth-file is required"); return; } } - if (fidmap_load(&ctx->parent_sess->fids, req->fid)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EBADF, "FID already in use"); - return; + if (req->fid == LIB9P_FID_NOFID) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EBADF, "cannot assign to NOFID"); + return; } - implements_lib9p_srv_file *rootdir = srv->rootdir(&ctx->ctx, req->aname.utf8); - assert((rootdir == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx)); + /* 1. File object */ + lo_interface lib9p_srv_file root_file = srv->rootdir(&ctx->ctx, req->aname); + assert(LO_IS_NULL(root_file) == lib9p_ctx_has_error(&ctx->ctx.basectx)); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; - rootdir->_parent_dir = rootdir; - rootdir->_refcount++; - if (!fidmap_store(&ctx->parent_sess->fids, req->fid, (struct _srv_fidinfo){ - .file = rootdir, - .flags = FIDFLAG_ISDIR, - })) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EMFILE, "too many open files"); - util_release(&ctx->ctx, rootdir); - return; - } + struct lib9p_qid root_qid = LO_CALL(root_file, qid); + assert(root_qid.type & LIB9P_QT_DIR); + + /* 2. pathinfo */ + struct srv_pathinfo *root_pathinfo = srv_util_pathsave(ctx, root_file, root_qid.path); - struct lib9p_stat stat = VCALL(rootdir, stat, &ctx->ctx); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) { - handle_Tclunk(ctx, - &(struct lib9p_msg_Tclunk){.fid = req->fid}, - &(struct lib9p_msg_Rclunk){}); + /* 3. fidinfo */ + if (!srv_util_fidsave(ctx, req->fid, root_pathinfo, false)) { + srv_util_pathfree(ctx, root_qid.path); return; } - lib9p_assert_stat(stat); - assert(stat.file_mode & LIB9P_DM_DIR); - - resp->qid = stat.file_qid; + resp->qid = root_qid; return; } @@ -628,87 +760,75 @@ static void handle_Twalk(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rwalk *resp) { util_handler_common(ctx, req, resp); + if (req->newfid == LIB9P_FID_NOFID) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EBADF, "cannot assign to NOFID"); + return; + } + struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } - if (req->newfid != req->fid && fidmap_load(&ctx->parent_sess->fids, req->newfid)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EBADF, "FID already in use"); - return; - } - implements_lib9p_srv_file *dir = fidinfo->file; - if (req->newfid != req->fid) { - dir = VCALL(dir, clone, &ctx->ctx); - assert((dir == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx)); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - return; - dir->_refcount++; /* ref-A: presumptive insertion into fidmap */ - } - bool isdir = (fidinfo->flags & FIDFLAG_ISDIR); + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); + pathinfo->gc_refcount++; resp->wqid = (struct lib9p_qid *)(&resp[1]); for (resp->nwqid = 0; resp->nwqid < req->nwname; resp->nwqid++) { - implements_lib9p_srv_file *member; - if (strcmp(req->wname[resp->nwqid].utf8, "..") == 0) { - member = dir->_parent_dir; + struct srv_pathinfo *new_pathinfo; + if (lib9p_str_eq(req->wname[resp->nwqid], lib9p_str(".."))) { + new_pathinfo = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir); + assert(new_pathinfo); + new_pathinfo->gc_refcount++; } else { - if (!isdir) { + if (!srv_util_pathisdir(pathinfo)) { lib9p_error(&ctx->ctx.basectx, LINUX_ENOTDIR, "not a directory"); break; } - member = VCALL(dir, dopen, &ctx->ctx, req->wname[resp->nwqid].utf8); - assert((member == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx)); + lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dwalk, &ctx->ctx, req->wname[resp->nwqid]); + assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->ctx.basectx)); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) break; - member->_parent_dir = dir; - dir->_refcount++; /* member->_parent_dir */ + new_pathinfo = srv_util_pathsave(ctx, member_file, LO_CALL(pathinfo->file, qid).path); } - member->_refcount++; /* presumptively take ref-A */ - struct lib9p_stat stat = VCALL(member, stat, &ctx->ctx); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) { - util_release(&ctx->ctx, member); /* presumption of taking ref-A failed */ - break; - } - lib9p_assert_stat(stat); - isdir = stat.file_mode & LIB9P_DM_DIR; - if (isdir && !util_check_perm(&ctx->ctx, &stat, 0b001)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "you do not have execute permission on that directory"); - util_release(&ctx->ctx, member); /* presumption of taking ref-A failed */ - break; + if (srv_util_pathisdir(new_pathinfo)) { + struct lib9p_stat stat = LO_CALL(new_pathinfo->file, stat, &ctx->ctx); + if (lib9p_ctx_has_error(&ctx->ctx.basectx)) + break; + lib9p_stat_assert(stat); + if (!srv_util_check_perm(ctx, &stat, 0b001)) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EACCES, "you do not have execute permission on that directory"); + srv_util_pathfree(ctx, LO_CALL(new_pathinfo->file, qid).path); + break; + } } - resp->wqid[resp->nwqid] = stat.file_qid; + resp->wqid[resp->nwqid] = LO_CALL(new_pathinfo->file, qid); - /* presumption of taking ref-A succeeded */ - util_release(&ctx->ctx, dir); - dir = member; + srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); + pathinfo = new_pathinfo; } if (resp->nwqid == req->nwname) { if (req->newfid == req->fid) { - handle_Tclunk(ctx, - &(struct lib9p_msg_Tclunk){.fid = req->fid}, - &(struct lib9p_msg_Rclunk){}); - } - if (!fidmap_store(&ctx->parent_sess->fids, req->newfid, (struct _srv_fidinfo){ - .file = dir, - .flags = isdir ? FIDFLAG_ISDIR : 0, - })) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EMFILE, "too many open files"); - util_release(&ctx->ctx, dir); /* presumption of insertion failed */ + if (srv_util_pathisdir(pathinfo)) + LO_CALL(fidinfo->dir.io, iofree); + else + LO_CALL(fidinfo->file.io, iofree); + fidinfo->flags = 0; } + if (!srv_util_fidsave(ctx, req->newfid, pathinfo, req->newfid == req->fid)) + srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); } else { assert(lib9p_ctx_has_error(&ctx->ctx.basectx)); - if (req->newfid != req->fid) - util_release(&ctx->ctx, dir); /* presumption of insertion failed */ + srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); if (resp->nwqid > 0) lib9p_ctx_clear_error(&ctx->ctx.basectx); } @@ -731,8 +851,10 @@ static void handle_Topen(struct _lib9p_srv_req *ctx, LINUX_EALREADY, "FID is already open"); return; } - if (fidinfo->flags & FIDFLAG_ISDIR) { - if ( ((req->mode & LIB9P_O_MODE_MASK) != LIB9P_O_READ) || + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); + if (srv_util_pathisdir(pathinfo)) { + if ( ((req->mode & LIB9P_O_MODE_MASK) != LIB9P_O_MODE_READ) || (req->mode & LIB9P_O_TRUNC) || (req->mode & LIB9P_O_RCLOSE) ) { lib9p_error(&ctx->ctx.basectx, @@ -742,61 +864,92 @@ static void handle_Topen(struct _lib9p_srv_req *ctx, } /* Variables. */ - lib9p_o_t reqmode = req->mode; - uint8_t fidflags = fidinfo->flags; - implements_lib9p_srv_file *file = fidinfo->file; + lib9p_o_t reqmode = req->mode; + uint8_t fidflags = fidinfo->flags; /* Check permissions. */ if (reqmode & LIB9P_O_RCLOSE) { - struct lib9p_stat parent_stat = VCALL(file->_parent_dir, stat, &ctx->ctx); + struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir); + assert(parent); + struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; - lib9p_assert_stat(parent_stat); - if (!util_check_perm(&ctx->ctx, &parent_stat, 0b010)) { + lib9p_stat_assert(parent_stat); + if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) { lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "permission denied to remove-on-close"); return; } - fidflags = fidflags | FIDFLAG_RCLOSE; + fidflags |= FIDFLAG_RCLOSE; } - struct lib9p_stat stat = VCALL(file, stat, &ctx->ctx); + struct lib9p_stat stat = LO_CALL(pathinfo->file, stat, &ctx->ctx); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; - lib9p_assert_stat(stat); - if (stat.file_mode & LIB9P_QT_APPEND) + lib9p_stat_assert(stat); + if ((stat.file_mode & LIB9P_DM_EXCL) && pathinfo->io_refcount) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EEXIST, "exclusive file is already opened"); + return; + } + if (stat.file_mode & LIB9P_DM_APPEND) reqmode = reqmode & ~LIB9P_O_TRUNC; uint8_t perm_bits = 0; + bool rd = false, wr = false; switch (reqmode & LIB9P_O_MODE_MASK) { - case LIB9P_O_READ: + case LIB9P_O_MODE_READ: perm_bits = 0b100; - fidflags = fidflags | FIDFLAG_OPEN_R; + rd = true; break; - case LIB9P_O_WRITE: + case LIB9P_O_MODE_WRITE: perm_bits = 0b010; - fidflags = fidflags | FIDFLAG_OPEN_W; + wr = true; break; - case LIB9P_O_RDWR: + case LIB9P_O_MODE_RDWR: perm_bits = 0b110; - fidflags = fidflags | FIDFLAG_OPEN_R | FIDFLAG_OPEN_W; + rd = wr = true; break; - case LIB9P_O_EXEC: + case LIB9P_O_MODE_EXEC: perm_bits = 0b001; - fidflags = fidflags | FIDFLAG_OPEN_R; + rd = true; break; } - if (!util_check_perm(&ctx->ctx, &stat, perm_bits)) { + if (!srv_util_check_perm(ctx, &stat, perm_bits)) { lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "permission denied"); + return; } /* Actually make the call. */ - uint32_t iounit = VCALL(file, io, &ctx->ctx, reqmode); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - return; + uint32_t iounit; + struct lib9p_qid qid; + if (srv_util_pathisdir(pathinfo)) { + fidinfo->dir.io = LO_CALL(pathinfo->file, dopen, &ctx->ctx); + assert(LO_IS_NULL(fidinfo->dir.io) == lib9p_ctx_has_error(&ctx->ctx.basectx)); + if (lib9p_ctx_has_error(&ctx->ctx.basectx)) + return; + fidinfo->dir.idx = 0; + fidinfo->dir.off = 0; + qid = LO_CALL(fidinfo->dir.io, qid); + iounit = 0; + } else { + fidinfo->file.io = LO_CALL(pathinfo->file, fopen, &ctx->ctx, + rd, wr, + reqmode & LIB9P_O_TRUNC); + assert(LO_IS_NULL(fidinfo->file.io) == lib9p_ctx_has_error(&ctx->ctx.basectx)); + if (lib9p_ctx_has_error(&ctx->ctx.basectx)) + return; + qid = LO_CALL(fidinfo->file.io, qid); + iounit = LO_CALL(fidinfo->file.io, iounit); + } /* Success. */ + if (rd) + fidflags |= FIDFLAG_OPEN_R; + if (wr) + fidflags |= FIDFLAG_OPEN_W; + pathinfo->io_refcount++; fidinfo->flags = fidflags; - resp->qid = stat.file_qid; + resp->qid = qid; resp->iounit = iounit; } @@ -814,6 +967,8 @@ static void handle_Tread(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rread *resp) { util_handler_common(ctx, req, resp); + /* TODO: serialize simultaneous reads to the same FID */ + /* Check that the FID is valid for this. */ struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { @@ -828,38 +983,47 @@ static void handle_Tread(struct _lib9p_srv_req *ctx, } /* Variables. */ - implements_lib9p_srv_file *file = fidinfo->file; - resp->data.dat = (char *)(&resp[1]); + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); /* Do it. */ - if (fidinfo->flags & FIDFLAG_ISDIR) { + if (srv_util_pathisdir(pathinfo)) { /* Translate byte-offset to object-index. */ size_t idx; if (req->offset == 0) idx = 0; - else if (req->offset == fidinfo->dir_off) - idx = fidinfo->dir_idx; + else if (req->offset == fidinfo->dir.off) + idx = fidinfo->dir.idx; else { lib9p_errorf(&ctx->ctx.basectx, - LINUX_EINVAL, "invalid offset (must be 0 or %"PRIu32"): %"PRIu32, - fidinfo->dir_off, req->offset); + LINUX_EINVAL, "invalid offset (must be 0 or %"PRIu64"): %"PRIu64, + fidinfo->dir.off, req->offset); return; } /* Do it. */ - size_t num = VCALL(file, dread, &ctx->ctx, (uint8_t *)resp->data.dat, req->count, idx); + resp->data = (char *)(&resp[1]); + size_t num = LO_CALL(fidinfo->dir.io, dread, &ctx->ctx, (uint8_t *)resp->data, req->count, idx); /* Translate object-count back to byte-count. */ uint32_t len = 0; for (size_t i = 0; i < num; i++) { uint32_t i_len; - lib9p_validate_stat(&ctx->ctx.basectx, req->count, &((uint8_t *)resp->data.dat)[len], &i_len, NULL); + lib9p_stat_validate(&ctx->ctx.basectx, req->count, &((uint8_t *)resp->data)[len], &i_len, NULL); len += i_len; } - resp->data.len = len; + resp->count = len; /* Remember. */ - fidinfo->dir_idx = idx+num; - fidinfo->dir_off = req->offset + len; - } else - resp->data.len = VCALL(file, pread, &ctx->ctx, resp->data.dat, req->count, req->offset); + fidinfo->dir.idx = idx+num; + fidinfo->dir.off = req->offset + len; + } else { + struct iovec iov; + LO_CALL(fidinfo->file.io, pread, &ctx->ctx, req->count, req->offset, &iov); + if (!lib9p_ctx_has_error(&ctx->ctx.basectx)) { + resp->count = iov.iov_len; + resp->data = iov.iov_base; + if (resp->count > req->count) + resp->count = req->count; + } + } } static void handle_Twrite(struct _lib9p_srv_req *ctx, @@ -867,6 +1031,8 @@ static void handle_Twrite(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rwrite *resp) { util_handler_common(ctx, req, resp); + /* TODO: serialize simultaneous writes to the same FID */ + /* Check that the FID is valid for this. */ struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { @@ -881,41 +1047,69 @@ static void handle_Twrite(struct _lib9p_srv_req *ctx, } /* Variables. */ - implements_lib9p_srv_file *file = fidinfo->file; + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); /* Do it. */ - resp->count = VCALL(file, pwrite, &ctx->ctx, req->data.dat, req->data.len, req->offset); + resp->count = LO_CALL(fidinfo->file.io, pwrite, &ctx->ctx, req->data, req->count, req->offset); } -static void handle_Tclunk(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tclunk *req, - struct lib9p_msg_Rclunk *resp) { - util_handler_common(ctx, req, resp); - - struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); +static void clunkremove(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, bool remove) { + struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBADF, "bad file number %"PRIu32, req->fid); + LINUX_EBADF, "bad file number %"PRIu32, fid); return; } - if (fidinfo->flags & FIDFLAG_RCLOSE) { - handle_Tremove(ctx, - &(struct lib9p_msg_Tremove){.fid = req->fid}, - &(struct lib9p_msg_Rremove){}); - return; + if (fidinfo->flags & FIDFLAG_RCLOSE) + remove = true; + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); + + if (remove) { + if (pathinfo->parent_dir == fidinfo->path) { + lib9p_errorf(&ctx->ctx.basectx, + LINUX_EBUSY, "cannot remove root"); + goto clunk; + } + struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir); + assert(parent); + struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx); + if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EACCES, "you do not have write permission on the parent directory"); + goto clunk; + } + LO_CALL(pathinfo->file, remove, &ctx->ctx); + } + + clunk: + if (fidinfo->flags & FIDFLAG_OPEN) { + if (srv_util_pathisdir(pathinfo)) + LO_CALL(fidinfo->dir.io, iofree); + else + LO_CALL(fidinfo->file.io, iofree); + pathinfo->io_refcount--; } + srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); + fidmap_del(&ctx->parent_sess->fids, fid); +} - VCALL(fidinfo->file, free, &ctx->ctx); - fidmap_del(&ctx->parent_sess->fids, req->fid); +static void handle_Tclunk(struct _lib9p_srv_req *ctx, + struct lib9p_msg_Tclunk *req, + struct lib9p_msg_Rclunk *resp) { + util_handler_common(ctx, req, resp); + + clunkremove(ctx, req->fid, false); } + static void handle_Tremove(struct _lib9p_srv_req *ctx, struct lib9p_msg_Tremove *req, struct lib9p_msg_Rremove *resp) { util_handler_common(ctx, req, resp); - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "remove not (yet?) implemented"); + clunkremove(ctx, req->fid, true); } static void handle_Tstat(struct _lib9p_srv_req *ctx, @@ -929,10 +1123,12 @@ static void handle_Tstat(struct _lib9p_srv_req *ctx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); - resp->stat = VCALL(fidinfo->file, stat, &ctx->ctx); + resp->stat = LO_CALL(pathinfo->file, stat, &ctx->ctx); if (!lib9p_ctx_has_error(&ctx->ctx.basectx)) - lib9p_assert_stat(resp->stat); + lib9p_stat_assert(resp->stat); } static void handle_Twstat(struct _lib9p_srv_req *ctx, diff --git a/lib9p/tables.c b/lib9p/tables.c new file mode 100644 index 0000000..271b17b --- /dev/null +++ b/lib9p/tables.c @@ -0,0 +1,186 @@ +/* lib9p/tables.c - Access tables of version and message information + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> + +#include <libmisc/endian.h> +#include <libmisc/log.h> /* for const_byte_str() */ + +#include "tables.h" + +/* bounds checks **************************************************************/ + +static inline void assert_ver(enum lib9p_version ver) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + assert(0 <= ver && ver < LIB9P_VER_NUM); +#pragma GCC diagnostic pop +} + +static inline void assert_typ(enum lib9p_msg_type typ) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + assert(0 <= typ && typ < 0xFF); +#pragma GCC diagnostic pop +} + +/* simple lookups *************************************************************/ + +const char *lib9p_version_str(enum lib9p_version ver) { + assert_ver(ver); + return _lib9p_table_ver[ver].name; +} + +uint32_t lib9p_version_min_msg_size(enum lib9p_version ver) { + assert_ver(ver); + return _lib9p_table_ver[ver].min_msg_size; +} + +const char *lib9p_msgtype_str(enum lib9p_version ver, enum lib9p_msg_type typ) { + assert_ver(ver); + assert_typ(typ); + return _lib9p_table_msg[ver][typ].name ?: const_byte_str(typ); +} + +lo_interface fmt_formatter lo_box_lib9p_msg_as_fmt_formatter(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body) { + assert(ctx); + assert_ver(ctx->version); + assert_typ(typ); + assert(_lib9p_table_msg[ctx->version][typ].box_as_fmt_formatter); + return _lib9p_table_msg[ctx->version][typ].box_as_fmt_formatter(body); +} + +/* main message functions *****************************************************/ + +static +ssize_t _lib9p_validate(uint8_t xxx_low_typ_bit, + const char *xxx_errmsg, + const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0x80], + struct lib9p_ctx *ctx, uint8_t *net_bytes) { + assert_ver(ctx->version); + /* Inspect the first 5 bytes ourselves. */ + uint32_t net_size = uint32le_decode(net_bytes); + if (net_size < 5) + return lib9p_error(ctx, LINUX_EBADMSG, "message is impossibly short"); + uint8_t typ = net_bytes[4]; + if (typ % 2 != xxx_low_typ_bit) + return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "%s: message_type=%s", xxx_errmsg, + lib9p_msgtype_str(ctx->version, typ)); + struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2]; + if (!tentry.validate) + return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "unknown message type: %s (protocol_version=%s)", + lib9p_msgtype_str(ctx->version, typ), lib9p_version_str(ctx->version)); + + /* Now use the message-type-specific tentry to process the whole thing. */ + return tentry.validate(ctx, net_size, net_bytes); +} + +ssize_t lib9p_Tmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) { + return _lib9p_validate(0, "expected a T-message but got an R-message", _lib9p_table_Tmsg_recv, + ctx, net_bytes); +} + +ssize_t lib9p_Rmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) { + return _lib9p_validate(1, "expected an R-message but got a T-message", _lib9p_table_Rmsg_recv, + ctx, net_bytes); +} + +static +void _lib9p_unmarshal(const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0x80], + struct lib9p_ctx *ctx, uint8_t *net_bytes, + enum lib9p_msg_type *ret_typ, void *ret_body) { + assert_ver(ctx->version); + enum lib9p_msg_type typ = net_bytes[4]; + *ret_typ = typ; + struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2]; + + tentry.unmarshal(ctx, net_bytes, ret_body); +} + +void lib9p_Tmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, + enum lib9p_msg_type *ret_typ, void *ret_body) { + _lib9p_unmarshal(_lib9p_table_Tmsg_recv, + ctx, net_bytes, ret_typ, ret_body); +} + +void lib9p_Rmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, + enum lib9p_msg_type *ret_typ, void *ret_body) { + _lib9p_unmarshal(_lib9p_table_Rmsg_recv, + ctx, net_bytes, ret_typ, ret_body); +} + +static +bool _lib9p_marshal(const struct _lib9p_send_tentry xxx_table[LIB9P_VER_NUM][0x80], + struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, + size_t *ret_iov_cnt, struct iovec *ret_iov, uint8_t *ret_copied) { + assert_ver(ctx->version); + assert_typ(typ); + struct _marshal_ret ret = { + .net_iov_cnt = 1, + .net_iov = ret_iov, + .net_copied_size = 0, + .net_copied = ret_copied, + }; + + struct _lib9p_send_tentry tentry = xxx_table[ctx->version][typ/2]; + bool ret_erred = tentry.marshal(ctx, body, &ret); + if (ret_iov[ret.net_iov_cnt-1].iov_len == 0) + ret.net_iov_cnt--; + *ret_iov_cnt = ret.net_iov_cnt; + return ret_erred; +} + +bool lib9p_Tmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, + struct lib9p_Tmsg_send_buf *ret) { + assert(typ % 2 == 0); + memset(ret, 0, sizeof(*ret)); + return _lib9p_marshal(_lib9p_table_Tmsg_send, + ctx, typ, body, + &ret->iov_cnt, ret->iov, ret->copied); +} + +bool lib9p_Rmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, + struct lib9p_Rmsg_send_buf *ret) { + assert(typ % 2 == 1); + memset(ret, 0, sizeof(*ret)); + return _lib9p_marshal(_lib9p_table_Rmsg_send, + ctx, typ, body, + &ret->iov_cnt, ret->iov, ret->copied); +} + +/* `struct lib9p_stat` helpers ************************************************/ + +bool lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, + uint32_t *ret_net_size, ssize_t *ret_host_size) { + ssize_t host_size = _lib9p_stat_validate(ctx, net_size, net_bytes, ret_net_size); + if (host_size < 0) + return true; + if (ret_host_size) + *ret_host_size = host_size; + return false; +} + +void lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, + struct lib9p_stat *ret) { + _lib9p_stat_unmarshal(ctx, net_bytes, ret); +} + +uint32_t lib9p_stat_marshal(struct lib9p_ctx *ctx, uint32_t max_net_size, struct lib9p_stat *obj, + uint8_t *ret_bytes) { + struct lib9p_ctx _ctx = *ctx; + _ctx.max_msg_size = max_net_size; + + struct iovec iov = {0}; + struct _marshal_ret ret = { + .net_iov_cnt = 1, + .net_iov = &iov, + .net_copied_size = 0, + .net_copied = ret_bytes, + }; + if (_lib9p_stat_marshal(&_ctx, obj, &ret)) + return 0; + return ret.net_iov[0].iov_len; +} diff --git a/lib9p/tables.h b/lib9p/tables.h new file mode 100644 index 0000000..edb402a --- /dev/null +++ b/lib9p/tables.h @@ -0,0 +1,59 @@ +/* lib9p/tables.h - Declare tables of version and message information + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIB9P_TABLES_H_ +#define _LIB9P_TABLES_H_ + +#include <lib9p/9p.h> + +/* version ********************************************************************/ + +struct _lib9p_ver_tentry { + const char *name; + uint32_t min_msg_size; +}; + +extern const struct _lib9p_ver_tentry _lib9p_table_ver[LIB9P_VER_NUM]; + +/* message ********************************************************************/ + +typedef lo_interface fmt_formatter (*_box_as_fmt_formatter_fn_t)(void *host_val); +struct _lib9p_msg_tentry { + const char *name; + _box_as_fmt_formatter_fn_t box_as_fmt_formatter; +}; + +typedef ssize_t (*_validate_fn_t)(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes); +typedef void (*_unmarshal_fn_t)(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out); +struct _lib9p_recv_tentry { + _validate_fn_t validate; + _unmarshal_fn_t unmarshal; +}; + +struct _marshal_ret { + size_t net_iov_cnt; + struct iovec *net_iov; + size_t net_copied_size; + uint8_t *net_copied; +}; +typedef bool (*_marshal_fn_t)(struct lib9p_ctx *ctx, void *host_val, struct _marshal_ret *ret); +struct _lib9p_send_tentry { + _marshal_fn_t marshal; +}; + +extern const struct _lib9p_msg_tentry _lib9p_table_msg[LIB9P_VER_NUM][0x100]; +extern const struct _lib9p_recv_tentry _lib9p_table_Tmsg_recv[LIB9P_VER_NUM][0x80]; +extern const struct _lib9p_recv_tentry _lib9p_table_Rmsg_recv[LIB9P_VER_NUM][0x80]; +extern const struct _lib9p_send_tentry _lib9p_table_Tmsg_send[LIB9P_VER_NUM][0x80]; +extern const struct _lib9p_send_tentry _lib9p_table_Rmsg_send[LIB9P_VER_NUM][0x80]; + +/* stat ***********************************************************************/ + +ssize_t _lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size); +void _lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out); +bool _lib9p_stat_marshal(struct lib9p_ctx *ctx, struct lib9p_stat *val, struct _marshal_ret *ret); + +#endif /* _LIB9P_TABLES_H_ */ diff --git a/lib9p/tests/runtest b/lib9p/tests/runtest new file mode 100755 index 0000000..379ea6d --- /dev/null +++ b/lib9p/tests/runtest @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# lib9p/tests/runtest - Simple tests for the 9P `test_server` +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +set -euE -o pipefail +set -x + +port=$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()') +valgrind --error-exitcode=2 ./tests/test_server/test_server "$port" & +server_pid=$! +# shellcheck disable=SC2064 +trap "kill $server_pid || true; wait $server_pid || true" EXIT +server_addr="localhost:${port}" + +client=(9p -a "$server_addr") + +expect_lines() ( + { set +x; } &>/dev/null + printf >&2 '+ diff -u expected.txt actual.txt\n' + diff -u <(printf '%s\n' "$@") <(printf '%s\n' "$out") +) + +while [[ -d /proc/$server_pid && "$(readlink /proc/$server_pid/fd/4 2>/dev/null)" != socket:* ]]; do sleep 0.1; done + +out=$("${client[@]}" ls -l '') +expect_lines \ + 'd-r-xr-xr-x M 0 root root 0 Oct 7 2024 Documentation' \ + '--r--r--r-- M 0 root root 166 Oct 7 2024 README.md' \ + '---w--w--w- M 0 root root 0 Oct 7 2024 shutdown' + +out=$("${client[@]}" ls -l 'Documentation/') +expect_lines \ + '--r--r--r-- M 0 root root 166 Oct 7 2024 x' + +out=$("${client[@]}" read 'README.md') +expect_lines \ + '<!--' \ + ' README.md - test static file' \ + '' \ + ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \ + ' SPDX-License-Identifier: AGPL-3.0-or-later' \ + '-->' \ + 'Hello, world!' + +out=$("${client[@]}" read 'Documentation/x') +expect_lines \ + '<!--' \ + ' Documentation/x.txt - test static file' \ + '' \ + ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \ + ' SPDX-License-Identifier: AGPL-3.0-or-later' \ + '-->' \ + 'foo' + +out=$("${client[@]}" stat 'Documentation/x') +expect_lines \ + "'x' 'root' 'root' 'root' q (0000000000000001 1 ) m 0444 at 1728337905 mt 1728337904 l 166 t 0 d 0" + +out=$("${client[@]}" write 'shutdown' <<<1) +expect_lines '' + +wait "$server_pid" +trap - EXIT diff --git a/lib9p/tests/test_compile.c b/lib9p/tests/test_compile.c new file mode 100644 index 0000000..8f2445d --- /dev/null +++ b/lib9p/tests/test_compile.c @@ -0,0 +1,299 @@ +/* lib9p/tests/test_compile.c - Generated by lib9p/tests/test_compile.c.gen. DO NOT EDIT! */ + +#include <lib9p/9p.h> +int main(void) { + [[gnu::unused]] uint64_t x; + x = LIB9P_TAG_NOTAG; + x = LIB9P_FID_NOFID; + x = LIB9P_DM_DIR; + x = LIB9P_DM_APPEND; + x = LIB9P_DM_EXCL; + x = _LIB9P_DM_PLAN9_MOUNT; + x = LIB9P_DM_AUTH; + x = LIB9P_DM_TMP; + x = _LIB9P_DM_UNUSED_25; + x = _LIB9P_DM_UNUSED_24; + x = LIB9P_DM_DEVICE; + x = _LIB9P_DM_UNUSED_22; + x = LIB9P_DM_PIPE; + x = LIB9P_DM_SOCKET; + x = LIB9P_DM_SETUID; + x = LIB9P_DM_SETGID; + x = _LIB9P_DM_UNUSED_17; + x = _LIB9P_DM_UNUSED_16; + x = _LIB9P_DM_UNUSED_15; + x = _LIB9P_DM_UNUSED_14; + x = _LIB9P_DM_UNUSED_13; + x = _LIB9P_DM_UNUSED_12; + x = _LIB9P_DM_UNUSED_11; + x = _LIB9P_DM_UNUSED_10; + x = _LIB9P_DM_UNUSED_9; + x = LIB9P_DM_OWNER_R; + x = LIB9P_DM_OWNER_W; + x = LIB9P_DM_OWNER_X; + x = LIB9P_DM_GROUP_R; + x = LIB9P_DM_GROUP_W; + x = LIB9P_DM_GROUP_X; + x = LIB9P_DM_OTHER_R; + x = LIB9P_DM_OTHER_W; + x = LIB9P_DM_OTHER_X; + x = LIB9P_DM_PERM_MASK; + x = LIB9P_QT_DIR; + x = LIB9P_QT_APPEND; + x = LIB9P_QT_EXCL; + x = _LIB9P_QT_PLAN9_MOUNT; + x = LIB9P_QT_AUTH; + x = LIB9P_QT_TMP; + x = LIB9P_QT_SYMLINK; + x = _LIB9P_QT_UNUSED_0; + x = LIB9P_QT_FILE; + x = LIB9P_NUID_NONUID; + x = _LIB9P_O_UNUSED_7; + x = LIB9P_O_RCLOSE; + x = _LIB9P_O_RESERVED_CEXEC; + x = LIB9P_O_TRUNC; + x = _LIB9P_O_UNUSED_3; + x = _LIB9P_O_UNUSED_2; + x = LIB9P_O_FLAG_MASK; + x = LIB9P_O_MODE_READ; + x = LIB9P_O_MODE_WRITE; + x = LIB9P_O_MODE_RDWR; + x = LIB9P_O_MODE_EXEC; + x = LIB9P_O_MODE_MASK; + x = LIB9P_ERRNO_NOERROR; + x = LIB9P_SUPER_MAGIC_V9FS_MAGIC; + x = _LIB9P_LO_UNUSED_31; + x = _LIB9P_LO_UNUSED_30; + x = _LIB9P_LO_UNUSED_29; + x = _LIB9P_LO_UNUSED_28; + x = _LIB9P_LO_UNUSED_27; + x = _LIB9P_LO_UNUSED_26; + x = _LIB9P_LO_UNUSED_25; + x = _LIB9P_LO_UNUSED_24; + x = _LIB9P_LO_UNUSED_23; + x = _LIB9P_LO_UNUSED_22; + x = _LIB9P_LO_UNUSED_21; + x = LIB9P_LO_SYNC; + x = LIB9P_LO_CLOEXEC; + x = LIB9P_LO_NOATIME; + x = LIB9P_LO_NOFOLLOW; + x = LIB9P_LO_DIRECTORY; + x = LIB9P_LO_LARGEFILE; + x = LIB9P_LO_DIRECT; + x = LIB9P_LO_BSD_FASYNC; + x = LIB9P_LO_DSYNC; + x = LIB9P_LO_NONBLOCK; + x = LIB9P_LO_APPEND; + x = LIB9P_LO_TRUNC; + x = LIB9P_LO_NOCTTY; + x = LIB9P_LO_EXCL; + x = LIB9P_LO_CREATE; + x = _LIB9P_LO_UNUSED_5; + x = _LIB9P_LO_UNUSED_4; + x = _LIB9P_LO_UNUSED_3; + x = _LIB9P_LO_UNUSED_2; + x = LIB9P_LO_FLAG_MASK; + x = LIB9P_LO_MODE_RDONLY; + x = LIB9P_LO_MODE_WRONLY; + x = LIB9P_LO_MODE_RDWR; + x = LIB9P_LO_MODE_NOACCESS; + x = LIB9P_LO_MODE_MASK; + x = LIB9P_DT_UNKNOWN; + x = LIB9P_DT_PIPE; + x = LIB9P_DT_CHAR_DEV; + x = LIB9P_DT_DIRECTORY; + x = LIB9P_DT_BLOCK_DEV; + x = LIB9P_DT_REGULAR; + x = LIB9P_DT_SYMLINK; + x = LIB9P_DT_SOCKET; + x = _LIB9P_DT_WHITEOUT; + x = _LIB9P_MODE_UNUSED_31; + x = _LIB9P_MODE_UNUSED_30; + x = _LIB9P_MODE_UNUSED_29; + x = _LIB9P_MODE_UNUSED_28; + x = _LIB9P_MODE_UNUSED_27; + x = _LIB9P_MODE_UNUSED_26; + x = _LIB9P_MODE_UNUSED_25; + x = _LIB9P_MODE_UNUSED_24; + x = _LIB9P_MODE_UNUSED_23; + x = _LIB9P_MODE_UNUSED_22; + x = _LIB9P_MODE_UNUSED_21; + x = _LIB9P_MODE_UNUSED_20; + x = _LIB9P_MODE_UNUSED_19; + x = _LIB9P_MODE_UNUSED_18; + x = _LIB9P_MODE_UNUSED_17; + x = _LIB9P_MODE_UNUSED_16; + x = LIB9P_MODE_PERM_SETGROUP; + x = LIB9P_MODE_PERM_SETUSER; + x = LIB9P_MODE_PERM_STICKY; + x = LIB9P_MODE_PERM_OWNER_R; + x = LIB9P_MODE_PERM_OWNER_W; + x = LIB9P_MODE_PERM_OWNER_X; + x = LIB9P_MODE_PERM_GROUP_R; + x = LIB9P_MODE_PERM_GROUP_W; + x = LIB9P_MODE_PERM_GROUP_X; + x = LIB9P_MODE_PERM_OTHER_R; + x = LIB9P_MODE_PERM_OTHER_W; + x = LIB9P_MODE_PERM_OTHER_X; + x = LIB9P_MODE_PERM_MASK; + x = LIB9P_MODE_FMT_PIPE; + x = LIB9P_MODE_FMT_CHAR_DEV; + x = LIB9P_MODE_FMT_DIRECTORY; + x = LIB9P_MODE_FMT_BLOCK_DEV; + x = LIB9P_MODE_FMT_REGULAR; + x = LIB9P_MODE_FMT_SYMLINK; + x = LIB9P_MODE_FMT_SOCKET; + x = LIB9P_MODE_FMT_MASK; + x = LIB9P_B4_FALSE; + x = LIB9P_B4_TRUE; + x = _LIB9P_GETATTR_UNUSED_63; + x = _LIB9P_GETATTR_UNUSED_62; + x = _LIB9P_GETATTR_UNUSED_61; + x = _LIB9P_GETATTR_UNUSED_60; + x = _LIB9P_GETATTR_UNUSED_59; + x = _LIB9P_GETATTR_UNUSED_58; + x = _LIB9P_GETATTR_UNUSED_57; + x = _LIB9P_GETATTR_UNUSED_56; + x = _LIB9P_GETATTR_UNUSED_55; + x = _LIB9P_GETATTR_UNUSED_54; + x = _LIB9P_GETATTR_UNUSED_53; + x = _LIB9P_GETATTR_UNUSED_52; + x = _LIB9P_GETATTR_UNUSED_51; + x = _LIB9P_GETATTR_UNUSED_50; + x = _LIB9P_GETATTR_UNUSED_49; + x = _LIB9P_GETATTR_UNUSED_48; + x = _LIB9P_GETATTR_UNUSED_47; + x = _LIB9P_GETATTR_UNUSED_46; + x = _LIB9P_GETATTR_UNUSED_45; + x = _LIB9P_GETATTR_UNUSED_44; + x = _LIB9P_GETATTR_UNUSED_43; + x = _LIB9P_GETATTR_UNUSED_42; + x = _LIB9P_GETATTR_UNUSED_41; + x = _LIB9P_GETATTR_UNUSED_40; + x = _LIB9P_GETATTR_UNUSED_39; + x = _LIB9P_GETATTR_UNUSED_38; + x = _LIB9P_GETATTR_UNUSED_37; + x = _LIB9P_GETATTR_UNUSED_36; + x = _LIB9P_GETATTR_UNUSED_35; + x = _LIB9P_GETATTR_UNUSED_34; + x = _LIB9P_GETATTR_UNUSED_33; + x = _LIB9P_GETATTR_UNUSED_32; + x = _LIB9P_GETATTR_UNUSED_31; + x = _LIB9P_GETATTR_UNUSED_30; + x = _LIB9P_GETATTR_UNUSED_29; + x = _LIB9P_GETATTR_UNUSED_28; + x = _LIB9P_GETATTR_UNUSED_27; + x = _LIB9P_GETATTR_UNUSED_26; + x = _LIB9P_GETATTR_UNUSED_25; + x = _LIB9P_GETATTR_UNUSED_24; + x = _LIB9P_GETATTR_UNUSED_23; + x = _LIB9P_GETATTR_UNUSED_22; + x = _LIB9P_GETATTR_UNUSED_21; + x = _LIB9P_GETATTR_UNUSED_20; + x = _LIB9P_GETATTR_UNUSED_19; + x = _LIB9P_GETATTR_UNUSED_18; + x = _LIB9P_GETATTR_UNUSED_17; + x = _LIB9P_GETATTR_UNUSED_16; + x = _LIB9P_GETATTR_UNUSED_15; + x = _LIB9P_GETATTR_UNUSED_14; + x = LIB9P_GETATTR_DATA_VERSION; + x = LIB9P_GETATTR_GEN; + x = LIB9P_GETATTR_BTIME; + x = LIB9P_GETATTR_BLOCKS; + x = LIB9P_GETATTR_SIZE; + x = LIB9P_GETATTR_INO; + x = LIB9P_GETATTR_CTIME; + x = LIB9P_GETATTR_MTIME; + x = LIB9P_GETATTR_ATIME; + x = LIB9P_GETATTR_RDEV; + x = LIB9P_GETATTR_GID; + x = LIB9P_GETATTR_UID; + x = LIB9P_GETATTR_NLINK; + x = LIB9P_GETATTR_MODE; + x = LIB9P_GETATTR_BASIC; + x = LIB9P_GETATTR_ALL; + x = _LIB9P_SETATTR_UNUSED_31; + x = _LIB9P_SETATTR_UNUSED_30; + x = _LIB9P_SETATTR_UNUSED_29; + x = _LIB9P_SETATTR_UNUSED_28; + x = _LIB9P_SETATTR_UNUSED_27; + x = _LIB9P_SETATTR_UNUSED_26; + x = _LIB9P_SETATTR_UNUSED_25; + x = _LIB9P_SETATTR_UNUSED_24; + x = _LIB9P_SETATTR_UNUSED_23; + x = _LIB9P_SETATTR_UNUSED_22; + x = _LIB9P_SETATTR_UNUSED_21; + x = _LIB9P_SETATTR_UNUSED_20; + x = _LIB9P_SETATTR_UNUSED_19; + x = _LIB9P_SETATTR_UNUSED_18; + x = _LIB9P_SETATTR_UNUSED_17; + x = _LIB9P_SETATTR_UNUSED_16; + x = _LIB9P_SETATTR_UNUSED_15; + x = _LIB9P_SETATTR_UNUSED_14; + x = _LIB9P_SETATTR_UNUSED_13; + x = _LIB9P_SETATTR_UNUSED_12; + x = _LIB9P_SETATTR_UNUSED_11; + x = _LIB9P_SETATTR_UNUSED_10; + x = _LIB9P_SETATTR_UNUSED_9; + x = LIB9P_SETATTR_MTIME_SET; + x = LIB9P_SETATTR_ATIME_SET; + x = LIB9P_SETATTR_CTIME; + x = LIB9P_SETATTR_MTIME; + x = LIB9P_SETATTR_ATIME; + x = LIB9P_SETATTR_SIZE; + x = LIB9P_SETATTR_GID; + x = LIB9P_SETATTR_UID; + x = LIB9P_SETATTR_MODE; + x = LIB9P_LOCK_TYPE_RDLCK; + x = LIB9P_LOCK_TYPE_WRLCK; + x = LIB9P_LOCK_TYPE_UNLCK; + x = _LIB9P_LOCK_FLAGS_UNUSED_31; + x = _LIB9P_LOCK_FLAGS_UNUSED_30; + x = _LIB9P_LOCK_FLAGS_UNUSED_29; + x = _LIB9P_LOCK_FLAGS_UNUSED_28; + x = _LIB9P_LOCK_FLAGS_UNUSED_27; + x = _LIB9P_LOCK_FLAGS_UNUSED_26; + x = _LIB9P_LOCK_FLAGS_UNUSED_25; + x = _LIB9P_LOCK_FLAGS_UNUSED_24; + x = _LIB9P_LOCK_FLAGS_UNUSED_23; + x = _LIB9P_LOCK_FLAGS_UNUSED_22; + x = _LIB9P_LOCK_FLAGS_UNUSED_21; + x = _LIB9P_LOCK_FLAGS_UNUSED_20; + x = _LIB9P_LOCK_FLAGS_UNUSED_19; + x = _LIB9P_LOCK_FLAGS_UNUSED_18; + x = _LIB9P_LOCK_FLAGS_UNUSED_17; + x = _LIB9P_LOCK_FLAGS_UNUSED_16; + x = _LIB9P_LOCK_FLAGS_UNUSED_15; + x = _LIB9P_LOCK_FLAGS_UNUSED_14; + x = _LIB9P_LOCK_FLAGS_UNUSED_13; + x = _LIB9P_LOCK_FLAGS_UNUSED_12; + x = _LIB9P_LOCK_FLAGS_UNUSED_11; + x = _LIB9P_LOCK_FLAGS_UNUSED_10; + x = _LIB9P_LOCK_FLAGS_UNUSED_9; + x = _LIB9P_LOCK_FLAGS_UNUSED_8; + x = _LIB9P_LOCK_FLAGS_UNUSED_7; + x = _LIB9P_LOCK_FLAGS_UNUSED_6; + x = _LIB9P_LOCK_FLAGS_UNUSED_5; + x = _LIB9P_LOCK_FLAGS_UNUSED_4; + x = _LIB9P_LOCK_FLAGS_UNUSED_3; + x = _LIB9P_LOCK_FLAGS_UNUSED_2; + x = LIB9P_LOCK_FLAGS_RECLAIM; + x = LIB9P_LOCK_FLAGS_BLOCK; + x = LIB9P_LOCK_STATUS_SUCCESS; + x = LIB9P_LOCK_STATUS_BLOCKED; + x = LIB9P_LOCK_STATUS_ERROR; + x = LIB9P_LOCK_STATUS_GRACE; + x = LIB9P_TMSG_MAX_IOV; + x = LIB9P_TMSG_MAX_IOV; + x = LIB9P_TMSG_MAX_COPY; + x = LIB9P_TMSG_MAX_COPY; + x = LIB9P_TMSG_MAX_COPY; + x = LIB9P_TMSG_MAX_COPY; + x = LIB9P_TMSG_MAX_COPY; + x = LIB9P_TMSG_MAX_COPY; + x = LIB9P_RMSG_MAX_IOV; + x = LIB9P_RMSG_MAX_IOV; + x = LIB9P_RMSG_MAX_IOV; + x = LIB9P_RMSG_MAX_COPY; + return 0; +} diff --git a/lib9p/tests/test_compile.c.gen b/lib9p/tests/test_compile.c.gen new file mode 100755 index 0000000..47046b3 --- /dev/null +++ b/lib9p/tests/test_compile.c.gen @@ -0,0 +1,19 @@ +#!/bin/sh +# lib9p/tests/test_compile.c.gen - Generate code to make sure all generated macros work +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +generated_h=$1 +outfile=$2 + +{ + echo "/* ${outfile} - Generated by $0. DO NOT EDIT! */" + echo + echo "#include <lib9p/9p.h>" + echo 'int main(void) {' + echo ' [[gnu::unused]] uint64_t x;' + sed -nE 's/^\s*#\s*define\s*(\S[^ (]*)\s.*/ x = \1;/p' <"$generated_h" + echo ' return 0;' + echo '}' +} >"$outfile" diff --git a/lib9p/tests/test_compile_config/config.h b/lib9p/tests/test_compile_config/config.h new file mode 100644 index 0000000..cc8eec1 --- /dev/null +++ b/lib9p/tests/test_compile_config/config.h @@ -0,0 +1,38 @@ +/* config.h - Compile-time configuration for lib9p/test/test_compile + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* 9P *************************************************************************/ + +#define CONFIG_9P_MAX_ERR_SIZE 128 +#define CONFIG_9P_MAX_9P2000_e_WELEM 16 + +#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24) +#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16 +#define CONFIG_9P_SRV_MAX_FIDS 16 +#define CONFIG_9P_SRV_MAX_REQS 2 +#define CONFIG_9P_SRV_MAX_DEPTH 3 + +#define CONFIG_9P_ENABLE_9P2000 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_e 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_L 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_p9p 1 /* bool */ + +/* COROUTINE ******************************************************************/ + +#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (32*1024) +#define CONFIG_COROUTINE_NAME_LEN 16 +#define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */ +#define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */ +#define CONFIG_COROUTINE_DEBUG 0 /* bool */ +#define CONFIG_COROUTINE_VALGRIND 1 /* bool */ +#define CONFIG_COROUTINE_GDB 1 /* bool */ +#define CONFIG_COROUTINE_NUM 2 + +#endif /* _CONFIG_H_ */ diff --git a/lib9p/tests/test_server/CMakeLists.txt b/lib9p/tests/test_server/CMakeLists.txt new file mode 100644 index 0000000..5313917 --- /dev/null +++ b/lib9p/tests/test_server/CMakeLists.txt @@ -0,0 +1,43 @@ +# lib9p/tests/test_server/CMakeLists.txt - Build script for test_server executable +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +if (PICO_PLATFORM STREQUAL "host") + +# Compile ###################################################################### + +add_library(test_server_objs OBJECT + main.c +) +target_include_directories(test_server_objs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config) +target_include_directories(test_server_objs PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries(test_server_objs + libcr + libcr_ipc + libmisc + lib9p + lib9p_util + libhw_cr +) + +# Analyze the stack ############################################################ + +add_stack_analysis(test_server_stack.c test_server_objs) + +# Link ######################################################################### + +add_executable(test_server) +target_sources(test_server PRIVATE + test_server_stack.c + "$<TARGET_OBJECTS:test_server_objs>" +) + +# Embed ######################################################################## + +target_embed_sources(test_server_objs test_server static.h + static/README.md + static/Documentation/x.txt +) + +endif() diff --git a/cmd/srv9p/config/config.h b/lib9p/tests/test_server/config/config.h index 82be297..03143e1 100644 --- a/cmd/srv9p/config/config.h +++ b/lib9p/tests/test_server/config/config.h @@ -1,17 +1,19 @@ -/* config.h - Compile-time configuration for srv9p +/* config.h - Compile-time configuration for lib9p/test/test_server * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _CONFIG_H_ #define _CONFIG_H_ -#define CONFIG_SRV9P_NUM_CONNS 8 +#define _CONFIG_9P_NUM_SOCKS 8 +#define CONFIG_SRV9P_NUM_CONNS _CONFIG_9P_NUM_SOCKS /* 9P *************************************************************************/ -#define CONFIG_9P_PORT 564 +#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */ + /** * This max-msg-size is sized so that a Twrite message can return * 8KiB of data. @@ -29,31 +31,36 @@ * negotiated. In Plan 9 1e it was (8*1024)+128, and was bumped to * (8*1024)+160 in 2e and 3e. */ -#define CONFIG_9P_MAX_MSG_SIZE ((4*1024)+24) +#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24) /** * Maximum host-data-structure size. A message may be larger in * unmarshaled-host-structures than marshaled-net-bytes due to (1) - * struct padding, (2) nul-terminator byes for strings. + * struct padding, (2) array pointers. */ -#define CONFIG_9P_MAX_HOSTMSG_SIZE CONFIG_9P_MAX_MSG_SIZE+16 -#define CONFIG_9P_MAX_FIDS 16 -#define CONFIG_9P_MAX_REQS 2 -#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */ -#define CONFIG_9P_ENABLE_9P2000 1 /* bool */ -#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */ -#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */ +#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16 +#define CONFIG_9P_SRV_MAX_FIDS 16 +#define CONFIG_9P_SRV_MAX_REQS 2 +#define CONFIG_9P_SRV_MAX_DEPTH 3 + +#define CONFIG_9P_ENABLE_9P2000 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_L 0 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_p9p 0 /* bool */ /* COROUTINE ******************************************************************/ -#define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (32*1024) +#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (32*1024) #define CONFIG_COROUTINE_NAME_LEN 16 #define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */ #define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */ #define CONFIG_COROUTINE_DEBUG 0 /* bool */ #define CONFIG_COROUTINE_VALGRIND 1 /* bool */ -#define CONFIG_COROUTINE_NUM (1 /* usb_common */ +\ - 1 /* usb_keyboard */ +\ - CONFIG_SRV9P_NUM_CONNS /* accept+read */ +\ - (CONFIG_9P_MAX_REQS*CONFIG_SRV9P_NUM_CONNS) /* work+write */ ) +#define CONFIG_COROUTINE_GDB 1 /* bool */ +#define CONFIG_COROUTINE_NUM ( \ + 1 /* usb_common */ + \ + 1 /* usb_keyboard */ + \ + CONFIG_SRV9P_NUM_CONNS /* accept+read */ + \ + (CONFIG_9P_SRV_MAX_REQS*CONFIG_SRV9P_NUM_CONNS) /* work+write */ ) #endif /* _CONFIG_H_ */ diff --git a/lib9p/tests/test_server/main.c b/lib9p/tests/test_server/main.c new file mode 100644 index 0000000..a31c083 --- /dev/null +++ b/lib9p/tests/test_server/main.c @@ -0,0 +1,203 @@ +/* lib9p/tests/test_server/main.c - Main entry point for test 9P server + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <error.h> +#include <stdlib.h> /* for atoi() */ + +#include <lib9p/srv.h> +#include <libcr/coroutine.h> +#include <libhw/generic/net.h> +#include <libhw/generic/alarmclock.h> +#include <libhw/host_alarmclock.h> +#include <libhw/host_net.h> +#include <libmisc/macro.h> +#include <util9p/static.h> + +#include "static.h" + +/* configuration **************************************************************/ + +#include "config.h" + +#ifndef CONFIG_SRV9P_NUM_CONNS + #error config.h must define CONFIG_SRV9P_NUM_CONNS +#endif + +/* globals ********************************************************************/ + +static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *, struct lib9p_s); + +const char *hexdig = "0123456789abcdef"; + +struct { + uint16_t port; + struct hostnet_tcp_listener listeners[CONFIG_SRV9P_NUM_CONNS]; + struct lib9p_srv srv; +} globals = { + .srv = (struct lib9p_srv){ + .rootdir = get_root, + }, +}; + +/* api ************************************************************************/ + +struct api_file { + uint64_t pathnum; +}; +LO_IMPLEMENTATION_H(lib9p_srv_file, struct api_file, api); +LO_IMPLEMENTATION_H(lib9p_srv_fio, struct api_file, api); + +LO_IMPLEMENTATION_C(lib9p_srv_file, struct api_file, api, static); +LO_IMPLEMENTATION_C(lib9p_srv_fio, struct api_file, api, static); + +static void api_free(struct api_file *self) { + assert(self); +} +static struct lib9p_qid api_qid(struct api_file *self) { + assert(self); + return (struct lib9p_qid){ + .type = LIB9P_QT_FILE, + .vers = 1, + .path = self->pathnum, + }; +} + +static struct lib9p_stat api_stat(struct api_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + return (struct lib9p_stat){ + .kern_type = 0, + .kern_dev = 0, + .file_qid = api_qid(self), + .file_mode = 0222, + .file_atime = UTIL9P_ATIME, + .file_mtime = UTIL9P_MTIME, + .file_size = 0, + .file_name = lib9p_str("shutdown"), + .file_owner_uid = lib9p_str("root"), + .file_owner_gid = lib9p_str("root"), + .file_last_modified_uid = lib9p_str("root"), + .file_extension = lib9p_str(NULL), + .file_owner_n_uid = 0, + .file_owner_n_gid = 0, + .file_last_modified_n_uid = 0, + }; +} +static void api_wstat(struct api_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_stat) { + assert(self); + assert(ctx); + lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot wstat API file"); +} +static void api_remove(struct api_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot remove API file"); +} + +LIB9P_SRV_NOTDIR(struct api_file, api) + +static lo_interface lib9p_srv_fio api_fopen(struct api_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) { + assert(self); + assert(ctx); + return lo_box_api_as_lib9p_srv_fio(self); +} + +static void api_iofree(struct api_file *self) { + assert(self); +} + +static uint32_t api_iounit(struct api_file *self) { + assert(self); + return 0; +} + +static uint32_t api_pwrite(struct api_file *self, struct lib9p_srv_ctx *ctx, void *buf, uint32_t byte_count, uint64_t LM_UNUSED(offset)) { + assert(self); + assert(ctx); + assert(buf); + if (byte_count == 0) + return 0; + for (int i = 0; i < CONFIG_SRV9P_NUM_CONNS; i++) + LO_CALL(lo_box_hostnet_tcplist_as_net_stream_listener(&globals.listeners[i]), close); + return byte_count; +} +static void api_pread(struct api_file *LM_UNUSED(self), struct lib9p_srv_ctx *LM_UNUSED(ctx), + uint32_t LM_UNUSED(byte_count), uint64_t LM_UNUSED(byte_offset), + struct iovec *LM_UNUSED(ret)) { + assert_notreached("not readable"); +} + +#define lo_box_api_as_lib9p_srv_file(obj) util9p_box(api, obj) + +/* file tree ******************************************************************/ + +enum { PATH_BASE = __COUNTER__ }; +#define PATH_COUNTER __COUNTER__ - PATH_BASE + +#define STATIC_FILE(STRNAME, SYMNAME) \ + UTIL9P_STATIC_FILE(PATH_COUNTER, STRNAME, \ + .data_start = _binary_static_##SYMNAME##_start, \ + .data_end = _binary_static_##SYMNAME##_end) +#define STATIC_DIR(STRNAME, ...) \ + UTIL9P_STATIC_DIR(PATH_COUNTER, STRNAME, __VA_ARGS__) + +struct lib9p_srv_file root = + STATIC_DIR("", + STATIC_DIR("Documentation", + STATIC_FILE("x", Documentation_x_txt), + ), + STATIC_FILE("README.md", README_md), + lo_box_api_as_lib9p_srv_file(&(struct api_file){.pathnum = PATH_COUNTER}), + ); + +static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), struct lib9p_s LM_UNUSED(treename)) { + return root; +} + +/* main ***********************************************************************/ + +static COROUTINE read_cr(void *_i) { + int i = *((int *)_i); + cr_begin(); + + hostnet_tcp_listener_init(&globals.listeners[i], globals.port); + + lib9p_srv_read_cr(&globals.srv, lo_box_hostnet_tcplist_as_net_stream_listener(&globals.listeners[i])); + + cr_end(); +} + +static COROUTINE init_cr(void *) { + cr_begin(); + + sleep_for_ms(1); + + for (int i = 0; i < CONFIG_SRV9P_NUM_CONNS; i++) { + char name[] = {'r', 'e', 'a', 'd', '-', hexdig[i], '\0'}; + if (!coroutine_add(name, read_cr, &i)) + error(1, 0, "coroutine_add(read_cr, &i)"); + } + for (int i = 0; i < 2*CONFIG_SRV9P_NUM_CONNS; i++) { + char name[] = {'w', 'r', 'i', 't', 'e', '-', hexdig[i], '\0'}; + if (!coroutine_add(name, lib9p_srv_write_cr, &globals.srv)) + error(1, 0, "coroutine_add(lib9p_srv_write_cr, &globals.srv)"); + } + + cr_exit(); +} + +int main(int argc, char *argv[]) { + if (argc != 2) + error(2, 0, "usage: %s PORT_NUMBER", argv[0]); + globals.port = atoi(argv[1]); + struct hostclock clock_monotonic = { + .clock_id = CLOCK_MONOTONIC, + }; + bootclock = lo_box_hostclock_as_alarmclock(&clock_monotonic); + coroutine_add("init", init_cr, NULL); + coroutine_main(); + return 0; +} diff --git a/lib9p/tests/test_server/static/Documentation/x.txt b/lib9p/tests/test_server/static/Documentation/x.txt new file mode 100644 index 0000000..e85ee4e --- /dev/null +++ b/lib9p/tests/test_server/static/Documentation/x.txt @@ -0,0 +1,7 @@ +<!-- + Documentation/x.txt - test static file + + Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later +--> +foo diff --git a/lib9p/tests/test_server/static/README.md b/lib9p/tests/test_server/static/README.md new file mode 100644 index 0000000..c2d88ed --- /dev/null +++ b/lib9p/tests/test_server/static/README.md @@ -0,0 +1,7 @@ +<!-- + README.md - test static file + + Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later +--> +Hello, world! diff --git a/lib9p/utf8.h b/lib9p/utf8.h new file mode 100644 index 0000000..5ffd674 --- /dev/null +++ b/lib9p/utf8.h @@ -0,0 +1,34 @@ +/* lib9p/utf8.h - Internal UTF-8 validation + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIB9P_UTF8_H_ +#define _LIB9P_UTF8_H_ + +static inline bool _is_valid_utf8(uint8_t *str, size_t len, bool forbid_nul) { + uint32_t ch; + uint8_t chlen; + assert(str); + for (size_t pos = 0; pos < len;) { + if ((str[pos] & 0b10000000) == 0b00000000) { ch = str[pos] & 0b01111111; chlen = 1; } + else if ((str[pos] & 0b11100000) == 0b11000000) { ch = str[pos] & 0b00011111; chlen = 2; } + else if ((str[pos] & 0b11110000) == 0b11100000) { ch = str[pos] & 0b00001111; chlen = 3; } + else if ((str[pos] & 0b11111000) == 0b11110000) { ch = str[pos] & 0b00000111; chlen = 4; } + else return false; + if ((ch == 0 && (chlen != 1 || forbid_nul)) || pos + chlen > len) return false; + for (uint8_t i = 1; i < chlen; i++) { + if ((str[pos+i] & 0b11000000) != 0b10000000) return false; + ch = (ch << 6) | (str[pos+i] & 0b00111111); + } + if (ch > 0x10FFFF) return false; + pos += chlen; + } + return true; +} + +#define is_valid_utf8(str, len) _is_valid_utf8(str, len, false) +#define is_valid_utf8_without_nul(str, len) _is_valid_utf8(str, len, true) + +#endif /* _LIB9P_UTF8_H_ */ diff --git a/lib9p_util/CMakeLists.txt b/lib9p_util/CMakeLists.txt new file mode 100644 index 0000000..2e5790e --- /dev/null +++ b/lib9p_util/CMakeLists.txt @@ -0,0 +1,13 @@ +# lib9p_util/CMakeLists.txt - Utilities for using the lib9p server +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +add_library(lib9p_util INTERFACE) +target_include_directories(lib9p_util PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_sources(lib9p_util INTERFACE + static.c +) +target_link_libraries(lib9p_util INTERFACE + lib9p +) diff --git a/lib9p_util/include/util9p/static.h b/lib9p_util/include/util9p/static.h new file mode 100644 index 0000000..0b391b8 --- /dev/null +++ b/lib9p_util/include/util9p/static.h @@ -0,0 +1,85 @@ +/* util9p/static.h - Serve static files over 9P + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _UTIL9P_STATIC_H_ +#define _UTIL9P_STATIC_H_ + +#include <lib9p/srv.h> + +#define util9p_box(nam, obj) \ + ((struct lib9p_srv_file){ \ + .self = obj, \ + .vtable = (void*)&_lo_##nam##_lib9p_srv_file_vtable, \ + }) + +#define UTIL9P_ATIME 1728337905 +#define UTIL9P_MTIME 1728337904 + +/* Common *********************************************************************/ + +typedef struct { + char *u_name; + uint32_t u_num; + char *g_name; + uint32_t g_num; + char *m_name; + uint32_t m_num; + + uint64_t pathnum; + char *name; + lib9p_dm_t perm; + uint32_t atime, mtime; +} _util9p_static_common; + +#define UTIL9P_STATIC_COMMON(PATH, STRNAME, MODE) \ + { \ + .u_name = "root", .u_num = 0, /* owner user */ \ + .g_name = "root", .g_num = 0, /* owner group */ \ + .m_name = "root", .m_num = 0, /* last-modified-by user */ \ + \ + .pathnum = PATH, \ + .name = STRNAME, \ + .perm = MODE, \ + .atime = UTIL9P_ATIME, \ + .mtime = UTIL9P_MTIME, \ + } + +/* Dir ************************************************************************/ + +struct util9p_static_dir { + _util9p_static_common; + + /* NULL-terminated */ + lo_interface lib9p_srv_file members[]; +}; +LO_IMPLEMENTATION_H(lib9p_srv_file, struct util9p_static_dir, util9p_static_dir); +#define lo_box_util9p_static_dir_as_lib9p_srv_file(obj) util9p_box(util9p_static_dir, obj) + +#define UTIL9P_STATIC_DIR(PATH, STRNAME, ...) \ + lo_box_util9p_static_dir_as_lib9p_srv_file(&((struct util9p_static_dir){ \ + ._util9p_static_common = UTIL9P_STATIC_COMMON(PATH, STRNAME, 0555), \ + .members = { __VA_ARGS__ LO_NULL(lib9p_srv_file) }, \ + })) + +/* File ***********************************************************************/ + +struct util9p_static_file { + _util9p_static_common; + + char *data_start; /* must not be NULL */ + char *data_end; /* may be NULL, in which case data_size is used */ + size_t data_size; /* only used if .data_end==NULL */ +}; +LO_IMPLEMENTATION_H(lib9p_srv_file, struct util9p_static_file, util9p_static_file); +#define lo_box_util9p_static_file_as_lib9p_srv_file(obj) util9p_box(util9p_static_file, obj) + +#define UTIL9P_STATIC_FILE(PATH, STRNAME, ...) \ + lo_box_util9p_static_file_as_lib9p_srv_file(&((struct util9p_static_file){ \ + ._util9p_static_common = UTIL9P_STATIC_COMMON(PATH, STRNAME, 0444), \ + __VA_ARGS__ \ + })) + +#endif /* _UTIL9P_STATIC_H_ */ diff --git a/lib9p_util/static.c b/lib9p_util/static.c new file mode 100644 index 0000000..7f1e6b7 --- /dev/null +++ b/lib9p_util/static.c @@ -0,0 +1,252 @@ +/* lib9p_util/static.c - Serve static files over 9P + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/assert.h> +#include <libmisc/macro.h> + +#include <util9p/static.h> + +LO_IMPLEMENTATION_C(lib9p_srv_file, struct util9p_static_dir, util9p_static_dir, static); +LO_IMPLEMENTATION_C(lib9p_srv_file, struct util9p_static_file, util9p_static_file, static); + +LO_IMPLEMENTATION_H(lib9p_srv_dio, struct util9p_static_dir, util9p_static_dir); +LO_IMPLEMENTATION_C(lib9p_srv_dio, struct util9p_static_dir, util9p_static_dir, static); + +LO_IMPLEMENTATION_H(lib9p_srv_fio, struct util9p_static_file, util9p_static_file); +LO_IMPLEMENTATION_C(lib9p_srv_fio, struct util9p_static_file, util9p_static_file, static); + +/* dir ************************************************************************/ + +static void util9p_static_dir_free(struct util9p_static_dir *self) { + assert(self); +} +static struct lib9p_qid util9p_static_dir_qid(struct util9p_static_dir *self) { + assert(self); + + return (struct lib9p_qid){ + .type = LIB9P_QT_DIR, + .vers = 1, + .path = self->pathnum, + }; +} + +static struct lib9p_stat util9p_static_dir_stat(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + return (struct lib9p_stat){ + .kern_type = 0, + .kern_dev = 0, + .file_qid = util9p_static_dir_qid(self), + .file_mode = LIB9P_DM_DIR | (self->perm & 0555), + .file_atime = self->atime, + .file_mtime = self->mtime, + .file_size = 0, + .file_name = lib9p_str(self->name), + .file_owner_uid = lib9p_str(self->u_name), + .file_owner_gid = lib9p_str(self->g_name), + .file_last_modified_uid = lib9p_str(self->m_name), + .file_extension = lib9p_str(NULL), + .file_owner_n_uid = self->u_num, + .file_owner_n_gid = self->g_num, + .file_last_modified_n_uid = self->m_num, + }; +} +static void util9p_static_dir_wstat(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx, + struct lib9p_stat) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); +} +static void util9p_static_dir_remove(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); +} + +static lo_interface lib9p_srv_file util9p_static_dir_dwalk(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx, + struct lib9p_s childname) { + assert(self); + assert(ctx); + + for (size_t i = 0; !LO_IS_NULL(self->members[i]); i++) { + lo_interface lib9p_srv_file file = self->members[i]; + struct lib9p_stat stat = LO_CALL(file, stat, ctx); + if (lib9p_ctx_has_error(&ctx->basectx)) + break; + lib9p_stat_assert(stat); + if (lib9p_str_eq(stat.file_name, childname)) + return file; + } + lib9p_error(&ctx->basectx, + LINUX_ENOENT, "no such file or directory"); + return LO_NULL(lib9p_srv_file); +} + +static lo_interface lib9p_srv_file util9p_static_dir_dcreate(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx, + struct lib9p_s LM_UNUSED(childname), + lib9p_dm_t LM_UNUSED(perm), lib9p_o_t LM_UNUSED(flags)) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); + return LO_NULL(lib9p_srv_file); +} + +LIB9P_SRV_NOTFILE(struct util9p_static_dir, util9p_static_dir); + +static lo_interface lib9p_srv_dio util9p_static_dir_dopen(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + return lo_box_util9p_static_dir_as_lib9p_srv_dio(self); +} +static void util9p_static_dir_iofree(struct util9p_static_dir *self) { + assert(self); +} +static size_t util9p_static_dir_dread(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx, + uint8_t *buf, + uint32_t byte_count, + size_t _obj_offset) { + assert(self); + assert(ctx); + + uint32_t byte_offset = 0; + size_t obj_offset = _obj_offset; + while (!LO_IS_NULL(self->members[obj_offset])) { + lo_interface lib9p_srv_file file = self->members[obj_offset]; + struct lib9p_stat stat = LO_CALL(file, stat, ctx); + if (lib9p_ctx_has_error(&ctx->basectx)) + break; + lib9p_stat_assert(stat); + uint32_t nbytes = lib9p_stat_marshal(&ctx->basectx, byte_count-byte_offset, &stat, + &buf[byte_offset]); + if (!nbytes) { + if (obj_offset == _obj_offset) + lib9p_error(&ctx->basectx, + LINUX_ERANGE, "stat object does not fit into negotiated max message size"); + break; + } + byte_offset += nbytes; + obj_offset++; + } + return obj_offset - _obj_offset; +} + +/* file ***********************************************************************/ + +static void util9p_static_file_free(struct util9p_static_file *self) { + assert(self); +} +static struct lib9p_qid util9p_static_file_qid(struct util9p_static_file *self) { + assert(self); + + return (struct lib9p_qid){ + .type = LIB9P_QT_FILE, + .vers = 1, + .path = self->pathnum, + }; +} + +static inline size_t util9p_static_file_size(struct util9p_static_file *file) { + assert(file); + +#if __unix__ + assert(file->data_start); +#endif + if (!file->data_end) + return file->data_size; + return (size_t)((uintptr_t)file->data_end - (uintptr_t)file->data_start); +} + + +static struct lib9p_stat util9p_static_file_stat(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + return (struct lib9p_stat){ + .kern_type = 0, + .kern_dev = 0, + .file_qid = util9p_static_file_qid(self), + .file_mode = self->perm & 0444, + .file_atime = self->atime, + .file_mtime = self->mtime, + .file_size = (uint64_t)util9p_static_file_size(self), + .file_name = lib9p_str(self->name), + .file_owner_uid = lib9p_str(self->u_name), + .file_owner_gid = lib9p_str(self->g_name), + .file_last_modified_uid = lib9p_str(self->m_name), + .file_extension = lib9p_str(NULL), + .file_owner_n_uid = self->u_num, + .file_owner_n_gid = self->g_num, + .file_last_modified_n_uid = self->m_num, + }; +} +static void util9p_static_file_wstat(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx, + struct lib9p_stat) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); +} +static void util9p_static_file_remove(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); +} + +LIB9P_SRV_NOTDIR(struct util9p_static_file, util9p_static_file); + +static lo_interface lib9p_srv_fio util9p_static_file_fopen(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx, + bool rd, bool wr, bool trunc) { + assert(self); + assert(ctx); + assert(rd); + assert(!wr); + assert(!trunc); + return lo_box_util9p_static_file_as_lib9p_srv_fio(self); +} +static void util9p_static_file_iofree(struct util9p_static_file *self) { + assert(self); +} +static uint32_t util9p_static_file_iounit(struct util9p_static_file *self) { + assert(self); + return 0; +} +static void util9p_static_file_pread(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx, + uint32_t byte_count, uint64_t byte_offset, + struct iovec *ret) { + assert(self); + assert(ctx); + assert(ret); + + size_t data_size = util9p_static_file_size(self); + + if (byte_offset > (uint64_t)data_size) { + lib9p_error(&ctx->basectx, + LINUX_EINVAL, "offset is past end-of-file length"); + return; + } + + size_t beg_off = (size_t)byte_offset; + size_t end_off = beg_off + (size_t)byte_count; + if (end_off > data_size) + end_off = data_size; + ret->iov_base = &self->data_start[beg_off]; + ret->iov_len = end_off-beg_off; +} +static uint32_t util9p_static_file_pwrite(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx, + void *LM_UNUSED(buf), + uint32_t LM_UNUSED(byte_count), + uint64_t LM_UNUSED(byte_offset)) { + assert(self); + assert(ctx); + + lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); + return 0; +} diff --git a/libcr/CMakeLists.txt b/libcr/CMakeLists.txt index fbc7618..80a4ece 100644 --- a/libcr/CMakeLists.txt +++ b/libcr/CMakeLists.txt @@ -1,10 +1,10 @@ -# libcr/CMakeLists.txt - TODO +# libcr/CMakeLists.txt - Simple embeddable coroutine implementation # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libcr INTERFACE) -target_include_directories(libcr SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_include_directories(libcr PUBLIC INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) target_sources(libcr INTERFACE coroutine.c ) @@ -14,3 +14,31 @@ target_link_libraries(libcr INTERFACE target_compile_options(libcr INTERFACE -fno-split-stack ) + +set(cfg_matrix + "CONFIG_COROUTINE_MEASURE_STACK;[0;1]" + "CONFIG_COROUTINE_PROTECT_STACK;[0;1]" + "CONFIG_COROUTINE_DEBUG;[0;1]" + "CONFIG_COROUTINE_VALGRIND;[0;1]" + "CONFIG_COROUTINE_GDB;[0;1]" +) +function(add_libcr_matrix_test n defs) + add_executable("test_matrix${n}" "tests/test_matrix.c") + target_link_libraries("test_matrix${n}" "libcr") + target_include_directories("test_matrix${n}" PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_matrix) + target_compile_definitions("test_matrix${n}" PUBLIC "${defs}") + if ("CONFIG_COROUTINE_VALGRIND=1" IN_LIST defs) + add_test( + NAME "libcr/test_matrix${n}" + COMMAND valgrind --error-exitcode=2 "./test_matrix${n}" + ) + else() + add_test( + NAME "libcr/test_matrix${n}" + COMMAND "./test_matrix${n}" + ) + endif() +endfunction() +if (ENABLE_TESTS) + apply_matrix(add_libcr_matrix_test "${cfg_matrix}") +endif() diff --git a/libcr/coroutine.c b/libcr/coroutine.c index 726b732..bf44219 100644 --- a/libcr/coroutine.c +++ b/libcr/coroutine.c @@ -1,15 +1,15 @@ /* libcr/coroutine.c - Simple embeddable coroutine implementation * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <setjmp.h> /* for setjmp(), longjmp(), jmp_buf */ #include <stdint.h> /* for uint8_t */ #include <stdlib.h> /* for aligned_alloc(), free() */ #include <string.h> /* for strncpy(), memset() */ #include <libmisc/assert.h> +#include <libmisc/macro.h> #define LOG_NAME COROUTINE #include <libmisc/log.h> @@ -21,28 +21,32 @@ #include "config.h" -#ifndef CONFIG_COROUTINE_DEFAULT_STACK_SIZE - #error config.h must define CONFIG_COROUTINE_DEFAULT_STACK_SIZE -#endif #ifndef CONFIG_COROUTINE_NAME_LEN - #error config.h must define CONFIG_COROUTINE_NAME_LEN + #error config.h must define CONFIG_COROUTINE_NAME_LEN (non-negative integer) #endif #ifndef CONFIG_COROUTINE_NUM - #error config.h must define CONFIG_COROUTINE_NUM + #error config.h must define CONFIG_COROUTINE_NUM (non-negative integer) #endif #ifndef CONFIG_COROUTINE_MEASURE_STACK - #error config.h must define CONFIG_COROUTINE_MEASURE_STACK + #error config.h must define CONFIG_COROUTINE_MEASURE_STACK (bool) #endif #ifndef CONFIG_COROUTINE_PROTECT_STACK - #error config.h must define CONFIG_COROUTINE_PROTECT_STACK + #error config.h must define CONFIG_COROUTINE_PROTECT_STACK (bool) #endif #ifndef CONFIG_COROUTINE_DEBUG - #error config.h must define CONFIG_COROUTINE_DEBUG + #error config.h must define CONFIG_COROUTINE_DEBUG (bool) #endif #ifndef CONFIG_COROUTINE_VALGRIND - #error config.h must define CONFIG_COROUTINE_VALGRIND + #error config.h must define CONFIG_COROUTINE_VALGRIND (bool) +#endif +#ifndef CONFIG_COROUTINE_GDB + #error config.h must define CONFIG_COROUTINE_GDB (bool) #endif +/* Enforce that CONFIG_COROUTINE_NUM is greater than 1, to work around + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118212 */ +static_assert(CONFIG_COROUTINE_NUM > 1); + /* Implementation *************************************************************/ #if CONFIG_COROUTINE_VALGRIND @@ -126,8 +130,6 @@ * no longer exists. */ -#define ALWAYS_INLINE [[gnu::always_inline]] inline - /* platform support ***********************************************************/ /* As part of sbc-harness, this only really needs to support ARM-32, but being @@ -148,11 +150,26 @@ /* For a signal to be *in* the mask means that the signal is * *blocked*. */ - bool cr_is_in_intrhandler(void) { + #define _CR_SIG_SENTINEL SIGURG + #if CONFIG_COROUTINE_GDB + #define _CR_SIG_GDB SIGWINCH + #endif + +#if CONFIG_COROUTINE_VALGRIND + /* Hack around a bug in Valgrind where it runs the + * sigsuspend(tmp_sigmask)-triggered handler function with the + * original mask, not the `tmp_mask`. */ + static bool _cr_plat_in_sigsuspend = false; +#endif + + bool cr_plat_is_in_intrhandler(void) { +#if CONFIG_COROUTINE_VALGRIND + if (_cr_plat_in_sigsuspend) + return true; +#endif sigset_t cur_mask; - sigfillset(&cur_mask); sigprocmask(0, NULL, &cur_mask); - if (sigismember(&cur_mask, SIGHUP)) + if (sigismember(&cur_mask, _CR_SIG_SENTINEL)) /* Interrupts are disabled, so we cannot be in * an interrupt handler. */ return false; @@ -162,47 +179,63 @@ return false; } static inline bool _cr_plat_are_interrupts_enabled(void) { - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); sigset_t cur_mask; sigfillset(&cur_mask); sigprocmask(0, NULL, &cur_mask); - return !sigismember(&cur_mask, SIGHUP); + return !sigismember(&cur_mask, _CR_SIG_SENTINEL); } static inline void cr_plat_wait_for_interrupt(void) { - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); assert(!_cr_plat_are_interrupts_enabled()); sigset_t set; sigemptyset(&set); +#if CONFIG_COROUTINE_VALGRIND + _cr_plat_in_sigsuspend = true; +#endif sigsuspend(&set); - - sigfillset(&set); - sigprocmask(SIG_SETMASK, &set, NULL); +#if CONFIG_COROUTINE_VALGRIND + _cr_plat_in_sigsuspend = false; +#endif } bool _cr_plat_save_and_disable_interrupts(void) { - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); sigset_t all, old; sigfillset(&all); sigprocmask(SIG_SETMASK, &all, &old); - return !sigismember(&old, SIGHUP); + return !sigismember(&old, _CR_SIG_SENTINEL); } void _cr_plat_enable_interrupts(void) { - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); assert(!_cr_plat_are_interrupts_enabled()); sigset_t zero; sigemptyset(&zero); sigprocmask(SIG_SETMASK, &zero, NULL); } + #if CONFIG_COROUTINE_GDB + static void _cr_gdb_intrhandler(int LM_UNUSED(sig)) {} + #endif + static void cr_plat_init(void) { + #if CONFIG_COROUTINE_GDB + int r; + struct sigaction action = { + .sa_handler = _cr_gdb_intrhandler, + }; + r = sigaction(_CR_SIG_GDB, &action, NULL); + assert(r == 0); + #endif + } #elif __ARM_ARCH_6M__ && __ARM_EABI__ - bool cr_is_in_intrhandler(void) { + bool cr_plat_is_in_intrhandler(void) { uint32_t isr_number; asm volatile ("mrs %0, ipsr" : /* %0 */"=l"(isr_number) ); return isr_number != 0; } - ALWAYS_INLINE static bool _cr_plat_are_interrupts_enabled(void) { - assert(!cr_is_in_intrhandler()); + LM_ALWAYS_INLINE static bool _cr_plat_are_interrupts_enabled(void) { + assert(!cr_plat_is_in_intrhandler()); uint32_t primask; asm volatile ("mrs %0, PRIMASK" : /* %0 */"=l"(primask) @@ -210,8 +243,8 @@ return primask == 0; } - ALWAYS_INLINE static void cr_plat_wait_for_interrupt(void) { - assert(!cr_is_in_intrhandler()); + LM_ALWAYS_INLINE static void cr_plat_wait_for_interrupt(void) { + assert(!cr_plat_is_in_intrhandler()); assert(!_cr_plat_are_interrupts_enabled()); asm volatile ("wfi\n" "cpsie i\n" @@ -220,16 +253,17 @@ :::"memory"); } bool _cr_plat_save_and_disable_interrupts(void) { - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); bool were_enabled = _cr_plat_are_interrupts_enabled(); asm volatile ("cpsid i"); return were_enabled; } void _cr_plat_enable_interrupts(void) { - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); assert(!_cr_plat_are_interrupts_enabled()); asm volatile ("cpsie i"); } + static void cr_plat_init(void) {} #else #error unsupported platform (not __unix__, not __ARM_ARCH_6M__ && __ARM_EABI__) #endif @@ -240,7 +274,7 @@ #define CR_PLAT_STACK_GROWS_DOWNWARD 1 #if CONFIG_COROUTINE_MEASURE_STACK - ALWAYS_INLINE static uintptr_t cr_plat_get_sp(void) { + LM_ALWAYS_INLINE static uintptr_t cr_plat_get_sp(void) { uintptr_t sp; asm volatile ("mov %0, sp":"=r"(sp)); return sp; @@ -272,7 +306,7 @@ #define CR_PLAT_STACK_GROWS_DOWNWARD 1 #if CONFIG_COROUTINE_MEASURE_STACK - ALWAYS_INLINE static uintptr_t cr_plat_get_sp(void) { + LM_ALWAYS_INLINE static uintptr_t cr_plat_get_sp(void) { uintptr_t sp; asm volatile ("movq %%rsp, %0":"=r"(sp)); return sp; @@ -304,6 +338,7 @@ * 1. Allow us to inspect the buffer. * 2. Do *not* save the interrupt mask. */ + #include <setjmp.h> /* for setjmp(), longjmp(), jmp_buf */ typedef struct { jmp_buf raw; #if CONFIG_COROUTINE_MEASURE_STACK @@ -314,11 +349,14 @@ uintptr_t sp; #endif } cr_plat_jmp_buf; - static inline void _cr_plat_setjmp_pre(cr_plat_jmp_buf *env) { + static void _cr_plat_setjmp_pre(cr_plat_jmp_buf *env [[gnu::unused]]) { #if CONFIG_COROUTINE_MEASURE_STACK env->sp = cr_plat_get_sp(); #endif } + #if CONFIG_COROUTINE_MEASURE_STACK + static uintptr_t cr_plat_setjmp_get_sp(cr_plat_jmp_buf *env) { return env->sp; } + #endif /* cr_plat_setjmp *NEEDS* to be a preprocessor macro rather * than a real function, because [[gnu::returns_twice]] * doesn't work. @@ -344,32 +382,26 @@ } #endif -/* preprocessor macros ********************************************************/ - -/** Return `n` rounded up to the nearest multiple of `d` */ -#define ROUND_UP(n, d) ( ( ((n)+(d)-1) / (d) ) * (d) ) -#define ARRAY_LEN(arr) (sizeof(arr)/sizeof((arr)[0])) -#define NEXT_POWER_OF_2(x) ((1ULL)<<((sizeof(unsigned long long)*8)-__builtin_clzll(x))) - /* types **********************************************************************/ -enum coroutine_state { - CR_NONE = 0, /* this slot in the table is empty */ - CR_INITIALIZING, /* running, before cr_begin() */ - CR_RUNNING, /* running, after cr_begin() */ - CR_RUNNABLE, /* not running, but runnable */ - CR_PAUSED, /* not running, and not runnable */ -}; - struct coroutine { + /* 1. state *************************************************/ volatile enum coroutine_state state; - cr_plat_jmp_buf env; + + /* 2. name **************************************************/ + [[gnu::nonstring]] char name[CONFIG_COROUTINE_NAME_LEN]; + + /* 3. stack *************************************************/ + /* stack_size *includes* CR_STACK_GUARD at each end. */ size_t stack_size; + /* stack is the bottom of the CR_STACK_GUARD at the bottom of the stack. */ void *stack; #if CONFIG_COROUTINE_VALGRIND unsigned stack_id; #endif - char name[CONFIG_COROUTINE_NAME_LEN]; + + /* 4. env ***************************************************/ + cr_plat_jmp_buf env; }; /* constants ******************************************************************/ @@ -391,16 +423,20 @@ static const uint8_t stack_pattern[] = { }; #endif #if CONFIG_COROUTINE_PROTECT_STACK - #define STACK_GUARD_SIZE \ - ROUND_UP(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT) + #define CR_STACK_GUARD_SIZE \ + ((size_t)LM_ROUND_UP(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT)) #else - #define STACK_GUARD_SIZE 0 + #define CR_STACK_GUARD_SIZE ((size_t)0) #endif /* global variables ***********************************************************/ +static bool coroutine_initialized = false; static cr_plat_jmp_buf coroutine_add_env; static cr_plat_jmp_buf coroutine_main_env; +#if CONFIG_COROUTINE_GDB +static cr_plat_jmp_buf coroutine_gdb_env; +#endif /* * Invariants (and non-invariants): @@ -431,29 +467,47 @@ static struct { * compiler will optimize `%array_len` to &(array_len-1)`, (b) * we don't have to worry about funny wrap-around behavior * when head or tail overflow. */ - cid_t buf[NEXT_POWER_OF_2(CONFIG_COROUTINE_NUM)]; + cid_t buf[LM_NEXT_POWER_OF_2(CONFIG_COROUTINE_NUM)]; } coroutine_ringbuf = {0}; static cid_t coroutine_running = 0; +static size_t coroutine_cnt = 0; /* utility functions **********************************************************/ -static inline const char* coroutine_state_str(enum coroutine_state state) { - assert(state < ARRAY_LEN(coroutine_state_strs)); - return coroutine_state_strs[state]; -} - static inline void coroutine_ringbuf_push(cid_t val) { - coroutine_ringbuf.buf[coroutine_ringbuf.head++ % ARRAY_LEN(coroutine_ringbuf.buf)] = val; - assert((coroutine_ringbuf.head % ARRAY_LEN(coroutine_ringbuf.buf)) != - (coroutine_ringbuf.tail % ARRAY_LEN(coroutine_ringbuf.buf))); + coroutine_ringbuf.buf[coroutine_ringbuf.head++ % LM_ARRAY_LEN(coroutine_ringbuf.buf)] = val; + assert((coroutine_ringbuf.head % LM_ARRAY_LEN(coroutine_ringbuf.buf)) != + (coroutine_ringbuf.tail % LM_ARRAY_LEN(coroutine_ringbuf.buf))); } static inline cid_t coroutine_ringbuf_pop(void) { if (coroutine_ringbuf.tail == coroutine_ringbuf.head) return 0; - return coroutine_ringbuf.buf[coroutine_ringbuf.tail++ % ARRAY_LEN(coroutine_ringbuf.buf)]; + return coroutine_ringbuf.buf[coroutine_ringbuf.tail++ % LM_ARRAY_LEN(coroutine_ringbuf.buf)]; } +#if CONFIG_COROUTINE_GDB +LM_NEVER_INLINE void cr_gdb_breakpoint(void) { + /* Prevent the call from being optimized away. */ + asm (""); +} +LM_NEVER_INLINE void cr_gdb_readjmp(cr_plat_jmp_buf *env) { + if (!cr_plat_setjmp(&coroutine_gdb_env)) + cr_plat_longjmp(env, 2); +} +#define cr_setjmp(env) ({ \ + int val = cr_plat_setjmp(env); \ + if (val == 2) { \ + cr_gdb_breakpoint(); \ + cr_plat_longjmp(&coroutine_gdb_env, 1); \ + } \ + val; \ + }) +#else +#define cr_setjmp(env) cr_plat_setjmp(env) +#endif +#define cr_longjmp(env) cr_plat_longjmp(env, 1) + static inline void assert_cid(cid_t cid) { assert(cid > 0); assert(cid <= CONFIG_COROUTINE_NUM); @@ -461,7 +515,7 @@ static inline void assert_cid(cid_t cid) { assert(coroutine_table[cid-1].stack_size); uint8_t *stack = coroutine_table[cid-1].stack; assert(stack); - for (size_t i = 0; i < STACK_GUARD_SIZE; i++) { + for (size_t i = 0; i < CR_STACK_GUARD_SIZE; i++) { size_t j = coroutine_table[cid-1].stack_size - (i+1); assert(stack[i] == stack_pattern[i%sizeof(stack_pattern)]); assert(stack[j] == stack_pattern[j%sizeof(stack_pattern)]); @@ -478,10 +532,24 @@ static inline void assert_cid(cid_t cid) { /* coroutine_add() ************************************************************/ +LM_NEVER_INLINE +cid_t coroutine_allocate_cid(void) { + static cid_t last_created = 0; + + size_t base = last_created; + for (size_t shift = 0; shift < CONFIG_COROUTINE_NUM; shift++) { + cid_t child = ((base + shift) % CONFIG_COROUTINE_NUM) + 1; + if (coroutine_table[child-1].state == CR_NONE) { + last_created = child; + return child; + } + } + return 0; +} + cid_t coroutine_add_with_stack_size(size_t stack_size, const char *name, cr_fn_t fn, void *args) { - static cid_t last_created = 0; cid_t parent = coroutine_running; if (parent) @@ -491,49 +559,53 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, debugf("coroutine_add_with_stack_size(%zu, \"%s\", %p, %p)...", stack_size, name, fn, args); - cid_t child; - { - size_t base = last_created; - for (size_t shift = 0; shift < CONFIG_COROUTINE_NUM; shift++) { - child = ((base + shift) % CONFIG_COROUTINE_NUM) + 1; - if (coroutine_table[child-1].state == CR_NONE) - goto found; - } - return 0; - found: + if (!coroutine_initialized) { + cr_plat_init(); + coroutine_initialized = true; } + + cid_t child = coroutine_allocate_cid(); + if (!child) + return 0; debugf("...child=%zu", child); - last_created = child; + /* 1. state *************************************************/ + coroutine_table[child-1].state = CR_INITIALIZING; + /* 2. name **************************************************/ if (name) strncpy(coroutine_table[child-1].name, name, sizeof(coroutine_table[child-1].name)); else memset(coroutine_table[child-1].name, 0, sizeof(coroutine_table[child-1].name)); - coroutine_table[child-1].stack_size = stack_size; + /* 3. stack *************************************************/ + coroutine_table[child-1].stack_size = stack_size + 2*CR_STACK_GUARD_SIZE; + infof("allocing \"%s\" stack with size %zu+2*%zu=%zu", + name, stack_size, CR_STACK_GUARD_SIZE, coroutine_table[child-1].stack_size); coroutine_table[child-1].stack = - aligned_alloc(CR_PLAT_STACK_ALIGNMENT, stack_size); + aligned_alloc(CR_PLAT_STACK_ALIGNMENT, coroutine_table[child-1].stack_size); + infof("... done, stack is [0x%p,0x%p)", + coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE, + coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE + stack_size); #if CONFIG_COROUTINE_MEASURE_STACK || CONFIG_COROUTINE_PROTECT_STACK - for (size_t i = 0; i < stack_size; i++) + for (size_t i = 0; i < coroutine_table[child-1].stack_size; i++) ((uint8_t *)coroutine_table[child-1].stack)[i] = stack_pattern[i%sizeof(stack_pattern)]; #endif #if CONFIG_COROUTINE_VALGRIND coroutine_table[child-1].stack_id = VALGRIND_STACK_REGISTER( - coroutine_table[child-1].stack + STACK_GUARD_SIZE, - coroutine_table[child-1].stack + stack_size - STACK_GUARD_SIZE); + coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE, + coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE + stack_size); #endif + /* 4. env ***************************************************/ coroutine_running = child; - coroutine_table[child-1].state = CR_INITIALIZING; - if (!cr_plat_setjmp(&coroutine_add_env)) { /* point=a */ + coroutine_cnt++; + if (!cr_setjmp(&coroutine_add_env)) { /* point=a */ void *stack_base = coroutine_table[child-1].stack + + CR_STACK_GUARD_SIZE #if CR_PLAT_STACK_GROWS_DOWNWARD + stack_size - - STACK_GUARD_SIZE -#else - + STACK_GUARD_SIZE #endif ; debugf("...stack =%p", coroutine_table[child-1].stack); @@ -555,20 +627,25 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, return child; } -cid_t coroutine_add(const char *name, cr_fn_t fn, void *args) { - return coroutine_add_with_stack_size( - CONFIG_COROUTINE_DEFAULT_STACK_SIZE, name, fn, args); -} - /* coroutine_main() ***********************************************************/ -[[noreturn]] void coroutine_main(void) { +void coroutine_main(void) { debugf("coroutine_main()"); + if (!coroutine_initialized) { + cr_plat_init(); + coroutine_initialized = true; + } bool saved = cr_save_and_disable_interrupts(); assert(saved); - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); coroutine_running = 0; - for (;;) { +#if CONFIG_COROUTINE_GDB + /* Some pointless call to prevent cr_gdb_readjmp() from + * getting pruned out by `ld --gc-sections`. */ + if (coroutine_table[0].state != CR_NONE) + cr_gdb_readjmp(&coroutine_table[0].env); +#endif + while (coroutine_cnt) { cid_t next; while ( !((next = coroutine_ringbuf_pop())) ) { /* No coroutines are runnable, wait for an interrupt @@ -576,10 +653,10 @@ cid_t coroutine_add(const char *name, cr_fn_t fn, void *args) { cr_plat_wait_for_interrupt(); } - if (!cr_plat_setjmp(&coroutine_main_env)) { /* point=b */ + if (!cr_setjmp(&coroutine_main_env)) { /* point=b */ coroutine_running = next; coroutine_table[coroutine_running-1].state = CR_RUNNING; - cr_plat_longjmp(&coroutine_table[coroutine_running-1].env, 1); /* jump to point=c */ + cr_longjmp(&coroutine_table[coroutine_running-1].env); /* jump to point=c */ } /* This is where we jump to from cr_exit(), and from * nowhere else. */ @@ -589,7 +666,10 @@ cid_t coroutine_add(const char *name, cr_fn_t fn, void *args) { #endif free(coroutine_table[coroutine_running-1].stack); coroutine_table[coroutine_running-1] = (struct coroutine){0}; + coroutine_cnt--; } + coroutine_running = 0; + cr_restore_interrupts(saved); } /* cr_*() *********************************************************************/ @@ -601,8 +681,8 @@ void cr_begin(void) { bool saved = cr_save_and_disable_interrupts(); coroutine_table[coroutine_running-1].state = CR_RUNNABLE; coroutine_ringbuf_push(coroutine_running); - if (!cr_plat_setjmp(&coroutine_table[coroutine_running-1].env)) /* point=c1 */ - cr_plat_longjmp(&coroutine_add_env, 1); /* jump to point=a */ + if (!cr_setjmp(&coroutine_table[coroutine_running-1].env)) /* point=c1 */ + cr_longjmp(&coroutine_add_env); /* jump to point=a */ cr_restore_interrupts(saved); } @@ -619,16 +699,16 @@ static inline void _cr_yield() { return; } - if (!cr_plat_setjmp(&coroutine_table[coroutine_running-1].env)) { /* point=c2 */ + if (!cr_setjmp(&coroutine_table[coroutine_running-1].env)) { /* point=c2 */ coroutine_running = next; coroutine_table[coroutine_running-1].state = CR_RUNNING; - cr_plat_longjmp(&coroutine_table[coroutine_running-1].env, 1); /* jump to point=c */ + cr_longjmp(&coroutine_table[coroutine_running-1].env); /* jump to point=c */ } } void cr_yield(void) { debugf("cid=%zu: cr_yield()", coroutine_running); - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); bool saved = cr_save_and_disable_interrupts(); @@ -640,7 +720,7 @@ void cr_yield(void) { void cr_pause_and_yield(void) { debugf("cid=%zu: cr_pause_and_yield()", coroutine_running); - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); bool saved = cr_save_and_disable_interrupts(); @@ -651,12 +731,12 @@ void cr_pause_and_yield(void) { [[noreturn]] void cr_exit(void) { debugf("cid=%zu: cr_exit()", coroutine_running); - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); (void)cr_save_and_disable_interrupts(); coroutine_table[coroutine_running-1].state = CR_NONE; - cr_plat_longjmp(&coroutine_main_env, 1); /* jump to point=b */ + cr_longjmp(&coroutine_main_env); /* jump to point=b */ } static void _cr_unpause(cid_t cid) { @@ -668,7 +748,7 @@ static void _cr_unpause(cid_t cid) { void cr_unpause(cid_t cid) { debugf("cr_unpause(%zu)", cid); - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); bool saved = cr_save_and_disable_interrupts(); @@ -678,41 +758,76 @@ void cr_unpause(cid_t cid) { void cr_unpause_from_intrhandler(cid_t cid) { debugf("cr_unpause_from_intrhandler(%zu)", cid); - assert(cr_is_in_intrhandler()); + assert(cr_plat_is_in_intrhandler()); _cr_unpause(cid); } cid_t cr_getcid(void) { - assert(!cr_is_in_intrhandler()); + assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); return coroutine_running; } -/* cr_cid_info() **************************************************************/ +#ifndef NDEBUG +void cr_assert_in_coroutine(void) { + assert(!cr_plat_is_in_intrhandler()); + assert_cid_state(coroutine_running, state == CR_RUNNING); +} -#if CONFIG_COROUTINE_MEASURE_STACK +void cr_assert_in_intrhandler(void) { + assert(cr_plat_is_in_intrhandler()); +} +#endif + +/* answering questions about coroutines ***************************************/ void cr_cid_info(cid_t cid, struct cr_cid_info *ret) { - assert_cid(cid); + assert(cid > 0); + assert(cid <= CONFIG_COROUTINE_NUM); assert(ret); + memset(ret, 0, sizeof(*ret)); + if (coroutine_table[cid-1].state == CR_NONE) + return; + assert_cid(cid); + + /* 1. state *************************************************/ + ret->state = coroutine_table[cid-1].state; + + /* 2. name **************************************************/ + memcpy(ret->name, coroutine_table[cid-1].name, CONFIG_COROUTINE_NAME_LEN); + + /* 3. stack *************************************************/ +#if CONFIG_COROUTINE_MEASURE_STACK + uint8_t *stack = (uint8_t *)coroutine_table[cid-1].stack; + uint8_t *stack_lo = stack + CR_STACK_GUARD_SIZE; + uint8_t *stack_hi = stack + coroutine_table[cid-1].stack_size - CR_STACK_GUARD_SIZE; + /* stack_cap */ - ret->stack_cap = coroutine_table[cid-1].stack_size - 2*STACK_GUARD_SIZE; + ret->stack_cap = stack_hi - stack_lo; /* stack_max */ ret->stack_max = ret->stack_cap; - uint8_t *stack = (uint8_t *)coroutine_table[cid-1].stack; for (;;) { size_t i = #if CR_PLAT_STACK_GROWS_DOWNWARD - STACK_GUARD_SIZE + ret->stack_cap - ret->stack_max + CR_STACK_GUARD_SIZE + ret->stack_cap - ret->stack_max #else - ret->stack_max - 1 - STACK_GUARD_SIZE + ret->stack_max - 1 - CR_STACK_GUARD_SIZE #endif ; - if (ret->stack_max == 0 || - stack[i] != stack_pattern[i%sizeof(stack_pattern)]) + if (ret->stack_max == 0) + break; + assert(stack_lo <= &stack[i] && &stack[i] < stack_hi); +#if CONFIG_COROUTINE_VALGRIND + VALGRIND_DISABLE_ERROR_REPORTING; +#endif + uint8_t v = stack[i]; +#if CONFIG_COROUTINE_VALGRIND + VALGRIND_ENABLE_ERROR_REPORTING; +#endif + if (v != stack_pattern[i%sizeof(stack_pattern)]) break; ret->stack_max--; } @@ -722,16 +837,21 @@ void cr_cid_info(cid_t cid, struct cr_cid_info *ret) { if (cid == coroutine_running) sp = cr_plat_get_sp(); else if (coroutine_table[cid-1].state == CR_RUNNING) - sp = coroutine_add_env.sp; + sp = cr_plat_setjmp_get_sp(&coroutine_add_env); else - sp = coroutine_table[cid-1].env.sp; + sp = cr_plat_setjmp_get_sp(&coroutine_table[cid-1].env); assert(sp); - uintptr_t sb = (uintptr_t)coroutine_table[cid-1].stack; #if CR_PLAT_STACK_GROWS_DOWNWARD - ret->stack_cur = (sb - STACK_GUARD_SIZE) - sp; + uintptr_t sb = (uintptr_t)stack_hi; + ret->stack_cur = sb - sp; #else - ret->stack_cur = sp - (sb + STACK_GUARD_SIZE); + uintptr_t sb = (uintptr_t)stack_lo; + ret->stack_cur = sp - sb; #endif +#endif /* CONFIG_COROUTINE_MEASURE_STACK */ } -#endif /* CONFIG_COROUTINE_MEASURE_STACK */ +const char *coroutine_state_str(enum coroutine_state state) { + assert(state < LM_ARRAY_LEN(coroutine_state_strs)); + return coroutine_state_strs[state]; +} diff --git a/libcr/include/libcr/coroutine.h b/libcr/include/libcr/coroutine.h index 86b8452..2505782 100644 --- a/libcr/include/libcr/coroutine.h +++ b/libcr/include/libcr/coroutine.h @@ -1,6 +1,6 @@ /* libcr/coroutine.h - Simple embeddable coroutine implementation * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -29,6 +29,14 @@ #include <stddef.h> /* for size_t */ #include <stdbool.h> /* for bool */ +/* Configuration **************************************************************/ + +#include "config.h" + +#ifndef CONFIG_COROUTINE_MEASURE_STACK + #error config.h must define CONFIG_COROUTINE_MEASURE_STACK (bool) +#endif + /* typedefs *******************************************************************/ /** @@ -89,13 +97,21 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, const char *name, cr_fn_t /** * Like coroutine_add_with_stack_size(), but uses a default stack size so * you don't need to think about it. + * + * Either define CONFIG_COROUTINE_STACK_SIZE_DEFAULT to use for all + * coroutines, or CONFIG_COROUTINE_STACK_SIZE_{fn} for each COROUTINE + * function. */ -cid_t coroutine_add(const char *name, cr_fn_t fn, void *args); +#ifdef CONFIG_COROUTINE_STACK_SIZE_DEFAULT +#define coroutine_add(name, fn, args) coroutine_add_with_stack_size(CONFIG_COROUTINE_STACK_SIZE_DEFAULT, name, fn, args) +#else +#define coroutine_add(name, fn, args) coroutine_add_with_stack_size(CONFIG_COROUTINE_STACK_SIZE_##fn, name, fn, args) +#endif /** - * The main scheduler loop. + * The main scheduler loop. Returns if all coroutines exit. */ -[[noreturn]] void coroutine_main(void); +void coroutine_main(void); /* inside of coroutines *******************************************************/ @@ -164,15 +180,48 @@ bool cr_is_in_intrhandler(void); */ void cr_unpause_from_intrhandler(cid_t); +/** + * cr_assert_in_coroutine() asserts that it is being called from a + * running coroutine. + */ +#ifdef NDEBUG +#define cr_assert_in_coroutine() ((void)0) +#else +void cr_assert_in_coroutine(void); +#endif + + +/** + * cr_assert_in_intrhandler() asserts that it is being called from an + * interrupt handler. + */ +#ifdef NDEBUG +#define cr_assert_in_intrhandler() ((void)0) +#else +void cr_assert_in_intrhandler(void); +#endif + /* answering questions about coroutines ***************************************/ -/* While the following are defined here unconditionally, the - * implementations are #if'd on CONFIG_COROUTINE_MEASURE_STACK. */ +enum coroutine_state { + CR_NONE = 0, /* this slot in the table is empty */ + CR_INITIALIZING, /* running, before cr_begin() */ + CR_RUNNING, /* running, after cr_begin() */ + CR_RUNNABLE, /* not running, but runnable */ + CR_PAUSED, /* not running, and not runnable */ +}; + +const char *coroutine_state_str(enum coroutine_state); struct cr_cid_info { + enum coroutine_state state; + [[gnu::nonstring]] char name[CONFIG_COROUTINE_NAME_LEN]; + +#if CONFIG_COROUTINE_MEASURE_STACK size_t stack_cap; size_t stack_max; size_t stack_cur; +#endif }; void cr_cid_info(cid_t cid, struct cr_cid_info *ret); diff --git a/libcr/tests/test_matrix.c b/libcr/tests/test_matrix.c new file mode 100644 index 0000000..1f23455 --- /dev/null +++ b/libcr/tests/test_matrix.c @@ -0,0 +1,24 @@ +/* libcr/tests/test_matrix.c - Tests for libcr + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> + +int a = 1; + +COROUTINE cr_init(void *) { + cr_begin(); + a = 2; + cr_end(); +} + +int main() { + coroutine_add("init", cr_init, NULL); + coroutine_main(); + if (a != 2) + return 1; + coroutine_main(); + return 0; +} diff --git a/libcr/tests/test_matrix/config.h b/libcr/tests/test_matrix/config.h new file mode 100644 index 0000000..978b9ac --- /dev/null +++ b/libcr/tests/test_matrix/config.h @@ -0,0 +1,14 @@ +/* config.h - Compile-time configuration for libcr test_matrix + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (4*1024) +#define CONFIG_COROUTINE_NAME_LEN 16 +#define CONFIG_COROUTINE_NUM 2 + +#endif /* _CONFIG_H_ */ diff --git a/libcr_ipc/CMakeLists.txt b/libcr_ipc/CMakeLists.txt index d44203e..bd72f54 100644 --- a/libcr_ipc/CMakeLists.txt +++ b/libcr_ipc/CMakeLists.txt @@ -1,10 +1,31 @@ -# libcr_ipc/CMakeLists.txt - TODO +# libcr_ipc/CMakeLists.txt - IPC primitives for libcr # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libcr_ipc INTERFACE) -target_include_directories(libcr_ipc SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_include_directories(libcr_ipc PUBLIC INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_sources(libcr_ipc INTERFACE + _linkedlist.c + chan.c + mutex.c + rpc.c + rwmutex.c + sema.c +) target_link_libraries(libcr_ipc INTERFACE libcr + libmisc +) + +set(ipc_tests + chan + mutex + rpc + rwmutex + select + sema ) +foreach(test IN LISTS ipc_tests) + add_lib_test(libcr_ipc "test_${test}") +endforeach() diff --git a/libcr_ipc/_linkedlist.c b/libcr_ipc/_linkedlist.c new file mode 100644 index 0000000..b27dba8 --- /dev/null +++ b/libcr_ipc/_linkedlist.c @@ -0,0 +1,62 @@ +/* libcr_ipc/_linkedlist.c - Common low-level linked lists for use in libcr_ipc + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stddef.h> /* for NULL */ + +#include "_linkedlist.h" + +/* singly linked list *********************************************************/ + +void cr_ipc_sll_push_to_rear(_cr_ipc_sll_root *root, cr_ipc_sll_node *node) { + assert(root); + node->rear = NULL; + if (root->rear) + root->rear->rear = node; + else + root->front = node; + root->rear = node; +} + +void cr_ipc_sll_pop_from_front(_cr_ipc_sll_root *root) { + assert(root); + assert(root->front); + root->front = root->front->rear; + if (!root->front) + root->rear = NULL; +} + +/* doubly linked list *********************************************************/ + +void cr_ipc_dll_push_to_rear(_cr_ipc_dll_root *root, cr_ipc_dll_node *node) { + assert(root); + assert(node); + node->front = root->rear; + node->rear = NULL; + if (root->rear) + root->rear->rear = node; + else + root->front = node; + root->rear = node; +} + +void cr_ipc_dll_remove(_cr_ipc_dll_root *root, cr_ipc_dll_node *node) { + assert(root); + assert(node); + if (node->front) + node->front->rear = node->rear; + else + root->front = node->rear; + if (node->rear) + node->rear->front = node->front; + else + root->rear = node->front; +} + +void cr_ipc_dll_pop_from_front(_cr_ipc_dll_root *root) { + assert(root); + assert(root->front); + cr_ipc_dll_remove(root, root->front); +} diff --git a/libcr_ipc/_linkedlist.h b/libcr_ipc/_linkedlist.h new file mode 100644 index 0000000..ab6d89e --- /dev/null +++ b/libcr_ipc/_linkedlist.h @@ -0,0 +1,51 @@ +/* libcr_ipc/_linkedlist.h - Common low-level linked lists for use in libcr_ipc + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBCR_IPC__LINKEDLIST_H_ +#define _LIBCR_IPC__LINKEDLIST_H_ + +#include <libmisc/assert.h> + +#include <libcr_ipc/_linkedlist_pub.h> + +/* singly linked list *********************************************************/ + +typedef struct _cr_ipc_sll_node { + struct _cr_ipc_sll_node *rear; +} cr_ipc_sll_node; + +#define cr_ipc_sll_node_cast(node_typ, node_ptr) \ + ({ \ + static_assert(_Generic(node_ptr, cr_ipc_sll_node *: 1, default: 0), \ + "typeof("#node_ptr") != cr_ipc_sll_node *"); \ + assert(node_ptr); \ + static_assert(offsetof(node_typ, cr_ipc_sll_node) == 0); \ + ((node_typ*)(node_ptr)); \ + }) + +void cr_ipc_sll_push_to_rear(_cr_ipc_sll_root *root, cr_ipc_sll_node *node); +void cr_ipc_sll_pop_from_front(_cr_ipc_sll_root *root); + +/* doubly linked list *********************************************************/ + +typedef struct _cr_ipc_dll_node { + struct _cr_ipc_dll_node *front, *rear; +} cr_ipc_dll_node; + +#define cr_ipc_dll_node_cast(node_typ, node_ptr) \ + ({ \ + static_assert(_Generic(node_ptr, cr_ipc_dll_node *: 1, default: 0), \ + "typeof("#node_ptr") != cr_ipc_dll_node *"); \ + assert(node_ptr); \ + static_assert(offsetof(node_typ, cr_ipc_dll_node) == 0); \ + ((node_typ*)(node_ptr)); \ + }) + +void cr_ipc_dll_push_to_rear(_cr_ipc_dll_root *root, cr_ipc_dll_node *node); +void cr_ipc_dll_remove(_cr_ipc_dll_root *root, cr_ipc_dll_node *node); +void cr_ipc_dll_pop_from_front(_cr_ipc_dll_root *root); + +#endif /* _LIBCR_IPC__LINKEDLIST_H_ */ diff --git a/libcr_ipc/chan.c b/libcr_ipc/chan.c new file mode 100644 index 0000000..12d2ec2 --- /dev/null +++ b/libcr_ipc/chan.c @@ -0,0 +1,169 @@ +/* libcr_ipc/chan.c - Simple channels for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <alloca.h> /* for alloca() */ +#include <string.h> /* for memcpy() */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ +#include <libmisc/assert.h> +#include <libmisc/rand.h> + +#include <libcr_ipc/chan.h> + +#include "_linkedlist.h" + +/* base channels **************************************************************/ + +struct cr_chan_waiter { + cr_ipc_dll_node; + cid_t cid; + void *val_ptr; + void (*dequeue)(void *, size_t); + void *dequeue_arg1; + size_t dequeue_arg2; +}; + +void cr_chan_dequeue(void *_ch, size_t) { + struct _cr_chan *ch = _ch; + cr_ipc_dll_pop_from_front(&ch->waiters); +} + +void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size) { + assert(ch); + assert(val_ptr); + + if (ch->waiters.front && ch->waiter_typ != self_typ) { /* non-blocking fast-path */ + /* Copy. */ + struct cr_chan_waiter *front = cr_ipc_dll_node_cast(struct cr_chan_waiter, ch->waiters.front); + if (self_typ == _CR_CHAN_SENDER) + memcpy(front->val_ptr, val_ptr, val_size); + else + memcpy(val_ptr, front->val_ptr, val_size); + cr_unpause(front->cid); + front->dequeue(front->dequeue_arg1, + front->dequeue_arg2); + cr_yield(); + } else { /* blocking slow-path */ + struct cr_chan_waiter self = { + .cid = cr_getcid(), + .val_ptr = val_ptr, + .dequeue = cr_chan_dequeue, + .dequeue_arg1 = ch, + }; + cr_ipc_dll_push_to_rear(&ch->waiters, &self); + ch->waiter_typ = self_typ; + cr_pause_and_yield(); + } +} + +/* select *********************************************************************/ + +enum cr_select_class { + CR_SELECT_CLASS_DEFAULT, + CR_SELECT_CLASS_BLOCKING, + CR_SELECT_CLASS_NONBLOCK, +}; + +struct cr_select_waiters { + size_t cnt; + struct cr_select_arg *args; + struct cr_chan_waiter *nodes; +}; + +static inline enum cr_select_class cr_select_getclass(struct cr_select_arg arg) { + switch (arg.op) { + case _CR_SELECT_OP_RECV: + if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_SENDER) + return CR_SELECT_CLASS_NONBLOCK; + else + return CR_SELECT_CLASS_BLOCKING; + case _CR_SELECT_OP_SEND: + if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_RECVER) + return CR_SELECT_CLASS_NONBLOCK; + else + return CR_SELECT_CLASS_BLOCKING; + case _CR_SELECT_OP_DEFAULT: + return CR_SELECT_CLASS_DEFAULT; + default: + assert_notreached("invalid arg.op"); + } +} + +void cr_select_dequeue(void *_waiters, size_t idx) { + struct cr_select_waiters *waiters = _waiters; + for (size_t i = 0; i < waiters->cnt; i++) + cr_ipc_dll_remove(&(waiters->args[i].ch->waiters), + &(waiters->nodes[i])); + waiters->cnt = idx; +} + +size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { + size_t cnt_blocking = 0; + size_t cnt_nonblock = 0; + size_t cnt_default = 0; + + assert(arg_cnt); + assert(arg_vec); + cr_assert_in_coroutine(); + + for (size_t i = 0; i < arg_cnt; i++) { + switch (cr_select_getclass(arg_vec[i])) { + case CR_SELECT_CLASS_BLOCKING: + cnt_blocking++; + break; + case CR_SELECT_CLASS_NONBLOCK: + cnt_nonblock++; + break; + case CR_SELECT_CLASS_DEFAULT: + cnt_default++; + break; + } + } + + if (cnt_nonblock) { + size_t choice = rand_uint63n(cnt_nonblock); + for (size_t i = 0, seen = 0; i < arg_cnt; i++) { + if (cr_select_getclass(arg_vec[i]) == CR_SELECT_CLASS_NONBLOCK) { + if (seen == choice) { + _cr_chan_xfer(arg_vec[i].op == _CR_SELECT_OP_RECV + ? _CR_CHAN_RECVER + : _CR_CHAN_SENDER, + arg_vec[i].ch, + arg_vec[i].val_ptr, + arg_vec[i].val_siz); + return i; + } + seen++; + } + } + assert_notreached("should have returned from inside for() loop"); + } + + if (cnt_default) { + for (size_t i = 0; i < arg_cnt; i++) + if (cr_select_getclass(arg_vec[i]) == CR_SELECT_CLASS_DEFAULT) + return i; + assert_notreached("should have returned from inside for() loop"); + } + + struct cr_select_waiters waiters = { + .cnt = arg_cnt, + .args = arg_vec, + .nodes = alloca(sizeof(struct cr_chan_waiter) * arg_cnt), + }; + for (size_t i = 0; i < arg_cnt; i++) { + waiters.nodes[i] = (struct cr_chan_waiter){ + .cid = cr_getcid(), + .val_ptr = arg_vec[i].val_ptr, + .dequeue = cr_select_dequeue, + .dequeue_arg1 = &waiters, + .dequeue_arg2 = i, + }; + cr_ipc_dll_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]); + } + cr_pause_and_yield(); + return waiters.cnt; +} diff --git a/libcr_ipc/include/libcr_ipc/_linkedlist.h b/libcr_ipc/include/libcr_ipc/_linkedlist.h deleted file mode 100644 index e5aa52a..0000000 --- a/libcr_ipc/include/libcr_ipc/_linkedlist.h +++ /dev/null @@ -1,108 +0,0 @@ -/* libcr_ipc/_linkedlist.h - Common low-level linked lists for use in libcr_ipc - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _LIBCR_IPC__LINKEDLIST_H_ -#define _LIBCR_IPC__LINKEDLIST_H_ - -#include <libmisc/assert.h> - -/* singly linked list *********************************************************/ - -/* The `root` type should have a ->front pointer and a ->rear pointer - * that point to the front and rear nodes respectively; or NULL if the - * list is empty. - * - * The `node` type should have a ->rear pointer that points to the - * node to the rear of it. - * - * ex: - * - * typedef struct { - * MY_NODE *rear; - * ...node_data... - * } MY_NODE; - * - * typedef struct { - * MY_NODE *front, *rear; - * ...root_data... - * } MY_ROOT; - */ - -#define _cr_ipc_sll_push_to_rear(root, node) do { \ - assert(root); \ - (node)->rear = NULL; \ - if ((root)->rear) \ - (root)->rear->rear = node; \ - else { \ - (root)->front = node; \ - (root)->rear = node; \ - } \ - } while(0) - -#define _cr_ipc_sll_pop_from_front(root) do { \ - assert(root); \ - assert((root)->front); \ - (root)->front = (root)->front->rear; \ - if (!((root)->front)) \ - (root)->rear = NULL; \ - } while(0) - -/* doubly linked list *********************************************************/ - -/* The `root` type should have a ->front pointer and a ->rear pointer - * that point to the front and rear nodes respectively; or NULL if the - * list is empty. - * - * The `node` type should also have a ->front pointer that points to - * the node in front of it, or NULL if it is the front; and a ->rear - * pointer that points to the node to the rear of it, or NULL if it is - * the rear. - * - * ex: - * - * typedef struct { - * MY_NODE *front, *rear; - * ...node_data... - * } MY_NODE; - * - * typedef struct { - * MY_NODE *front, *rear; - * ...root_data... - * } MY_ROOT; - */ - -#define _cr_ipc_dll_push_to_rear(root, node) do { \ - assert(root); \ - assert(node); \ - (node)->front = (root)->rear; \ - (node)->rear = NULL; \ - if ((root)->rear) \ - (root)->rear->rear = node; \ - else \ - (root)->front = node; \ - (root)->rear = node; \ - } while(0) - -#define _cr_ipc_dll_remove(root, node) do { \ - assert(root); \ - assert(node); \ - if ((node)->front) \ - (node)->front->rear = (node)->rear; \ - else \ - (root)->front = (node)->rear; \ - if ((node)->rear) \ - (node)->rear->front = (node)->front; \ - else \ - (root)->rear = (node)->front; \ - } while (0) - -#define _cr_ipc_dll_pop_from_front(root) do { \ - assert(root); \ - assert((root)->front); \ - _cr_ipc_dll_remove(root, (root)->front); \ - } while(0) - -#endif /* _LIBCR_IPC__LINKEDLIST_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/_linkedlist_pub.h b/libcr_ipc/include/libcr_ipc/_linkedlist_pub.h new file mode 100644 index 0000000..6719ba4 --- /dev/null +++ b/libcr_ipc/include/libcr_ipc/_linkedlist_pub.h @@ -0,0 +1,26 @@ +/* libcr_ipc/_linkedlist_pub.h - Common low-level linked lists for use in libcr_ipc + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBCR_IPC__LINKEDLIST_PUB_H_ +#define _LIBCR_IPC__LINKEDLIST_PUB_H_ + +/* singly linked list *********************************************************/ + +struct _cr_ipc_sll_node; + +typedef struct { + struct _cr_ipc_sll_node *front, *rear; +} _cr_ipc_sll_root; + +/* doubly linked list *********************************************************/ + +struct _cr_ipc_dll_node; + +typedef struct { + struct _cr_ipc_dll_node *front, *rear; +} _cr_ipc_dll_root; + +#endif /* _LIBCR_IPC__LINKEDLIST_PUB_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/chan.h b/libcr_ipc/include/libcr_ipc/chan.h index 53e68dd..ad311a0 100644 --- a/libcr_ipc/include/libcr_ipc/chan.h +++ b/libcr_ipc/include/libcr_ipc/chan.h @@ -1,6 +1,6 @@ /* libcr_ipc/chan.h - Simple channels for libcr * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -9,11 +9,12 @@ #include <stdbool.h> /* for bool */ #include <stddef.h> /* for size_t */ -#include <string.h> /* for memcpy */ -#include <libcr/coroutine.h> /* for cid_t, cr_unpause(), cr_pause_and_yield() */ +#include <libmisc/macro.h> /* LM_CAT2_() */ -#include <libcr_ipc/_linkedlist.h> +#include <libcr_ipc/_linkedlist_pub.h> + +/* base channels **************************************************************/ /** * CR_CHAN_DECLARE(NAME, VAL_T) declares the following type and @@ -21,7 +22,7 @@ * * type: * - * /** + * / ** * * A NAME##_t is a fair unbuffered channel that transports * * values of type `VAL_T`. * * @@ -35,7 +36,7 @@ * * methods: * - * /** + * / ** * * NAME##_send(ch, val) sends `val` over `ch`. * * * * @runs_in coroutine @@ -44,7 +45,7 @@ * * / * void NAME##_send(NAME##_t *ch, VAL_T val); * - * /** + * / ** * * NAME##_recv(ch) reads and returns a value from ch. * * * * @runs_in coroutine @@ -53,7 +54,7 @@ * * / * VAL_T NAME##_recv(NAME##_t *ch); * - * /** + * / ** * * NAME##_can_send(ch) returns whether NAME##_send(ch, val) * * would run without pausing. * * @@ -63,7 +64,7 @@ * * / * bool NAME##_can_send(NAME##_t *ch); * - * /** + * / ** * * NAME##_can_recv(ch) returns whether NAME##_recv(ch) would * * return without pausing. * * @@ -78,78 +79,97 @@ struct _cr_chan core; \ VAL_T vals[0]; \ } NAME##_t; \ - \ + \ static inline void NAME##_send(NAME##_t *ch, VAL_T val) { \ + cr_assert_in_coroutine(); \ _cr_chan_xfer(_CR_CHAN_SENDER, &ch->core, &val, sizeof(val)); \ } \ - \ + \ static inline VAL_T NAME##_recv(NAME##_t *ch) { \ + cr_assert_in_coroutine(); \ VAL_T val; \ _cr_chan_xfer(_CR_CHAN_RECVER, &ch->core, &val, sizeof(val)); \ return val; \ } \ - \ + \ static inline bool NAME##_can_send(NAME##_t *ch) { \ + cr_assert_in_coroutine(); \ return ch->core.waiters.front && \ ch->core.waiter_typ == _CR_CHAN_RECVER; \ } \ - \ + \ static inline bool NAME##_can_recv(NAME##_t *ch) { \ + cr_assert_in_coroutine(); \ return ch->core.waiters.front && \ ch->core.waiter_typ == _CR_CHAN_SENDER; \ - } + } \ + \ + extern int LM_CAT2_(_CR_CHAN_FORCE_SEMICOLON_, __COUNTER__) enum _cr_chan_waiter_typ { _CR_CHAN_SENDER, _CR_CHAN_RECVER, }; -struct _cr_chan_waiter { - struct _cr_chan_waiter *front, *rear; - cid_t cid; - void *val_ptr; - void (*dequeue)(void *, size_t); - void *dequeue_arg1; - size_t dequeue_arg2; -}; - struct _cr_chan { enum _cr_chan_waiter_typ waiter_typ; - struct { - struct _cr_chan_waiter *front, *rear; - } waiters; + _cr_ipc_dll_root waiters; +}; + +void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size); + +/* cr_select arguments ********************************************************/ + +/** + * Do not populate cr_select_arg yourself; use the + * CR_SELECT_{RECV,SEND,DEFAULT} macros. + */ +struct cr_select_arg { + enum { + _CR_SELECT_OP_RECV, + _CR_SELECT_OP_SEND, + _CR_SELECT_OP_DEFAULT, + } op; + struct _cr_chan *ch; + void *val_ptr; + size_t val_siz; }; -static void _cr_chan_dequeue(void *_ch, size_t) { - struct _cr_chan *ch = _ch; - _cr_ipc_dll_pop_from_front(&ch->waiters); -} - -static inline void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size) { - assert(ch); - assert(val_ptr); - - if (ch->waiters.front && ch->waiter_typ != self_typ) { /* non-blocking fast-path */ - /* Copy. */ - if (self_typ == _CR_CHAN_SENDER) - memcpy(ch->waiters.front->val_ptr, val_ptr, val_size); - else - memcpy(val_ptr, ch->waiters.front->val_ptr, val_size); - cr_unpause(ch->waiters.front->cid); - ch->waiters.front->dequeue(ch->waiters.front->dequeue_arg1, - ch->waiters.front->dequeue_arg2); - cr_yield(); - } else { /* blocking slow-path */ - struct _cr_chan_waiter self = { - .cid = cr_getcid(), - .val_ptr = val_ptr, - .dequeue = _cr_chan_dequeue, - .dequeue_arg1 = ch, - }; - _cr_ipc_dll_push_to_rear(&ch->waiters, &self); - ch->waiter_typ = self_typ; - cr_pause_and_yield(); - } -} +#define CR_SELECT_RECV(CH, VALP) \ + /* The _valp temporary variable is to get the compiler to check that \ + * the types are compatible. */ \ + ((struct cr_select_arg){ \ + .op = _CR_SELECT_OP_RECV, \ + .ch = &((CH)->core), \ + .val_ptr = ({ typeof((CH)->vals[0]) *_valp = VALP; _valp; }), \ + .val_siz = sizeof((CH)->vals[0]), \ + }) +/* BUG: It's bogus that CR_SELECT_SEND takes VALP instead of VAL, but + * since we need an address, taking VAL would introduce uncomfortable + * questions about where VAL sits on the stack. */ +#define CR_SELECT_SEND(CH, VALP) \ + /* The _valp temporary variable is to get the compiler to check that \ + * the types are compatible. */ \ + ((struct cr_select_arg){ \ + .op = _CR_SELECT_OP_SEND, \ + .ch = &((CH)->core), \ + .val_ptr = ({ typeof((CH)->vals[0]) *_valp = VALP; _valp; }), \ + .val_siz = sizeof((CH)->vals[0]), \ + }) +#define CR_SELECT_DEFAULT \ + ((struct cr_select_arg){ \ + .op = _CR_SELECT_OP_DEFAULT, \ + }) + +/* cr_select_v(arg_cnt, arg_vec) **********************************************/ + +size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]); + +/* cr_select_l(arg1, arg2, arg3, ...) ******************************************/ + +#define cr_select_l(...) ({ \ + struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \ + cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0])); \ +}) #endif /* _LIBCR_IPC_CHAN_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/mutex.h b/libcr_ipc/include/libcr_ipc/mutex.h index 579f7d5..ec40f5c 100644 --- a/libcr_ipc/include/libcr_ipc/mutex.h +++ b/libcr_ipc/include/libcr_ipc/mutex.h @@ -1,6 +1,6 @@ /* libcr_ipc/mutex.h - Simple mutexes for libcr * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -9,14 +9,9 @@ #include <stdbool.h> /* for bool */ -#include <libcr/coroutine.h> /* for cid_t, cr_unpause(), cr_pause_and_yield() */ +#include <libmisc/private.h> -#include <libcr_ipc/_linkedlist.h> - -struct _cr_mutex_waiter { - struct _cr_mutex_waiter *rear; - cid_t cid; -}; +#include <libcr_ipc/_linkedlist_pub.h> /** * A cr_mutex_t is a fair mutex. @@ -27,10 +22,10 @@ struct _cr_mutex_waiter { * first place, then it has no business unlocking one. */ typedef struct { + BEGIN_PRIVATE(LIBCR_IPC_MUTEX_H); bool locked; - struct { - struct _cr_mutex_waiter *front, *rear; - } waiters; + _cr_ipc_sll_root waiters; + END_PRIVATE(LIBCR_IPC_MUTEX_H); } cr_mutex_t; /** @@ -40,20 +35,7 @@ typedef struct { * @cr_pauses maybe * @cr_yields maybe */ -static inline void cr_mutex_lock(cr_mutex_t *mu) { - assert(mu); - - if (!mu->locked) /* non-blocking fast-path */ - mu->locked = true; - else { /* blocking slow-path */ - struct _cr_mutex_waiter self = { - .cid = cr_getcid(), - }; - _cr_ipc_sll_push_to_rear(&mu->waiters, &self); - cr_pause_and_yield(); - } - assert(mu->locked); -} +void cr_mutex_lock(cr_mutex_t *mu); /** * Unlock the mutex. Unblocks a coroutine that is blocked on @@ -63,15 +45,6 @@ static inline void cr_mutex_lock(cr_mutex_t *mu) { * @cr_pauses never * @cr_yields never */ -static inline void cr_mutex_unlock(cr_mutex_t *mu) { - assert(mu); - - assert(mu->locked); - if (mu->waiters.front) { - cr_unpause(mu->waiters.front->cid); - _cr_ipc_sll_pop_from_front(&mu->waiters); - } else - mu->locked = false; -} +void cr_mutex_unlock(cr_mutex_t *mu); #endif /* _LIBCR_IPC_MUTEX_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/rpc.h b/libcr_ipc/include/libcr_ipc/rpc.h index 382105a..07ace95 100644 --- a/libcr_ipc/include/libcr_ipc/rpc.h +++ b/libcr_ipc/include/libcr_ipc/rpc.h @@ -1,6 +1,6 @@ /* libcr_ipc/rpc.h - Simple request/response system for libcr * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -9,9 +9,9 @@ #include <stdbool.h> /* for bool */ -#include <libcr/coroutine.h> /* for cid_t, cr_unpause(), cr_pause_and_yield() */ +#include <libmisc/macro.h> /* for LM_CAT2_() */ -#include <libcr_ipc/_linkedlist.h> +#include <libcr_ipc/_linkedlist_pub.h> /** * CR_RPC_DECLARE(NAME, REQ_T, RESP_T) declares the following types @@ -19,7 +19,7 @@ * * type: * - * /** + * / ** * * A NAME##_t is a fair rpc-channel on which the requester submits a * * value of type `REQ_T` and the responder responds with a value of * * type `RESP_T`. @@ -34,9 +34,15 @@ * * _recv_req() and _send_resp(). * typedef ... NAME##_t; * + * / ** + * * A NAME##_req_t is handle that wraps a REQ_T and is used to return + * * the response RESP_T to the correct requester. `REQ_T req` is the + * * only public member. + * typedef struct { REQ_T req; ... } NAME##_req_t; + * * methods: * - * /** + * / ** * * NAME##_send_req(ch, req) submits the `req` request over `ch` and * * returns the response. * * @@ -46,7 +52,7 @@ * * / * RESP_T NAME##_send_req(NAME##_t *ch, REQ_T req); * - * /** + * / ** * * NAME##_recv_req(ch) reads a request from ch, and returns a * * NAME##_req_t handle wrapping that request. * * @@ -56,7 +62,7 @@ * * / * NAME##_req_t NAME##_recv_req(NAME##_t *ch); * - * /** + * / ** * * NAME##_can_recv_req(ch) returns whether NAME##_recv_req(ch) * * would return without pausing. * * @@ -68,7 +74,7 @@ * * type: * - * /** + * / ** * * A NAME##_req_t is a handle that wraps a REQ_T, and is a channel * * that a response may be written to. * * / @@ -76,7 +82,7 @@ * * methods: * - * /** + * / ** * * cr_rpc_send_resp(req, resp) sends the given response to the given * * request. * * @@ -89,72 +95,61 @@ #define CR_RPC_DECLARE(NAME, REQ_T, RESP_T) \ typedef struct { \ REQ_T req; \ - \ - RESP_T *_resp; \ + \ + RESP_T *_resp; /* where to write resp to */ \ cid_t _requester; \ } NAME##_req_t; \ - \ - struct _##NAME##_waiting_req { \ - struct _##NAME##_waiting_req *rear; \ - REQ_T *req; \ - RESP_T *resp; \ - cid_t cid; \ - }; \ - \ - struct _##NAME##_waiting_resp { \ - struct _##NAME##_waiting_resp *rear; \ - cid_t cid; \ - }; \ - \ + \ typedef struct { \ - struct { \ - struct _##NAME##_waiting_req *front, *rear; \ - } waiting_reqs; \ - struct { \ - struct _##NAME##_waiting_resp *front, *rear; \ - } waiting_resps; \ + struct _cr_rpc core; \ + NAME##_req_t handle[0]; \ } NAME##_t; \ - \ + \ static inline RESP_T NAME##_send_req(NAME##_t *ch, REQ_T req) { \ + cr_assert_in_coroutine(); \ RESP_T resp; \ - struct _##NAME##_waiting_req self = { \ - .req = &req, \ - .resp = &resp, \ - .cid = cr_getcid(), \ - }; \ - _cr_ipc_sll_push_to_rear(&ch->waiting_reqs, &self); \ - if (ch->waiting_resps.front) \ - cr_unpause(ch->waiting_resps.front->cid); \ - cr_pause_and_yield(); \ + _cr_rpc_send_req(&ch->core, \ + &req, sizeof(req), \ + &resp); \ return resp; \ } \ - \ + \ static inline NAME##_req_t NAME##_recv_req(NAME##_t *ch) { \ - if (!ch->waiting_reqs.front) { \ - struct _##NAME##_waiting_resp self = { \ - .cid = cr_getcid(), \ - }; \ - _cr_ipc_sll_push_to_rear(&ch->waiting_resps, &self); \ - cr_pause_and_yield(); \ - } \ - assert(ch->waiting_reqs.front); \ - NAME##_req_t ret = { \ - .req = *(ch->waiting_reqs.front->req), \ - ._resp = ch->waiting_reqs.front->resp, \ - ._requester = ch->waiting_reqs.front->cid, \ - }; \ - _cr_ipc_sll_pop_from_front(&ch->waiting_reqs); \ + cr_assert_in_coroutine(); \ + NAME##_req_t ret; \ + _cr_rpc_recv_req(&ch->core, \ + &ret.req, sizeof(ret.req), \ + (void **)&ret._resp, \ + &ret._requester); \ return ret; \ } \ - \ + \ static inline bool NAME##_can_recv_req(NAME##_t *ch) { \ - return ch->waiting_reqs.front != NULL; \ + cr_assert_in_coroutine(); \ + return ch->core.waiters.front && \ + ch->core.waiter_typ == _CR_RPC_REQUESTER; \ } \ - \ + \ static inline void NAME##_send_resp(NAME##_req_t req, RESP_T resp) { \ + cr_assert_in_coroutine(); \ *(req._resp) = resp; \ cr_unpause(req._requester); \ cr_yield(); \ - } + } \ + \ + extern int LM_CAT2_(_CR_RPC_FORCE_SEMICOLON_, __COUNTER__) + +enum _cr_rpc_waiter_typ { + _CR_RPC_REQUESTER, + _CR_RPC_RESPONDER, +}; + +struct _cr_rpc { + enum _cr_rpc_waiter_typ waiter_typ; + _cr_ipc_sll_root waiters; +}; + +void _cr_rpc_send_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void *resp_ptr); +void _cr_rpc_recv_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void **ret_resp_ptr, cid_t *ret_requester); #endif /* _LIBCR_IPC_RPC_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/rwmutex.h b/libcr_ipc/include/libcr_ipc/rwmutex.h new file mode 100644 index 0000000..924acce --- /dev/null +++ b/libcr_ipc/include/libcr_ipc/rwmutex.h @@ -0,0 +1,77 @@ +/* libcr_ipc/rwmutex.h - Simple read/write mutexes for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBCR_IPC_RWMUTEX_H_ +#define _LIBCR_IPC_RWMUTEX_H_ + +#include <stdbool.h> + +#include <libmisc/private.h> + +#include <libcr_ipc/_linkedlist_pub.h> + +/** + * A cr_rwmutex_t is a fair read/write mutex. + * + * A waiting writer blocks any new readers; this ensures that the + * writer is able to eventually get the lock. + * + * None of the methods have `_from_intrhandler` variants because (1) + * an interrupt handler can't block, so it shouldn't ever lock a mutex + * because that can block; and (2) if it can't lock a mutex in the + * first place, then it has no business unlocking one. + */ +typedef struct { + BEGIN_PRIVATE(LIBCR_IPC_RWMUTEX_H); + unsigned nreaders; + bool locked; + bool unpausing; + _cr_ipc_sll_root waiters; + END_PRIVATE(LIBCR_IPC_RWMUTEX_H); +} cr_rwmutex_t; + +/** + * Lock the mutex for writing. Blocks if it is already locked. + * + * @runs_in coroutine + * @cr_pauses maybe + * @cr_yields maybe + */ +void cr_rwmutex_lock(cr_rwmutex_t *mu); + +/** + * Undo a single cr_rwmutex_lock() call. Unblocks either a single + * coroutine that is blocked on cr_rwmutex_lock() or arbitrarily many + * coroutines that are blocked on cr_rwmutex_rlock(). + * + * @runs_in coroutine + * @cr_pauses never + * @cr_yields never + */ +void cr_rwmutex_unlock(cr_rwmutex_t *mu); + +/** + * Lock the mutex for reading. Blocks if it is already locked for + * writing. + * + * @runs_in coroutine + * @cr_pauses maybe + * @cr_yields maybe + */ +void cr_rwmutex_rlock(cr_rwmutex_t *mu); + +/** + * Undo a single cr_rwmutext_rock() call. If the reader count is + * reduced to zero, unblocks a single coroutine that is blocked on + * cr_rwmutex_lock(). + * + * @runs_in coroutine + * @cr_pauses never + * @cr_yields never + */ +void cr_rwmutex_runlock(cr_rwmutex_t *mu); + +#endif /* _LIBCR_IPC_RWMUTEX_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/select.h b/libcr_ipc/include/libcr_ipc/select.h deleted file mode 100644 index 788bf53..0000000 --- a/libcr_ipc/include/libcr_ipc/select.h +++ /dev/null @@ -1,179 +0,0 @@ -/* libcr_ipc/select.h - Select between channels - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <alloca.h> /* for alloca() */ -#include <stddef.h> /* for size_t */ - -#include <libmisc/assert.h> -#include <libmisc/rand.h> - -#include <libcr_ipc/chan.h> - -#ifndef _LIBCR_IPC_SELECT_H_ -#define _LIBCR_IPC_SELECT_H_ - -/* arguments ******************************************************************/ - -/** - * Do not populate cr_select_arg yourself; use the - * CR_SELECT_{RECV,SEND,DEFAULT} macros. - */ -struct cr_select_arg { - enum { - _CR_SELECT_OP_RECV, - _CR_SELECT_OP_SEND, - _CR_SELECT_OP_DEFAULT, - } op; - struct _cr_chan *ch; - void *val_ptr; - size_t val_siz; -}; - -#define CR_SELECT_RECV(CH, VALP) ({ \ - assert(CH); \ - assert(VALP); \ - /* The _valp indirection is to get the \ - * compiler to check that the types are \ - * compatible. */ \ - typeof((CH)->vals[0]) *_valp = VALP; \ - ((struct cr_select_arg){ \ - .op = _CR_SELECT_OP_RECV, \ - .ch = &((CH)->core), \ - .val_ptr = _valp, \ - .val_siz = sizeof((CH)->vals[0]), \ - }); \ - }) -#define CR_SELECT_SEND(CH, VAL) ({ \ - assert(CH); \ - typeof((CH)->vals[0]) val_lvalue = VAL; \ - ((struct cr_select_arg){ \ - .op = _CR_SELECT_OP_SEND, \ - .ch = &((CH)->core), \ - .val_ptr = &val_lvalue;, \ - .val_siz = sizeof((CH)->vals[0]), \ - }); \ - }) -#define CR_SELECT_DEFAULT \ - ((struct cr_select_arg){ \ - .op = _CR_SELECT_OP_DEFAULT, \ - }) - -/* cr_select_v(arg_cnt, arg_vec) **********************************************/ - -enum _cr_select_class { - _CR_SELECT_CLASS_DEFAULT, - _CR_SELECT_CLASS_BLOCKING, - _CR_SELECT_CLASS_NONBLOCK, -}; - -static inline enum _cr_select_class _cr_select_getclass(struct cr_select_arg arg) { - switch (arg.op) { - case _CR_SELECT_OP_RECV: - if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_SENDER) - return _CR_SELECT_CLASS_NONBLOCK; - else - return _CR_SELECT_CLASS_BLOCKING; - case _CR_SELECT_OP_SEND: - if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_RECVER) - return _CR_SELECT_CLASS_NONBLOCK; - else - return _CR_SELECT_CLASS_BLOCKING; - case _CR_SELECT_OP_DEFAULT: - return _CR_SELECT_CLASS_DEFAULT; - default: - assert_notreached("invalid arg.op"); - } -} - -struct _cr_select_waiters { - size_t cnt; - struct cr_select_arg *args; - struct _cr_chan_waiter *nodes; -}; - -static void _cr_select_dequeue(void *_waiters, size_t idx) { - struct _cr_select_waiters *waiters = waiters; - for (size_t i = 0; i < waiters->cnt; i++) - _cr_ipc_dll_remove(&(waiters->args[i].ch->waiters), - &(waiters->nodes[i])); - waiters->cnt = idx; -} - -static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { - size_t cnt_blocking = 0; - size_t cnt_nonblock = 0; - size_t cnt_default = 0; - - assert(arg_cnt); - assert(arg_vec); - - for (size_t i = 0; i < arg_cnt; i++) { - switch (_cr_select_getclass(arg_vec[i])) { - case _CR_SELECT_CLASS_BLOCKING: - cnt_blocking++; - break; - case _CR_SELECT_CLASS_NONBLOCK: - cnt_nonblock++; - break; - case _CR_SELECT_CLASS_DEFAULT: - cnt_default++; - break; - } - } - - if (cnt_nonblock) { - size_t choice = rand_uint63n(cnt_nonblock); - for (size_t i = 0, seen = 0; i < arg_cnt; i++) { - if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_NONBLOCK) { - if (seen == choice) { - _cr_chan_xfer(arg_vec[i].op == _CR_SELECT_OP_RECV - ? _CR_CHAN_RECVER - : _CR_CHAN_SENDER, - arg_vec[i].ch, - arg_vec[i].val_ptr, - arg_vec[i].val_siz); - return i; - } - seen++; - } - } - assert_notreached("should have returned from inside for() loop"); - } - - if (cnt_default) { - for (size_t i = 0; i < arg_cnt; i++) - if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_DEFAULT) - return i; - assert_notreached("should have returned from inside for() loop"); - } - - struct _cr_select_waiters waiters = { - .cnt = arg_cnt, - .args = arg_vec, - .nodes = alloca(sizeof(struct _cr_chan_waiter) * arg_cnt), - }; - for (size_t i = 0; i < arg_cnt; i++) { - waiters.nodes[i] = (struct _cr_chan_waiter){ - .cid = cr_getcid(), - .val_ptr = arg_vec[i].val_ptr, - .dequeue = _cr_select_dequeue, - .dequeue_arg1 = &waiters, - .dequeue_arg2 = i, - }; - _cr_ipc_dll_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]); - } - cr_pause_and_yield(); - return waiters.cnt; -} - -/* cr_select_l(arg1, arg2, arg3, ...) ******************************************/ - -#define cr_select_l(...) ({ \ - struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \ - cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0])); \ -}) - -#endif /* _LIBCR_IPC_SELECT_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/sema.h b/libcr_ipc/include/libcr_ipc/sema.h index 0895a22..ae8d93d 100644 --- a/libcr_ipc/include/libcr_ipc/sema.h +++ b/libcr_ipc/include/libcr_ipc/sema.h @@ -1,20 +1,17 @@ /* libcr_ipc/sema.h - Simple semaphores for libcr * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _LIBCR_IPC_SEMA_H_ #define _LIBCR_IPC_SEMA_H_ -#include <libcr/coroutine.h> /* for cid_t, cr_unpause(), cr_pause_and_yield() */ +#include <stdbool.h> -#include <libcr_ipc/_linkedlist.h> +#include <libmisc/private.h> -struct _cr_sema_waiter { - struct _cr_sema_waiter *rear; - cid_t cid; -}; +#include <libcr_ipc/_linkedlist_pub.h> /** * A cr_sema_t is a fair unbounded[1] counting semaphore. @@ -22,10 +19,11 @@ struct _cr_sema_waiter { * [1]: Well, UINT_MAX */ typedef struct { + BEGIN_PRIVATE(LIBCR_IPC_SEMA_H); unsigned int cnt; - struct { - struct _cr_sema_waiter *front, *rear; - } waiters; + bool unpausing; + _cr_ipc_sll_root waiters; + END_PRIVATE(LIBCR_IPC_SEMA_H); } cr_sema_t; /** @@ -35,34 +33,14 @@ typedef struct { * @cr_pauses never * @cr_yields never */ -static inline void cr_sema_signal(cr_sema_t *sema) { - assert(sema); - - bool saved = cr_save_and_disable_interrupts(); - sema->cnt++; - if (sema->waiters.front) { - sema->cnt--; - cr_unpause(sema->waiters.front->cid); - _cr_ipc_sll_pop_from_front(&sema->waiters); - } - cr_restore_interrupts(saved); -} +void cr_sema_signal(cr_sema_t *sema); /** * Like cr_sema_signal(), but for use from an interrupt handler. * * @runs_in intrhandler */ -static inline void cr_sema_signal_from_intrhandler(cr_sema_t *sema) { - assert(sema); - - sema->cnt++; - if (sema->waiters.front) { - sema->cnt--; - cr_unpause_from_intrhandler(sema->waiters.front->cid); - _cr_ipc_sll_pop_from_front(&sema->waiters); - } -} +void cr_sema_signal_from_intrhandler(cr_sema_t *sema); /** * Wait until the semaphore is >0, then decrement it. @@ -71,20 +49,6 @@ static inline void cr_sema_signal_from_intrhandler(cr_sema_t *sema) { * @cr_pauses maybe * @cr_yields maybe */ -static inline void cr_sema_wait(cr_sema_t *sema) { - assert(sema); - - bool saved = cr_save_and_disable_interrupts(); - if (sema->cnt) { /* non-blocking */ - sema->cnt--; - } else { /* blocking */ - struct _cr_sema_waiter self = { - .cid = cr_getcid(), - }; - _cr_ipc_sll_push_to_rear(&sema->waiters, &self); - cr_pause_and_yield(); - } - cr_restore_interrupts(saved); -} +void cr_sema_wait(cr_sema_t *sema); #endif /* _LIBCR_IPC_SEMA_H_ */ diff --git a/libcr_ipc/mutex.c b/libcr_ipc/mutex.c new file mode 100644 index 0000000..28debba --- /dev/null +++ b/libcr_ipc/mutex.c @@ -0,0 +1,45 @@ +/* libcr_ipc/mutex.c - Simple mutexes for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ + +#define IMPLEMENTATION_FOR_LIBCR_IPC_MUTEX_H YES +#include <libcr_ipc/mutex.h> + +#include "_linkedlist.h" + +struct cr_mutex_waiter { + cr_ipc_sll_node; + cid_t cid; +}; + +void cr_mutex_lock(cr_mutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + if (!mu->locked) /* non-blocking fast-path */ + mu->locked = true; + else { /* blocking slow-path */ + struct cr_mutex_waiter self = { + .cid = cr_getcid(), + }; + cr_ipc_sll_push_to_rear(&mu->waiters, &self); + cr_pause_and_yield(); + } + assert(mu->locked); +} + +void cr_mutex_unlock(cr_mutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + assert(mu->locked); + if (mu->waiters.front) { + cr_unpause(cr_ipc_sll_node_cast(struct cr_mutex_waiter, mu->waiters.front)->cid); + cr_ipc_sll_pop_from_front(&mu->waiters); + } else + mu->locked = false; +} diff --git a/libcr_ipc/rpc.c b/libcr_ipc/rpc.c new file mode 100644 index 0000000..a648fde --- /dev/null +++ b/libcr_ipc/rpc.c @@ -0,0 +1,85 @@ +/* libcr_ipc/rpc.c - Simple request/response system for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcpy() */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ + +#include <libcr_ipc/rpc.h> + +#include "_linkedlist.h" + +struct cr_rpc_requester { + cr_ipc_sll_node; + cid_t cid; + void *req_ptr; /* where to read req from */ + void *resp_ptr; /* where to write resp to */ +}; + +struct cr_rpc_responder { + cr_ipc_sll_node; + /* before enqueued | after dequeued */ + /* -------------------+-------------------- */ + cid_t cid; /* responder cid | requester cid */ + void *ptr; /* where to write req | where to write resp */ +}; + +void _cr_rpc_send_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void *resp_ptr) { + assert(ch); + assert(req_ptr); + assert(resp_ptr); + + if (ch->waiters.front && ch->waiter_typ != _CR_RPC_REQUESTER) { /* fast-path (still blocks) */ + struct cr_rpc_responder *responder = + cr_ipc_sll_node_cast(struct cr_rpc_responder, ch->waiters.front); + cr_ipc_sll_pop_from_front(&ch->waiters); + /* Copy the req to the responder's stack. */ + memcpy(responder->ptr, req_ptr, req_size); + /* Notify the responder that we have done so. */ + cr_unpause(responder->cid); + responder->cid = cr_getcid(); + responder->ptr = resp_ptr; + /* Wait for the responder to set `*resp_ptr`. */ + cr_pause_and_yield(); + } else { /* blocking slow-path */ + struct cr_rpc_requester self = { + .cid = cr_getcid(), + .req_ptr = req_ptr, + .resp_ptr = resp_ptr, + }; + cr_ipc_sll_push_to_rear(&ch->waiters, &self); + /* Wait for a responder to both copy our req and sed + * `*resp_ptr`. */ + cr_pause_and_yield(); + } +} + +void _cr_rpc_recv_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void **ret_resp_ptr, cid_t *ret_requester) { + assert(ch); + assert(req_ptr); + assert(ret_resp_ptr); + assert(ret_requester); + + if (ch->waiters.front && ch->waiter_typ != _CR_RPC_RESPONDER) { /* non-blocking fast-path */ + struct cr_rpc_requester *requester = + cr_ipc_sll_node_cast(struct cr_rpc_requester, ch->waiters.front); + cr_ipc_sll_pop_from_front(&ch->waiters); + + memcpy(req_ptr, requester->req_ptr, req_size); + *ret_requester = requester->cid; + *ret_resp_ptr = requester->resp_ptr; + } else { /* blocking slow-path */ + struct cr_rpc_responder self = { + .cid = cr_getcid(), + .ptr = req_ptr, + }; + cr_ipc_sll_push_to_rear(&ch->waiters, &self); + ch->waiter_typ = _CR_RPC_RESPONDER; + cr_pause_and_yield(); + *ret_requester = self.cid; + *ret_resp_ptr = self.ptr; + } +} diff --git a/libcr_ipc/rwmutex.c b/libcr_ipc/rwmutex.c new file mode 100644 index 0000000..04016d6 --- /dev/null +++ b/libcr_ipc/rwmutex.c @@ -0,0 +1,103 @@ +/* libcr_ipc/rwmutex.c - Simple read/write mutexes for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ + +#define IMPLEMENTATION_FOR_LIBCR_IPC_RWMUTEX_H YES +#include <libcr_ipc/rwmutex.h> + +#include "_linkedlist.h" + +struct cr_rwmutex_waiter { + cr_ipc_sll_node; + bool is_reader; + cid_t cid; +}; + +void cr_rwmutex_lock(cr_rwmutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + struct cr_rwmutex_waiter self = { + .is_reader = false, + .cid = cr_getcid(), + }; + cr_ipc_sll_push_to_rear(&mu->waiters, &self); + if (mu->waiters.front != &self.cr_ipc_sll_node || mu->locked) + cr_pause_and_yield(); + assert(mu->waiters.front == &self.cr_ipc_sll_node); + + /* We now hold the lock (and are mu->waiters.front). */ + cr_ipc_sll_pop_from_front(&mu->waiters); + assert(mu->nreaders == 0); + mu->locked = true; + mu->unpausing = false; +} + +void cr_rwmutex_rlock(cr_rwmutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + struct cr_rwmutex_waiter self = { + .is_reader = true, + .cid = cr_getcid(), + }; + cr_ipc_sll_push_to_rear(&mu->waiters, &self); + if (mu->waiters.front != &self.cr_ipc_sll_node || (mu->locked && mu->nreaders == 0)) + cr_pause_and_yield(); + assert(mu->waiters.front == &self.cr_ipc_sll_node); + + /* We now hold the lock (and are mu->waiters.front). */ + cr_ipc_sll_pop_from_front(&mu->waiters); + mu->nreaders++; + mu->locked = true; + struct cr_rwmutex_waiter *waiter = mu->waiters.front + ? cr_ipc_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front) + : NULL; + if (waiter && waiter->is_reader) { + assert(mu->unpausing); + cr_unpause(waiter->cid); + } else { + mu->unpausing = false; + } +} + +void cr_rwmutex_unlock(cr_rwmutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + assert(mu->locked); + assert(mu->nreaders == 0); + assert(!mu->unpausing); + if (mu->waiters.front) { + struct cr_rwmutex_waiter *waiter = + cr_ipc_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front); + mu->unpausing = true; + cr_unpause(waiter->cid); + } else { + mu->locked = false; + } +} + +void cr_rwmutex_runlock(cr_rwmutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + assert(mu->locked); + assert(mu->nreaders > 0); + mu->nreaders--; + if (mu->nreaders == 0 && !mu->unpausing) { + if (mu->waiters.front) { + struct cr_rwmutex_waiter *waiter = + cr_ipc_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front); + assert(!waiter->is_reader); + mu->unpausing = true; + cr_unpause(waiter->cid); + } else { + mu->locked = false; + } + } +} diff --git a/libcr_ipc/sema.c b/libcr_ipc/sema.c new file mode 100644 index 0000000..85add6c --- /dev/null +++ b/libcr_ipc/sema.c @@ -0,0 +1,66 @@ +/* libcr_ipc/sema.c - Simple semaphores for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ + +#define IMPLEMENTATION_FOR_LIBCR_IPC_SEMA_H YES +#include <libcr_ipc/sema.h> + +#include "_linkedlist.h" + +struct cr_sema_waiter { + cr_ipc_sll_node; + cid_t cid; +}; + +void cr_sema_signal(cr_sema_t *sema) { + assert(sema); + cr_assert_in_coroutine(); + + bool saved = cr_save_and_disable_interrupts(); + sema->cnt++; + if (sema->waiters.front && !sema->unpausing) { + cr_unpause( + cr_ipc_sll_node_cast(struct cr_sema_waiter, sema->waiters.front)->cid); + sema->unpausing = true; + } + cr_restore_interrupts(saved); +} + +void cr_sema_signal_from_intrhandler(cr_sema_t *sema) { + assert(sema); + cr_assert_in_intrhandler(); + + sema->cnt++; + if (sema->waiters.front && !sema->unpausing) { + cr_unpause_from_intrhandler( + cr_ipc_sll_node_cast(struct cr_sema_waiter, sema->waiters.front)->cid); + sema->unpausing = true; + } +} + +void cr_sema_wait(cr_sema_t *sema) { + assert(sema); + cr_assert_in_coroutine(); + + bool saved = cr_save_and_disable_interrupts(); + + struct cr_sema_waiter self = { + .cid = cr_getcid(), + }; + cr_ipc_sll_push_to_rear(&sema->waiters, &self); + if (sema->waiters.front != &self.cr_ipc_sll_node || !sema->cnt) + cr_pause_and_yield(); + assert(sema->waiters.front == &self.cr_ipc_sll_node && sema->cnt); + cr_ipc_sll_pop_from_front(&sema->waiters); + sema->cnt--; + if (sema->cnt && sema->waiters.front) + cr_unpause( + cr_ipc_sll_node_cast(struct cr_sema_waiter, sema->waiters.front)->cid); + else + sema->unpausing = false; + cr_restore_interrupts(saved); +} diff --git a/libcr_ipc/tests/config.h b/libcr_ipc/tests/config.h new file mode 100644 index 0000000..a648589 --- /dev/null +++ b/libcr_ipc/tests/config.h @@ -0,0 +1,20 @@ +/* config.h - Compile-time configuration for the libcr_ipc tests + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (8*1024) +#define CONFIG_COROUTINE_NAME_LEN 16 +#define CONFIG_COROUTINE_NUM 16 + +#define CONFIG_COROUTINE_MEASURE_STACK 1 +#define CONFIG_COROUTINE_PROTECT_STACK 1 +#define CONFIG_COROUTINE_DEBUG 1 +#define CONFIG_COROUTINE_VALGRIND 1 +#define CONFIG_COROUTINE_GDB 1 + +#endif /* _CONFIG_H_ */ diff --git a/libcr_ipc/tests/test.h b/libcr_ipc/tests/test.h new file mode 100644 index 0000000..4928d8d --- /dev/null +++ b/libcr_ipc/tests/test.h @@ -0,0 +1,21 @@ +/* libcr_ipc/tests/test.h - Common test utilities + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBCR_IPC_TESTS_TEST_H_ +#define _LIBCR_IPC_TESTS_TEST_H_ + +#include <stdio.h> +#include <stdlib.h> /* for exit() */ + +#define test_assert(expr) do { \ + if (!(expr)) { \ + printf("test failure: %s:%d:%s: %s\n", \ + __FILE__, __LINE__, __func__, #expr); \ + exit(1); \ + } \ + } while (0) + +#endif /* _LIBCR_IPC_TESTS_TEST_H_ */ diff --git a/libcr_ipc/tests/test_chan.c b/libcr_ipc/tests/test_chan.c new file mode 100644 index 0000000..9b6f018 --- /dev/null +++ b/libcr_ipc/tests/test_chan.c @@ -0,0 +1,49 @@ +/* libcr_ipc/tests/test_chan.c - Tests for <libcr_ipc/chan.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> +#include <libcr_ipc/chan.h> + +#include "test.h" + +CR_CHAN_DECLARE(intchan, int); + +COROUTINE cr_producer(void *_ch) { + intchan_t *ch = _ch; + cr_begin(); + + intchan_send(ch, 1); + + while (!intchan_can_send(ch)) + cr_yield(); + + intchan_send(ch, 2); + + + cr_end(); +} + +COROUTINE cr_consumer(void *_ch) { + int x; + intchan_t *ch = _ch; + cr_begin(); + + x = intchan_recv(ch); + test_assert(x == 1); + + x = intchan_recv(ch); + test_assert(x == 2); + + cr_end(); +} + +int main() { + intchan_t ch = {0}; + coroutine_add("producer", cr_producer, &ch); + coroutine_add("consumer", cr_consumer, &ch); + coroutine_main(); + return 0; +} diff --git a/libcr_ipc/tests/test_mutex.c b/libcr_ipc/tests/test_mutex.c new file mode 100644 index 0000000..43714c9 --- /dev/null +++ b/libcr_ipc/tests/test_mutex.c @@ -0,0 +1,37 @@ +/* libcr_ipc/tests/test_mutex.c - Tests for <libcr_ipc/mutex.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> +#include <libcr_ipc/mutex.h> + +#include "test.h" + +int counter = 0; + +COROUTINE cr_worker(void *_mu) { + cr_mutex_t *mu = _mu; + cr_begin(); + + for (int i = 0; i < 100; i++) { + cr_mutex_lock(mu); + int a = counter; + cr_yield(); + counter = a + 1; + cr_mutex_unlock(mu); + cr_yield(); + } + + cr_end(); +} + +int main() { + cr_mutex_t mu = {}; + coroutine_add("a", cr_worker, &mu); + coroutine_add("b", cr_worker, &mu); + coroutine_main(); + test_assert(counter == 200); + return 0; +} diff --git a/libcr_ipc/tests/test_rpc.c b/libcr_ipc/tests/test_rpc.c new file mode 100644 index 0000000..1e3c471 --- /dev/null +++ b/libcr_ipc/tests/test_rpc.c @@ -0,0 +1,59 @@ +/* libcr_ipc/tests/test_rpc.c - Tests for <libcr_ipc/rpc.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> +#include <libcr_ipc/rpc.h> + +#include "test.h" + +CR_RPC_DECLARE(intrpc, int, int); + +/* Test that the RPC is fair, have worker1 start waiting first, and + * ensure that it gets the first request. */ + +COROUTINE cr_caller(void *_ch) { + intrpc_t *ch = _ch; + cr_begin(); + + int resp = intrpc_send_req(ch, 1); + test_assert(resp == 2); + + resp = intrpc_send_req(ch, 3); + test_assert(resp == 4); + + cr_exit(); +} + +COROUTINE cr_worker1(void *_ch) { + intrpc_t *ch = _ch; + cr_begin(); + + intrpc_req_t req = intrpc_recv_req(ch); + test_assert(req.req == 1); + intrpc_send_resp(req, 2); + + cr_exit(); +} + +COROUTINE cr_worker2(void *_ch) { + intrpc_t *ch = _ch; + cr_begin(); + + intrpc_req_t req = intrpc_recv_req(ch); + test_assert(req.req == 3); + intrpc_send_resp(req, 4); + + cr_exit(); +} + +int main() { + intrpc_t ch = {0}; + coroutine_add("worker1", cr_worker1, &ch); + coroutine_add("caller", cr_caller, &ch); + coroutine_add("worker2", cr_worker2, &ch); + coroutine_main(); + return 0; +} diff --git a/libcr_ipc/tests/test_rwmutex.c b/libcr_ipc/tests/test_rwmutex.c new file mode 100644 index 0000000..77e8c7c --- /dev/null +++ b/libcr_ipc/tests/test_rwmutex.c @@ -0,0 +1,88 @@ +/* libcr_ipc/tests/test_rwmutex.c - Tests for <libcr_ipc/rwmutex.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> + +#include <libcr/coroutine.h> +#include <libcr_ipc/rwmutex.h> + +#include "test.h" + +cr_rwmutex_t mu = {}; +char out[10] = {0}; +size_t len = 0; + +COROUTINE cr1_reader(void *_mu) { + cr_rwmutex_t *mu = _mu; + cr_begin(); + + cr_rwmutex_rlock(mu); + out[len++] = 'r'; + cr_yield(); + cr_rwmutex_runlock(mu); + + cr_end(); +} + +COROUTINE cr1_writer(void *_mu) { + cr_rwmutex_t *mu = _mu; + cr_begin(); + + cr_rwmutex_lock(mu); + out[len++] = 'w'; + cr_yield(); + cr_rwmutex_unlock(mu); + + cr_end(); +} + +COROUTINE cr2_waiter(void *_ch) { + char ch = *(char *)_ch; + cr_begin(); + + cr_rwmutex_rlock(&mu); + out[len++] = ch; + cr_rwmutex_runlock(&mu); + + cr_end(); +} + +COROUTINE cr2_init(void *) { + cr_begin(); + + char ch; + cr_rwmutex_lock(&mu); + ch = 'a'; coroutine_add("wait-a", cr2_waiter, &ch); + ch = 'b'; coroutine_add("wait-b", cr2_waiter, &ch); + cr_yield(); + ch = 'c'; coroutine_add("wait-c", cr2_waiter, &ch); + cr_rwmutex_unlock(&mu); + + cr_end(); +} + +int main() { + printf("== test 1 =========================================\n"); + coroutine_add("r1", cr1_reader, &mu); + coroutine_add("r2", cr1_reader, &mu); + coroutine_add("w", cr1_writer, &mu); + coroutine_add("r3", cr1_reader, &mu); + coroutine_add("r4", cr1_reader, &mu); + coroutine_main(); + test_assert(len == 5); + test_assert(strcmp(out, "rrwrr") == 0); + + printf("== test 2 =========================================\n"); + mu = (cr_rwmutex_t){}; + len = 0; + memset(out, 0, sizeof(out)); + coroutine_add("init", cr2_init, NULL); + coroutine_main(); + test_assert(len == 3); + test_assert(strcmp(out, "abc") == 0); + + return 0; +} diff --git a/libcr_ipc/tests/test_select.c b/libcr_ipc/tests/test_select.c new file mode 100644 index 0000000..1db645b --- /dev/null +++ b/libcr_ipc/tests/test_select.c @@ -0,0 +1,90 @@ +/* libcr_ipc/tests/test_select.c - Tests for <libcr_ipc/select.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> +#include <libcr_ipc/chan.h> + +#include "test.h" + +CR_CHAN_DECLARE(intchan, int); + +intchan_t ch[10] = {0}; +intchan_t fch = {0}; + +COROUTINE cr_consumer(void *) { + cr_begin(); + + struct cr_select_arg args[11]; + + bool chdone[10] = {0}; + int arg2ch[10]; + for (;;) { + int ret_ch; + int i_arg = 0; + for (int i_ch = 0; i_ch < 10; i_ch++) { + if (!chdone[i_ch]) { + args[i_arg] = CR_SELECT_RECV(&ch[i_ch], &ret_ch); + arg2ch[i_arg] = i_ch; + i_arg++; + } + } + if (!i_arg) + break; + args[i_arg] = CR_SELECT_DEFAULT; /* check that default doesn't trigger */ + test_assert(i_arg <= 10); + int ret_arg = cr_select_v(i_arg+1, args); + test_assert(ret_arg < i_arg); + test_assert(arg2ch[ret_arg] == ret_ch); + chdone[ret_ch] = true; + } + int ret_ch, ret_arg; + args[0] = CR_SELECT_RECV(&ch[0], &ret_ch); + args[1] = CR_SELECT_DEFAULT; + ret_arg = cr_select_v(2, args); + test_assert(ret_arg == 1); + + int send = 567; + args[0] = CR_SELECT_SEND(&fch, &send); + args[1] = CR_SELECT_DEFAULT; + ret_arg = cr_select_v(2, args); + test_assert(ret_arg == 0); + + send = 890; + args[0] = CR_SELECT_SEND(&fch, &send); + args[1] = CR_SELECT_DEFAULT; + ret_arg = cr_select_v(2, args); + test_assert(ret_arg == 1); + + cr_end(); +} + +COROUTINE cr_producer(void *_n) { + int n = *(int *)_n; + cr_begin(); + + intchan_send(&ch[n], n); + + cr_end(); +} + +COROUTINE cr_final(void *) { + cr_begin(); + + int ret = intchan_recv(&fch); + printf("ret=%d\n", ret); + test_assert(ret == 567); + + cr_end(); +} + +int main() { + for (int i = 0; i < 10; i++) + coroutine_add("producer", cr_producer, &i); + coroutine_add("consumer", cr_consumer, NULL); + coroutine_add("final", cr_final, NULL); + coroutine_main(); + return 0; +} diff --git a/libcr_ipc/tests/test_sema.c b/libcr_ipc/tests/test_sema.c new file mode 100644 index 0000000..e5b22a5 --- /dev/null +++ b/libcr_ipc/tests/test_sema.c @@ -0,0 +1,76 @@ +/* libcr_ipc/tests/test_sema.c - Tests for <libcr_ipc/sema.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> + +#define IMPLEMENTATION_FOR_LIBCR_IPC_SEMA_H YES /* so we can access .cnt */ +#include <libcr_ipc/sema.h> + +#include "test.h" + +int counter = 0; + +COROUTINE cr_first(void *_sema) { + cr_sema_t *sema = _sema; + cr_begin(); + + cr_sema_wait(sema); + counter++; + cr_sema_signal(sema); + + cr_exit(); +} + +COROUTINE cr_second(void *_sema) { + cr_sema_t *sema = _sema; + cr_begin(); + + cr_sema_signal(sema); /* should be claimed by cr_first, which has been waiting */ + cr_sema_wait(sema); /* should block, because cr_first claimed it */ + test_assert(counter == 1); + + cr_exit(); +} + +COROUTINE cr_producer(void *_sema) { + cr_sema_t *sema = _sema; + cr_begin(); + + for (int i = 0; i < 10; i++) + cr_sema_signal(sema); + + cr_end(); +} + +COROUTINE cr_consumer(void *_sema) { + cr_sema_t *sema = _sema; + cr_begin(); + + for (int i = 0; i < 5; i++) + cr_sema_wait(sema); + + cr_end(); +} + +int main() { + cr_sema_t sema = {}; + + printf("== test 1 =========================================\n"); + coroutine_add("first", cr_first, &sema); + coroutine_add("second", cr_second, &sema); + coroutine_main(); + test_assert(sema.cnt == 0); + + printf("== test 2 =========================================\n"); + coroutine_add("consumer", cr_consumer, &sema); + coroutine_add("producer", cr_producer, &sema); + coroutine_main(); + coroutine_add("consumer", cr_consumer, &sema); + coroutine_main(); + test_assert(sema.cnt == 0); + + return 0; +} diff --git a/libdhcp/CMakeLists.txt b/libdhcp/CMakeLists.txt index 2ded1f4..dee7cb6 100644 --- a/libdhcp/CMakeLists.txt +++ b/libdhcp/CMakeLists.txt @@ -1,10 +1,10 @@ -# libdhcp/CMakeLists.txt - TODO +# libdhcp/CMakeLists.txt - A DHCP client # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libdhcp INTERFACE) -target_include_directories(libdhcp SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(libdhcp PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_sources(libdhcp INTERFACE dhcp_client.c ) diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c index c5da59b..8ec3647 100644 --- a/libdhcp/dhcp_client.c +++ b/libdhcp/dhcp_client.c @@ -1,6 +1,6 @@ /* libdhcp/dhcp_client.c - A DHCP client * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -87,7 +87,6 @@ #include <string.h> /* for strlen(), memcpy(), memset() */ #include <libmisc/rand.h> -#include <libmisc/vcall.h> #include <libhw/generic/alarmclock.h> #define LOG_NAME DHCP @@ -125,8 +124,8 @@ enum requirement { struct dhcp_client { /* Static. */ - implements_net_iface *iface; - implements_net_packet_conn *sock; + lo_interface net_iface iface; + lo_interface net_packet_conn sock; struct net_eth_addr self_eth_addr; char *self_hostname; size_t self_id_len; @@ -167,6 +166,18 @@ struct dhcp_client { }; +static const char *state_strs[] = { + [STATE_INIT] = "INIT", + [STATE_SELECTING] = "SELECTING", + [STATE_REQUESTING] = "REQUESTING", + [STATE_BOUND] = "BOUND", + [STATE_RENEWING] = "RENEWING", + [STATE_REBINDING] = "REBINDING", + + [STATE_INIT_REBOOT] = "INIT_REBOOT", + [STATE_REBOOTING] = "REBOOTING", +}; + /** * For convenience in switch blocks, a list of the states that, when * msgtyp==DHCP_MSGTYP_REQUEST, dhcp_client_send() has assert()ed the state is @@ -301,7 +312,7 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c switch (msgtyp) { case DHCP_MSGTYP_DISCOVER: case DHCP_MSGTYP_INFORM: case DHCP_MSGTYP_REQUEST: - secs = (VCALL(bootclock, get_time_ns) - client->time_ns_init)/NS_PER_S; + secs = (LO_CALL(bootclock, get_time_ns) - client->time_ns_init)/NS_PER_S; if (!secs) /* systemd's sd-dhcp-client.c asserts that some * servers are broken and require .secs to be @@ -449,7 +460,8 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c /**********************************************************************\ * Send * \**********************************************************************/ - ssize_t r = VCALL(client->sock, sendto, scratch_msg, DHCP_MSG_BASE_SIZE + optlen, + debugf("state %s: sending DHCP %s", state_strs[client->state], dhcp_msgtyp_str(msgtyp)); + ssize_t r = LO_CALL(client->sock, sendto, scratch_msg, DHCP_MSG_BASE_SIZE + optlen, client_broadcasts ? net_ip4_addr_broadcast : client->lease_server_id, DHCP_PORT_SERVER); if (r < 0) { debugf("error: sendto: %zd", r); @@ -566,7 +578,7 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg assert(client); ignore: - msg_len = VCALL(client->sock, recvfrom, &ret->raw, sizeof(ret->raw), &srv_addr, &srv_port); + msg_len = LO_CALL(client->sock, recvfrom, &ret->raw, sizeof(ret->raw), &srv_addr, &srv_port); if (msg_len < 0) /* msg_len is -errno */ return msg_len; @@ -689,10 +701,11 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg } /** @return true if there's a conflict, false if the addr appears to be unused */ -static bool dhcp_check_conflict(implements_net_packet_conn *sock, struct net_ip4_addr addr) { - assert(sock); - ssize_t v = VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000); - return v != NET_EARP_TIMEOUT; +static bool dhcp_check_conflict(lo_interface net_packet_conn sock, struct net_ip4_addr addr) { + assert(!LO_IS_NULL(sock)); + ssize_t v = LO_CALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000); + debugf("check_ip_conflict => %zd", v); + return v != -NET_EARP_TIMEOUT; } static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_msg *msg, bool ifup) { @@ -726,12 +739,17 @@ static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_ client->lease_time_ns_t2 = (dur_ns_t2 == DHCP_INFINITY * NS_PER_S) ? 0 : client->time_ns_init + dur_ns_t2; client->lease_time_ns_end = (dur_ns_end == DHCP_INFINITY * NS_PER_S) ? 0 : client->time_ns_init + dur_ns_end; - if (ifup) - VCALL(client->iface, ifup, (struct net_iface_config){ + if (ifup) { + infof("applying configuration to "PRI_net_eth_addr, ARG_net_eth_addr(client->self_eth_addr)); + infof(":: addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(client->lease_client_addr)); + infof(":: gateway_addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(client->lease_auxdata.gateway_addr)); + infof(":: subnet_mask = "PRI_net_ip4_addr, ARG_net_ip4_addr(client->lease_auxdata.subnet_mask)); + LO_CALL(client->iface, ifup, (struct net_iface_config){ .addr = client->lease_client_addr, .gateway_addr = client->lease_auxdata.gateway_addr, .subnet_mask = client->lease_auxdata.subnet_mask, }); + } } static void dhcp_client_setstate(struct dhcp_client *client, @@ -749,14 +767,15 @@ static void dhcp_client_setstate(struct dhcp_client *client, /* State transition diagram: https://datatracker.ietf.org/doc/html/rfc2131#page-35 */ for (;;) { + debugf("loop: state=%s", state_strs[client->state]); switch (client->state) { case STATE_INIT: client->xid = rand_uint63n(UINT32_MAX); - client->time_ns_init = VCALL(bootclock, get_time_ns); + client->time_ns_init = LO_CALL(bootclock, get_time_ns); dhcp_client_setstate(client, STATE_SELECTING, DHCP_MSGTYP_DISCOVER, NULL, scratch_msg); break; case STATE_SELECTING: - VCALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS); + LO_CALL(client->sock, set_recv_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS); switch ((r = dhcp_client_recv(client, scratch_msg))) { case 0: switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { @@ -778,7 +797,7 @@ static void dhcp_client_setstate(struct dhcp_client *client, } break; case STATE_REQUESTING: - VCALL(client->sock, set_read_deadline, 0); + LO_CALL(client->sock, set_recv_deadline, 0); switch ((r = dhcp_client_recv(client, scratch_msg))) { case 0: switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { @@ -787,6 +806,8 @@ static void dhcp_client_setstate(struct dhcp_client *client, break; case DHCP_MSGTYP_ACK: if (dhcp_check_conflict(client->sock, client->lease_client_addr)) { + debugf("IP "PRI_net_ip4_addr" is already in use", + ARG_net_ip4_addr(client->lease_client_addr)); dhcp_client_setstate(client, STATE_INIT, DHCP_MSGTYP_DECLINE, "IP is already in use", scratch_msg); } else { dhcp_client_take_lease(client, scratch_msg, true); @@ -803,7 +824,7 @@ static void dhcp_client_setstate(struct dhcp_client *client, } break; case STATE_BOUND: - VCALL(client->sock, set_read_deadline, client->lease_time_ns_t1); + LO_CALL(client->sock, set_recv_deadline, client->lease_time_ns_t1); switch ((r = dhcp_client_recv(client, scratch_msg))) { case 0: /* discard */ @@ -818,14 +839,14 @@ static void dhcp_client_setstate(struct dhcp_client *client, break; case STATE_RENEWING: client->xid = rand_uint63n(UINT32_MAX); - client->time_ns_init = VCALL(bootclock, get_time_ns); + client->time_ns_init = LO_CALL(bootclock, get_time_ns); - VCALL(client->sock, set_read_deadline, client->lease_time_ns_t2); + LO_CALL(client->sock, set_recv_deadline, client->lease_time_ns_t2); switch ((r = dhcp_client_recv(client, scratch_msg))) { case 0: switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { case DHCP_MSGTYP_NAK: - VCALL(client->iface, ifdown); + LO_CALL(client->iface, ifdown); dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); break; case DHCP_MSGTYP_ACK: @@ -846,12 +867,12 @@ static void dhcp_client_setstate(struct dhcp_client *client, } break; case STATE_REBINDING: - VCALL(client->sock, set_read_deadline, client->lease_time_ns_end); + LO_CALL(client->sock, set_recv_deadline, client->lease_time_ns_end); switch ((r = dhcp_client_recv(client, scratch_msg))) { case 0: switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { case DHCP_MSGTYP_NAK: - VCALL(client->iface, ifdown); + LO_CALL(client->iface, ifdown); dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); break; case DHCP_MSGTYP_ACK: @@ -863,7 +884,7 @@ static void dhcp_client_setstate(struct dhcp_client *client, } break; case -NET_ERECV_TIMEOUT: - VCALL(client->iface, ifdown); + LO_CALL(client->iface, ifdown); dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); break; default: @@ -880,9 +901,9 @@ static void dhcp_client_setstate(struct dhcp_client *client, } } -[[noreturn]] void dhcp_client_main(implements_net_iface *iface, +[[noreturn]] void dhcp_client_main(lo_interface net_iface iface, char *self_hostname) { - assert(iface); + assert(!LO_IS_NULL(iface)); /* Even though a client ID is optional and not meaningful for * us (the best we can do is to duplicate .chaddr), systemd's @@ -890,14 +911,14 @@ static void dhcp_client_setstate(struct dhcp_client *client, * require it to be set. */ struct {uint8_t typ; struct net_eth_addr dat;} client_id = { DHCP_HTYPE_ETHERNET, - VCALL(iface, hwaddr), + LO_CALL(iface, hwaddr), }; struct dhcp_client client = { /* Static. */ .iface = iface, - .sock = VCALL(iface, udp_conn, DHCP_PORT_CLIENT), - .self_eth_addr = VCALL(iface, hwaddr), + .sock = LO_CALL(iface, udp_conn, DHCP_PORT_CLIENT), + .self_eth_addr = LO_CALL(iface, hwaddr), .self_hostname = self_hostname, .self_id_len = sizeof(client_id), .self_id_dat = &client_id, @@ -905,7 +926,7 @@ static void dhcp_client_setstate(struct dhcp_client *client, /* Mutable. */ .state = STATE_INIT, }; - assert(client.sock); + assert(!LO_IS_NULL(client.sock)); struct dhcp_recv_msg scratch; diff --git a/libdhcp/dhcp_common.h b/libdhcp/dhcp_common.h index b265df3..5b51ce2 100644 --- a/libdhcp/dhcp_common.h +++ b/libdhcp/dhcp_common.h @@ -68,6 +68,7 @@ #define _LIBDHCP_DHCP_COMMON_H_ #include <libmisc/endian.h> +#include <libmisc/log.h> /* for const_byte_str() */ /* Config *********************************************************************/ @@ -302,4 +303,18 @@ static inline bool dhcp_opt_length_is_valid(uint8_t opt, uint16_t len) { #define DHCP_MSGTYP_RELEASE ((uint8_t) 7) /* RFC2132, client->server */ #define DHCP_MSGTYP_INFORM ((uint8_t) 8) /* RFC2132, client->server */ +static const char *dhcp_msgtyp_str(uint8_t typ) { + switch (typ) { + case DHCP_MSGTYP_DISCOVER: return "DHCP_MSGTYP_DISCOVER"; + case DHCP_MSGTYP_OFFER: return "DHCP_MSGTYP_OFFER"; + case DHCP_MSGTYP_REQUEST: return "DHCP_MSGTYP_REQUEST"; + case DHCP_MSGTYP_DECLINE: return "DHCP_MSGTYP_DECLINE"; + case DHCP_MSGTYP_ACK: return "DHCP_MSGTYP_ACK"; + case DHCP_MSGTYP_NAK: return "DHCP_MSGTYP_NAK"; + case DHCP_MSGTYP_RELEASE: return "DHCP_MSGTYP_RELEASE"; + case DHCP_MSGTYP_INFORM: return "DHCP_MSGTYP_INFORM"; + default: return const_byte_str(typ); + } +} + #endif /* _LIBDHCP_DHCP_COMMON_H_ */ diff --git a/libdhcp/include/libdhcp/client.h b/libdhcp/include/libdhcp/client.h index c3cb76f..f81e9b1 100644 --- a/libdhcp/include/libdhcp/client.h +++ b/libdhcp/include/libdhcp/client.h @@ -1,6 +1,6 @@ /* libdhcp/client.h - A DHCP client * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -69,7 +69,7 @@ #include <libhw/generic/net.h> -[[noreturn]] void dhcp_client_main(implements_net_iface *iface, +[[noreturn]] void dhcp_client_main(lo_interface net_iface iface, char *self_hostname); #endif /* _LIBDHCP_CLIENT_H_ */ diff --git a/libfmt/CMakeLists.txt b/libfmt/CMakeLists.txt new file mode 100644 index 0000000..f65d462 --- /dev/null +++ b/libfmt/CMakeLists.txt @@ -0,0 +1,17 @@ +# libfmt/CMakeLists.txt - Support for pico-fmt +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +add_library(libfmt INTERFACE) +target_include_directories(libfmt PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_sources(libfmt INTERFACE + libmisc.c + libobj.c + quote.c +) +target_link_libraries(libfmt INTERFACE + pico_fmt + libmisc + libobj +) diff --git a/libfmt/include/libfmt/fmt.h b/libfmt/include/libfmt/fmt.h new file mode 100644 index 0000000..1b5bde1 --- /dev/null +++ b/libfmt/include/libfmt/fmt.h @@ -0,0 +1,23 @@ +/* libfmt/fmt.h - Support for pico-fmt + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBFMT_FMT_H_ +#define _LIBFMT_FMT_H_ + +#include <pico/fmt_printf.h> +#include <pico/fmt_install.h> + +#include <libobj/obj.h> + +/** + * An object that implements fmt_formatter can be printed using + * `printf("%v", boxed_obj)`. + */ +#define fmt_formatter_LO_IFACE \ + LO_FUNC(void, format, struct fmt_state *) +LO_INTERFACE(fmt_formatter); + +#endif /* _LIBFMT_FMT_H_ */ diff --git a/libfmt/libmisc.c b/libfmt/libmisc.c new file mode 100644 index 0000000..97a2c80 --- /dev/null +++ b/libfmt/libmisc.c @@ -0,0 +1,61 @@ +/* libfmt/libmisc.c - Integrate pico-fmt with libmisc + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdarg.h> /* for va_list, va_start(), va_end() */ +#include <stdio.h> /* for vprintf(), putchar() */ +#if LIB_PICO_STDIO +#include <pico/stdio.h> /* for stdio_putchar_raw() */ +#endif + +#include <libmisc/macro.h> /* for LM_UNUSED() */ +#include <libmisc/_intercept.h> /* for __lm_printf() and __lm_light_printf() */ + +#include <libfmt/fmt.h> /* for fmt_vfctprintf() */ + +#if LIB_PICO_STDIO +static void libfmt_light_fct(char character, void *LM_UNUSED(arg)) { + if (character == '\n') + stdio_putchar_raw('\r'); + stdio_putchar_raw(character); +} +#else +static void libfmt_libc_fct(char character, void *LM_UNUSED(arg)) { + putchar(character); +} +#endif + +int __lm_printf(const char *format, ...) { + va_list va; + va_start(va, format); +#if LIB_PICO_STDIO + /* pico_stdio has already intercepted vprintf for us, and + * their stdio_buffered_printer() is better than our + * libfmt_libc_fct() because buffering. */ + int ret = vprintf(format, va); +#else + int ret = fmt_vfctprintf(libfmt_libc_fct, NULL, format, va); +#endif + va_end(va); + return ret; +} + +int __lm_light_printf(const char *format, ...) { + va_list va; + va_start(va, format); +#if LIB_PICO_STDIO + /* libfmt_light_fct() and stdio_buffered_printer() both use 68 + * bytes of stack; but the buffer lives on the stack of + * stdio.c:__wrap_vprintf(); so that's where you'll see the + * numbers be different if you're analyzing it. (Also, being + * able to skip the stdio_stack_buffer_flush() call.) */ + int ret = fmt_vfctprintf(libfmt_light_fct, NULL, format, va); + stdio_flush(); +#else + int ret = fmt_vfctprintf(libfmt_libc_fct, NULL, format, va); +#endif + va_end(va); + return ret; +} diff --git a/libfmt/libobj.c b/libfmt/libobj.c new file mode 100644 index 0000000..e4b833b --- /dev/null +++ b/libfmt/libobj.c @@ -0,0 +1,17 @@ +/* libfmt/libobj.c - Integrate pico-fmt with libobj + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libfmt/fmt.h> + +static void libfmt_conv_formatter(struct fmt_state *state) { + lo_interface fmt_formatter obj = va_arg(*state->args, lo_interface fmt_formatter); + LO_CALL(obj, format, state); +} + +[[gnu::constructor]] +static void libfmt_install_formatter(void) { + fmt_install('v', libfmt_conv_formatter); +} diff --git a/libfmt/quote.c b/libfmt/quote.c new file mode 100644 index 0000000..c91e0b0 --- /dev/null +++ b/libfmt/quote.c @@ -0,0 +1,159 @@ +/* libfmt/quote.c - C-string quoting for pico-fmt + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for strnlen() */ +#include <stdint.h> /* for uint{n}_t() */ + +#include <libfmt/fmt.h> + +enum quote { + QUOTE_NONE, /* c */ + QUOTE_SIMPLE, /* \c */ + QUOTE_U4, /* \uABCD */ + QUOTE_U8, /* \UABCDABCD */ +}; + +static inline enum quote needs_quote(uint32_t ch) { + if (ch == '\a' || + ch == '\b' || + ch == '\f' || + ch == '\n' || + ch == '\r' || + ch == '\t' || + ch == '\v' || + ch == '\\' || + ch == '\'' || + ch == '"' || + ch == '?') + return QUOTE_SIMPLE; + else if (' ' <= ch && ch <= '~') + return QUOTE_NONE; + else if (ch < 0x10000) + return QUOTE_U4; + else + return QUOTE_U8; +} + +/** + * Quote a string to ASCII-only C syntax. Valid UTF-8 is quoted as + * short C-escape characters, \uABCD or \UABCDABCD; invalid UTF-8 is + * quoted as \xAB. + */ +static void libfmt_conv_quote(struct fmt_state *state) { + uint32_t ch; + uint8_t chlen; + + const char *in = va_arg(*state->args, char*); + size_t in_len = strnlen(in, (state->flags & FMT_FLAG_PRECISION) ? state->precision : (size_t)-1); + + size_t out_len = 2; + for (size_t pos = 0; pos < in_len;) { + if ((in[pos] & 0b10000000) == 0b00000000) { ch = in[pos] & 0b01111111; chlen = 1; } + else if ((in[pos] & 0b11100000) == 0b11000000) { ch = in[pos] & 0b00011111; chlen = 2; } + else if ((in[pos] & 0b11110000) == 0b11100000) { ch = in[pos] & 0b00001111; chlen = 3; } + else if ((in[pos] & 0b11111000) == 0b11110000) { ch = in[pos] & 0b00000111; chlen = 4; } + else goto measure_invalid_utf8; + if ((ch == 0 && chlen != 1) || pos + chlen > in_len) goto measure_invalid_utf8; + for (uint8_t i = 1; i < chlen; i++) { + if ((in[pos+i] & 0b11000000) != 0b10000000) goto measure_invalid_utf8; + ch = (ch << 6) | (in[pos+i] & 0b00111111); + } + if (ch > 0x10FFFF) goto measure_invalid_utf8; + pos += chlen; + + switch (needs_quote(ch)) { + case QUOTE_NONE : out_len += 1; break; + case QUOTE_SIMPLE : out_len += 2; break; + case QUOTE_U4 : out_len += 6; break; + case QUOTE_U8 : out_len += 10; break; + } + continue; + measure_invalid_utf8: + pos++; + out_len += 4; /* \xAB */ + } + + if (!(state->flags & FMT_FLAG_LEFT)) { + for (size_t i = 0; i + out_len < state->width; i++) { + fmt_state_putchar(state, ' '); + } + } + + fmt_state_putchar(state, '"'); + for (size_t pos = 0; pos < in_len;) { + if ((in[pos] & 0b10000000) == 0b00000000) { ch = in[pos] & 0b01111111; chlen = 1; } + else if ((in[pos] & 0b11100000) == 0b11000000) { ch = in[pos] & 0b00011111; chlen = 2; } + else if ((in[pos] & 0b11110000) == 0b11100000) { ch = in[pos] & 0b00001111; chlen = 3; } + else if ((in[pos] & 0b11111000) == 0b11110000) { ch = in[pos] & 0b00000111; chlen = 4; } + else goto output_invalid_utf8; + if ((ch == 0 && chlen != 1) || pos + chlen > in_len) goto output_invalid_utf8; + for (uint8_t i = 1; i < chlen; i++) { + if ((in[pos+i] & 0b11000000) != 0b10000000) goto output_invalid_utf8; + ch = (ch << 6) | (in[pos+i] & 0b00111111); + } + if (ch > 0x10FFFF) goto output_invalid_utf8; + pos += chlen; + + switch (needs_quote(ch)) { + case QUOTE_NONE: + fmt_state_putchar(state, ch); + break; + case QUOTE_SIMPLE: + fmt_state_putchar(state, '\\'); + switch (ch) { + case '\a': fmt_state_putchar(state, 'a'); break; + case '\b': fmt_state_putchar(state, 'b'); break; + case '\f': fmt_state_putchar(state, 'f'); break; + case '\n': fmt_state_putchar(state, 'n'); break; + case '\r': fmt_state_putchar(state, 'r'); break; + case '\t': fmt_state_putchar(state, 't'); break; + case '\v': fmt_state_putchar(state, 'v'); break; + case '\\': fmt_state_putchar(state, '\\'); break; + case '\'': fmt_state_putchar(state, '\''); break; + case '"': fmt_state_putchar(state, '"'); break; + case '?': fmt_state_putchar(state, '?'); break; + } + break; + case QUOTE_U4: + fmt_state_putchar(state, '\\'); + fmt_state_putchar(state, 'u'); + fmt_state_putchar(state, (ch >> 12) & 0xF); + fmt_state_putchar(state, (ch >> 8) & 0xF); + fmt_state_putchar(state, (ch >> 4) & 0xF); + fmt_state_putchar(state, (ch >> 0) & 0xF); + break; + case QUOTE_U8: + fmt_state_putchar(state, '\\'); + fmt_state_putchar(state, 'U'); + fmt_state_putchar(state, (ch >> 28) & 0xF); + fmt_state_putchar(state, (ch >> 24) & 0xF); + fmt_state_putchar(state, (ch >> 20) & 0xF); + fmt_state_putchar(state, (ch >> 16) & 0xF); + fmt_state_putchar(state, (ch >> 12) & 0xF); + fmt_state_putchar(state, (ch >> 8) & 0xF); + fmt_state_putchar(state, (ch >> 4) & 0xF); + fmt_state_putchar(state, (ch >> 0) & 0xF); + break; + } + continue; + output_invalid_utf8: + fmt_state_putchar(state, '\\'); + fmt_state_putchar(state, 'x'); + fmt_state_putchar(state, (in[pos] >> 4) & 0xF); + fmt_state_putchar(state, (in[pos] >> 0) & 0xF); + pos++; + } + fmt_state_putchar(state, '"'); + + for (size_t i = 0; i + out_len < state->width; i++) { + fmt_state_putchar(state, ' '); + } +} + +[[gnu::constructor]] +static void libfmt_install_quote(void) { + fmt_install('q', libfmt_conv_quote); +} diff --git a/libhw/CMakeLists.txt b/libhw/CMakeLists.txt deleted file mode 100644 index d1767da..0000000 --- a/libhw/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -# libhw/CMakeLists.txt - Device drivers -# -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -add_library(libhw INTERFACE) -target_link_libraries(libhw INTERFACE - libhw_generic -) - -if (PICO_PLATFORM STREQUAL "rp2040") - target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/rp2040_include) - target_link_libraries(libhw INTERFACE - libcr_ipc - ) - target_sources(libhw INTERFACE - rp2040_hwtimer.c - rp2040_hwspi.c - w5500.c - ) - target_link_libraries(libhw INTERFACE - hardware_gpio - hardware_irq - hardware_spi - hardware_timer - ) -endif() - -if (PICO_PLATFORM STREQUAL "host") - target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/host_include) - target_sources(libhw INTERFACE - host_util.c - host_alarmclock.c - host_net.c - ) -endif() diff --git a/libhw/rp2040_hwspi.c b/libhw/rp2040_hwspi.c deleted file mode 100644 index 47dfc97..0000000 --- a/libhw/rp2040_hwspi.c +++ /dev/null @@ -1,114 +0,0 @@ -/* libhw/rp2040_hwspi.c - <libhw/generic/spi.h> implementation for the RP2040's ARM Primecell SSP (PL022) - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <hardware/spi.h> /* pico-sdk:hardware_spi */ -#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */ - -#include <libmisc/assert.h> -#include <libmisc/vcall.h> - -#define IMPLEMENTATION_FOR_LIBHW_RP2040_HWSPI_H YES -#include <libhw/rp2040_hwspi.h> - -static void rp2040_hwspi_readwritev(implements_spi *, const struct bidi_iovec *iov, int iovcnt); - -struct spi_vtable rp2040_hwspi_vtable = { - .readwritev = rp2040_hwspi_readwritev, -}; - -void _rp2040_hwspi_init(struct rp2040_hwspi *self, - enum rp2040_hwspi_instance inst_num, - enum spi_mode mode, - uint baudrate_hz, - uint pin_miso, - uint pin_mosi, - uint pin_clk, - uint pin_cs) { - /* Be not weary: This is but 12 lines of actual code; and many - * lines of comments and assert()s. */ - spi_inst_t *inst; - - assert(self); - assert(baudrate_hz); - assert(pin_miso != pin_mosi); - assert(pin_miso != pin_clk); - assert(pin_miso != pin_cs); - assert(pin_mosi != pin_clk); - assert(pin_mosi != pin_cs); - assert(pin_clk != pin_cs); - - /* Regarding the constraints on pin assignments: see the - * RP2040 datasheet, table 2, in §1.4.3 "GPIO Functions". */ - switch (inst_num) { - case RP2040_HWSPI_0: - inst = spi0; - assert(pin_miso == 0 || pin_miso == 4 || pin_miso == 16 || pin_miso == 20); - /*assert(pin_cs == 1 || pin_cs == 5 || pin_cs == 17 || pin_cs == 21);*/ - assert(pin_clk == 2 || pin_clk == 6 || pin_clk == 18 || pin_clk == 22); - assert(pin_mosi == 3 || pin_mosi == 7 || pin_mosi == 19 || pin_mosi == 23); - break; - case RP2040_HWSPI_1: - inst = spi1; - assert(pin_miso == 8 || pin_miso == 12 || pin_miso == 24 || pin_miso == 28); - /*assert(pin_cs == 9 || pin_cs == 13 || pin_cs == 25 || pin_cs == 29);*/ - assert(pin_clk == 10 || pin_clk == 14 || pin_clk == 26); - assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27); - break; - default: - assert_notreached("invalid hwspi instance number"); - } - - spi_init(inst, baudrate_hz); - spi_set_format(inst, 8, - (mode & 0b10) ? SPI_CPOL_1 : SPI_CPOL_0, - (mode & 0b01) ? SPI_CPHA_1 : SPI_CPHA_0, - SPI_MSB_FIRST); - - /* Connect the pins to the PL022; set them each to "function - * 1" (again, see the RP2040 datasheet, table 2, in §1.4.3 - * "GPIO Functions"). - * - * ("GPIO_FUNC_SPI" is how the pico-sdk spells "function 1", - * since on the RP2040 all of the "function 1" functions are - * some part of SPI.) */ - gpio_set_function(pin_clk, GPIO_FUNC_SPI); - gpio_set_function(pin_mosi, GPIO_FUNC_SPI); - gpio_set_function(pin_miso, GPIO_FUNC_SPI); - - /* Initialize the CS pin for software control. */ - gpio_init(pin_cs); - gpio_set_dir(pin_cs, GPIO_OUT); - gpio_put(pin_cs, 1); - - /* Return. */ - self->vtable = &rp2040_hwspi_vtable; - self->inst = inst; - self->pin_cs = pin_cs; -} - -static void rp2040_hwspi_readwritev(implements_spi *_self, const struct bidi_iovec *iov, int iovcnt) { - struct rp2040_hwspi *self = VCALL_SELF(struct rp2040_hwspi, implements_spi, _self); - assert(self); - spi_inst_t *inst = self->inst; - - assert(inst); - assert(iov); - assert(iovcnt); - - gpio_put(self->pin_cs, 0); - /* TODO: Replace blocking reads+writes with DMA. */ - for (int i = 0; i < iovcnt; i++) { - if (iov[i].iov_write_src && iov[i].iov_read_dst) - spi_write_read_blocking(inst, iov[i].iov_write_src, iov[i].iov_read_dst, iov[i].iov_len); - else if (iov[i].iov_write_src) - spi_write_blocking(inst, iov[i].iov_write_src, iov[i].iov_len); - else if (iov[i].iov_read_dst) - spi_read_blocking(inst, 0, iov[i].iov_read_dst, iov[i].iov_len); - else - assert_notreached("bidi_iovec is neither read nor write"); - } - gpio_put(self->pin_cs, 1); -} diff --git a/libhw/rp2040_include/libhw/rp2040_hwspi.h b/libhw/rp2040_include/libhw/rp2040_hwspi.h deleted file mode 100644 index 7c4991b..0000000 --- a/libhw/rp2040_include/libhw/rp2040_hwspi.h +++ /dev/null @@ -1,78 +0,0 @@ -/* libhw/rp2040_hwspi.h - <libhw/generic/spi.h> implementation for the RP2040's ARM Primecell SSP (PL022) - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _LIBHW_RP2040_HWSPI_H_ -#define _LIBHW_RP2040_HWSPI_H_ - -#include <pico/binary_info.h> /* for bi_* */ - -#include <libmisc/private.h> - -#include <libhw/generic/spi.h> - -enum rp2040_hwspi_instance { - RP2040_HWSPI_0 = 0, - RP2040_HWSPI_1 = 1, -}; - -struct rp2040_hwspi { - implements_spi; - - BEGIN_PRIVATE(LIBHW_RP2040_HWSPI_H) - void /*spi_inst_t*/ *inst; - uint pin_cs; - END_PRIVATE(LIBHW_RP2040_HWSPI_H) -}; - -/** - * Initialize an instance of `struct rp2040_hwspi`. - * - * @param self : struct rp2040_hwspi : the structure to initialize - * @param name : char * : a name for the SPI port; to include in the bininfo - * @param inst_num : enum rp2040_hwspi_instance : the PL220 instance number; RP2040_HWSPI_{0,1} - * @param mode : enum spi_mode : the SPI mode; SPI_MODE_{0..3} - * @param pin_miso : uint : pin number; 0, 4, 16, or 20 for _HWSPI_0; 8, 12, 24, or 28 for _HWSPI_1 - * @param pin_mosi : uint : pin number; 3, 7, 19, or 23 for _HWSPI_0; 11, 15, or 27 for _HWSPI_1 - * @param pin_clk : uint : pin number; 2, 6, 18, or 22 for _HWSPI_0; 10, 14, or 26 for _HWSPI_1 - * @param pin_cs : uint : pin number; any unused GPIO pin - * - * There is no bit-order argument; the RP2040's hardware SPI always - * uses MSB-first bit order. - * - * I know we called this "hwspi", but we're actually going to - * disconnect the CS pin from the PL022 SSP and manually GPIO it from - * the CPU. This is because the PL022 has a maximum of 16-bit frames, - * but we need to be able to do *at least* 32-bit frames (and ideally, - * much larger). By managing it ourselves, we can just keep CS pulled - * low extra-long, making the frame extra-long. However, this means - * that we can't SPI so fast that the CPU can't do things in time; - * experimentally much faster than 60MHz seems to be when I start - * getting mangled messages. We wouldn't have this speed limit with a - * PIO-based SPI driver, because it could toggle CLK and CS in - * lock-step with receiving data from the FIFO. - */ -#define rp2040_hwspi_init(self, name, \ - inst_num, mode, baudrate_hz, \ - pin_miso, pin_mosi, pin_clk, pin_cs) \ - do { \ - bi_decl(bi_4pins_with_names(pin_miso, name" SPI MISO", \ - pin_mosi, name" SPI MOSI", \ - pin_mosi, name" SPI CLK", \ - pin_mosi, name" SPI CS")); \ - _rp2040_hwspi_init(self, \ - inst_num, mode, baudrate_hz, \ - pin_miso, pin_mosi, pin_clk, pin_cs); \ - } while(0) -void _rp2040_hwspi_init(struct rp2040_hwspi *self, - enum rp2040_hwspi_instance inst_num, - enum spi_mode mode, - uint baudrate_hz, - uint pin_miso, - uint pin_mosi, - uint pin_clk, - uint pin_cs); - -#endif /* _LIBHW_RP2040_HWSPI_H_ */ diff --git a/libhw_cr/CMakeLists.txt b/libhw_cr/CMakeLists.txt new file mode 100644 index 0000000..ba20b26 --- /dev/null +++ b/libhw_cr/CMakeLists.txt @@ -0,0 +1,43 @@ +# libhw_cr/CMakeLists.txt - Device drivers for libcr +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +add_library(libhw_cr INTERFACE) +target_link_libraries(libhw_cr INTERFACE + libhw_generic + libcr +) + +target_sources(libhw_cr INTERFACE + alarmclock.c +) + +if (PICO_PLATFORM STREQUAL "rp2040") + target_include_directories(libhw_cr PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/rp2040_include) + target_link_libraries(libhw_cr INTERFACE + libcr_ipc + ) + target_sources(libhw_cr INTERFACE + rp2040_dma.c + rp2040_gpioirq.c + rp2040_hwspi.c + rp2040_hwtimer.c + w5500.c + ) + target_link_libraries(libhw_cr INTERFACE + hardware_gpio + hardware_irq + hardware_spi + hardware_timer + ) +endif() + +if (PICO_PLATFORM STREQUAL "host") + target_include_directories(libhw_cr PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/host_include) + target_sources(libhw_cr INTERFACE + host_util.c + host_alarmclock.c + host_net.c + ) +endif() diff --git a/libhw_cr/alarmclock.c b/libhw_cr/alarmclock.c new file mode 100644 index 0000000..6eec52b --- /dev/null +++ b/libhw_cr/alarmclock.c @@ -0,0 +1,23 @@ +/* libhw_cr/alarmclock.c - sleep() implementation for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> + +#include <libhw/generic/alarmclock.h> + +static void alarmclock_sleep_intrhandler(void *_arg) { + cid_t cid = *(cid_t *)_arg; + cr_unpause_from_intrhandler(cid); +} + +void alarmclock_sleep_until_ns(lo_interface alarmclock clock, uint64_t abstime_ns) { + bool saved = cr_save_and_disable_interrupts(); + cid_t cid = cr_getcid(); + struct alarmclock_trigger trigger; + LO_CALL(clock, add_trigger, &trigger, abstime_ns, alarmclock_sleep_intrhandler, &cid); + cr_pause_and_yield(); + cr_restore_interrupts(saved); +} diff --git a/libhw/host_alarmclock.c b/libhw_cr/host_alarmclock.c index 8b9bdcf..9eedec2 100644 --- a/libhw/host_alarmclock.c +++ b/libhw_cr/host_alarmclock.c @@ -1,64 +1,27 @@ -/* libhw/host_alarmclock.c - <libhw/generic/alarmclock.h> implementation for POSIX hosts +/* libhw_cr/host_alarmclock.c - <libhw/generic/alarmclock.h> implementation for POSIX hosts * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #include <errno.h> #include <error.h> #include <signal.h> -#include <time.h> #include <libcr/coroutine.h> #include <libmisc/assert.h> -#include <libmisc/vcall.h> #define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES #include <libhw/generic/alarmclock.h> -#include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_ns_time() */ - -/* Types **********************************************************************/ - -struct hostclock { - implements_alarmclock; - bool initialized; - clockid_t clock_id; - timer_t timer_id; - struct alarmclock_trigger *queue; -}; - -/* Globals ********************************************************************/ - -static uint64_t hostclock_get_time_ns(implements_alarmclock *self); -static bool hostclock_add_trigger(implements_alarmclock *self, - struct alarmclock_trigger *trigger, - uint64_t fire_at_ns, - void (*cb)(void *), - void *cb_arg); -static void hostclock_del_trigger(implements_alarmclock *self, - struct alarmclock_trigger *trigger); +#define IMPLEMENTATION_FOR_LIBHW_HOST_ALARMCLOCK_H YES +#include <libhw/host_alarmclock.h> -static struct alarmclock_vtable hostclock_vtable = { - .get_time_ns = hostclock_get_time_ns, - .add_trigger = hostclock_add_trigger, - .del_trigger = hostclock_del_trigger, -}; - -static struct hostclock clock_monotonic = { - .vtable = &hostclock_vtable, - .clock_id = CLOCK_MONOTONIC, -}; - -implements_alarmclock *bootclock = &clock_monotonic; - -/* Main implementation ********************************************************/ +#include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_ns_time() */ -#define UNUSED(name) +LO_IMPLEMENTATION_C(alarmclock, struct hostclock, hostclock, static); -static uint64_t hostclock_get_time_ns(implements_alarmclock *_alarmclock) { - struct hostclock *alarmclock = - VCALL_SELF(struct hostclock, implements_alarmclock, _alarmclock); +static uint64_t hostclock_get_time_ns(struct hostclock *alarmclock) { assert(alarmclock); struct timespec ts; @@ -69,7 +32,7 @@ static uint64_t hostclock_get_time_ns(implements_alarmclock *_alarmclock) { return ns_from_host_ns_time(ts); } -static void hostclock_handle_sig_alarm(int UNUSED(sig), siginfo_t *info, void *UNUSED(ucontext)) { +static void hostclock_handle_sig_alarm(int LM_UNUSED(sig), siginfo_t *info, void *LM_UNUSED(ucontext)) { struct hostclock *alarmclock = info->si_value.sival_ptr; assert(alarmclock); @@ -93,13 +56,11 @@ static void hostclock_handle_sig_alarm(int UNUSED(sig), siginfo_t *info, void *U } } -static bool hostclock_add_trigger(implements_alarmclock *_alarmclock, - struct alarmclock_trigger *trigger, - uint64_t fire_at_ns, - void (*cb)(void *), - void *cb_arg) { - struct hostclock *alarmclock = - VCALL_SELF(struct hostclock, implements_alarmclock, _alarmclock); +static bool hostclock_add_trigger(struct hostclock *alarmclock, + struct alarmclock_trigger *trigger, + uint64_t fire_at_ns, + void (*cb)(void *), + void *cb_arg) { assert(alarmclock); assert(trigger); assert(fire_at_ns); @@ -150,11 +111,8 @@ static bool hostclock_add_trigger(implements_alarmclock *_alarmclock, return false; } -static void hostclock_del_trigger(implements_alarmclock *_alarmclock, - struct alarmclock_trigger *trigger) { - struct hostclock *alarmclock = - VCALL_SELF(struct hostclock, implements_alarmclock, _alarmclock); - +static void hostclock_del_trigger(struct hostclock *alarmclock, + struct alarmclock_trigger *trigger) { assert(alarmclock); assert(trigger); diff --git a/libhw_cr/host_include/libhw/host_alarmclock.h b/libhw_cr/host_include/libhw/host_alarmclock.h new file mode 100644 index 0000000..0cb8d30 --- /dev/null +++ b/libhw_cr/host_include/libhw/host_alarmclock.h @@ -0,0 +1,27 @@ +/* libhw/host_alarmclock.h - <libhw/generic/alarmclock.h> implementation for hosted glibc + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_HOST_ALARMCLOCK_H_ +#define _LIBHW_HOST_ALARMCLOCK_H_ + +#include <stdbool.h> /* for bool */ +#include <time.h> /* for clockid_t, timer_t */ + +#include <libmisc/private.h> +#include <libhw/generic/alarmclock.h> + +struct hostclock { + clockid_t clock_id; + + BEGIN_PRIVATE(LIBHW_HOST_ALARMCLOCK_H); + bool initialized; + timer_t timer_id; + struct alarmclock_trigger *queue; + END_PRIVATE(LIBHW_HOST_ALARMCLOCK_H); +}; +LO_IMPLEMENTATION_H(alarmclock, struct hostclock, hostclock); + +#endif /* _LIBHW_HOST_ALARMCLOCK_H_ */ diff --git a/libhw/host_include/libhw/host_net.h b/libhw_cr/host_include/libhw/host_net.h index bfef5c9..a16ed01 100644 --- a/libhw/host_include/libhw/host_net.h +++ b/libhw_cr/host_include/libhw/host_net.h @@ -1,6 +1,6 @@ /* libhw/host_net.h - <libhw/generic/net.h> implementation for hosted glibc * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -14,33 +14,30 @@ #include <libhw/generic/net.h> struct _hostnet_tcp_conn { - implements_net_stream_conn; - - BEGIN_PRIVATE(LIBHW_HOST_NET_H) + BEGIN_PRIVATE(LIBHW_HOST_NET_H); int fd; uint64_t read_deadline_ns; - END_PRIVATE(LIBHW_HOST_NET_H) + END_PRIVATE(LIBHW_HOST_NET_H); }; +LO_IMPLEMENTATION_H(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp); struct hostnet_tcp_listener { - implements_net_stream_listener; - - BEGIN_PRIVATE(LIBHW_HOST_NET_H) + BEGIN_PRIVATE(LIBHW_HOST_NET_H); int fd; struct _hostnet_tcp_conn active_conn; - END_PRIVATE(LIBHW_HOST_NET_H) + END_PRIVATE(LIBHW_HOST_NET_H); }; +LO_IMPLEMENTATION_H(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist); void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port); struct hostnet_udp_conn { - implements_net_packet_conn; - - BEGIN_PRIVATE(LIBHW_HOST_NET_H) + BEGIN_PRIVATE(LIBHW_HOST_NET_H); int fd; uint64_t read_deadline_ns; - END_PRIVATE(LIBHW_HOST_NET_H) + END_PRIVATE(LIBHW_HOST_NET_H); }; +LO_IMPLEMENTATION_H(net_packet_conn, struct hostnet_udp_conn, hostnet_udp); void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port); diff --git a/libhw/host_net.c b/libhw_cr/host_net.c index 2ba8b23..6ed6e46 100644 --- a/libhw/host_net.c +++ b/libhw_cr/host_net.c @@ -1,6 +1,6 @@ -/* libhw/host_net.c - <libhw/generic/net.h> implementation for hosted glibc +/* libhw_cr/host_net.c - <libhw/generic/net.h> implementation for hosted glibc * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -20,7 +20,8 @@ #include <libcr/coroutine.h> #include <libmisc/assert.h> -#include <libmisc/vcall.h> +#include <libmisc/macro.h> +#include <libobj/obj.h> #include <libhw/generic/alarmclock.h> @@ -29,13 +30,24 @@ #include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_us_time() */ -/* common *********************************************************************/ +LO_IMPLEMENTATION_C(io_closer, struct hostnet_tcp_listener, hostnet_tcplist, static); +LO_IMPLEMENTATION_C(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist, static); + +LO_IMPLEMENTATION_C(io_reader, struct _hostnet_tcp_conn, hostnet_tcp, static); +LO_IMPLEMENTATION_C(io_writer, struct _hostnet_tcp_conn, hostnet_tcp, static); +LO_IMPLEMENTATION_C(io_readwriter, struct _hostnet_tcp_conn, hostnet_tcp, static); +LO_IMPLEMENTATION_C(io_closer, struct _hostnet_tcp_conn, hostnet_tcp, static); +LO_IMPLEMENTATION_C(io_bidi_closer, struct _hostnet_tcp_conn, hostnet_tcp, static); +LO_IMPLEMENTATION_C(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp, static); -#define UNUSED(name) +LO_IMPLEMENTATION_C(io_closer, struct hostnet_udp_conn, hostnet_udp, static); +LO_IMPLEMENTATION_C(net_packet_conn, struct hostnet_udp_conn, hostnet_udp, static); + +/* common *********************************************************************/ static int hostnet_sig_io = 0; -static void hostnet_handle_sig_io(int UNUSED(sig), siginfo_t *info, void *UNUSED(ucontext)) { +static void hostnet_handle_sig_io(int LM_UNUSED(sig), siginfo_t *info, void *LM_UNUSED(ucontext)) { cr_unpause_from_intrhandler((cid_t)info->si_value.sival_int); } @@ -67,9 +79,11 @@ static void hostnet_init(void) { static inline bool RUN_PTHREAD(void *(*fn)(void *), void *args) { pthread_t thread; + bool saved = cr_save_and_disable_interrupts(); if (pthread_create(&thread, NULL, fn, args)) return true; cr_pause_and_yield(); + cr_restore_interrupts(saved); if (pthread_join(thread, NULL)) return true; return false; @@ -108,25 +122,6 @@ static inline ssize_t hostnet_map_negerrno(ssize_t v, enum hostnet_timeout_op op /* TCP init() ( AKA socket(3) + listen(3) )************************************/ -static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_listener *); -static int hostnet_tcplist_close(implements_net_stream_listener *); -static void hostnet_tcp_set_read_deadline(implements_net_stream_conn *conn, uint64_t ts_ns); -static ssize_t hostnet_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count); -static ssize_t hostnet_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count); -static int hostnet_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr); - -static struct net_stream_listener_vtable hostnet_tcp_listener_vtable = { - .accept = hostnet_tcplist_accept, - .close = hostnet_tcplist_close, -}; - -static struct net_stream_conn_vtable hostnet_tcp_conn_vtable = { - .set_read_deadline = hostnet_tcp_set_read_deadline, - .read = hostnet_tcp_read, - .write = hostnet_tcp_write, - .close = hostnet_tcp_close, -}; - void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) { int listenerfd; union { @@ -150,7 +145,6 @@ void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) if (listen(listenerfd, 0) < 0) error(1, errno, "listen(fd=%d)", listenerfd); - self->vtable = &hostnet_tcp_listener_vtable; self->fd = listenerfd; } @@ -174,9 +168,7 @@ static void *hostnet_pthread_accept(void *_args) { return NULL; } -static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_listener *_listener) { - struct hostnet_tcp_listener *listener = - VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener); +static lo_interface net_stream_conn hostnet_tcplist_accept(struct hostnet_tcp_listener *listener) { assert(listener); int ret_connfd; @@ -187,55 +179,53 @@ static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_ .ret_connfd = &ret_connfd, }; if (RUN_PTHREAD(hostnet_pthread_accept, &args)) - return NULL; + return LO_NULL(net_stream_conn); + + if (ret_connfd < 0) + return LO_NULL(net_stream_conn); - listener->active_conn.vtable = &hostnet_tcp_conn_vtable; listener->active_conn.fd = ret_connfd; listener->active_conn.read_deadline_ns = 0; - return &listener->active_conn; + return lo_box_hostnet_tcp_as_net_stream_conn(&listener->active_conn); } /* TCP listener close() *******************************************************/ -static int hostnet_tcplist_close(implements_net_stream_listener *_listener) { - struct hostnet_tcp_listener *listener = - VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener); +static int hostnet_tcplist_close(struct hostnet_tcp_listener *listener) { assert(listener); - return hostnet_map_negerrno(close(listener->fd) ? -errno : 0, OP_NONE); + return hostnet_map_negerrno(shutdown(listener->fd, SHUT_RDWR) ? -errno : 0, OP_NONE); } /* TCP read() *****************************************************************/ -static void hostnet_tcp_set_read_deadline(implements_net_stream_conn *_conn, uint64_t ts_ns) { - struct _hostnet_tcp_conn *conn = - VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn); +static void hostnet_tcp_set_read_deadline(struct _hostnet_tcp_conn *conn, uint64_t ts_ns) { assert(conn); conn->read_deadline_ns = ts_ns; } -struct hostnet_pthread_read_args { - pthread_t cr_thread; - cid_t cr_coroutine; +struct hostnet_pthread_readv_args { + pthread_t cr_thread; + cid_t cr_coroutine; - int connfd; - struct timeval timeout; - void *buf; - size_t count; + int connfd; + struct timeval timeout; + const struct iovec *iov; + int iovcnt; - ssize_t *ret; + ssize_t *ret; }; -static void *hostnet_pthread_read(void *_args) { - struct hostnet_pthread_read_args *args = _args; +static void *hostnet_pthread_readv(void *_args) { + struct hostnet_pthread_readv_args *args = _args; *(args->ret) = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, &args->timeout, sizeof(args->timeout)); if (*(args->ret) < 0) goto end; - *(args->ret) = read(args->connfd, args->buf, args->count); + *(args->ret) = readv(args->connfd, args->iov, args->iovcnt); if (*(args->ret) < 0) goto end; @@ -246,24 +236,24 @@ static void *hostnet_pthread_read(void *_args) { return NULL; } -static ssize_t hostnet_tcp_read(implements_net_stream_conn *_conn, void *buf, size_t count) { - struct _hostnet_tcp_conn *conn = - VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn); +static ssize_t hostnet_tcp_readv(struct _hostnet_tcp_conn *conn, const struct iovec *iov, int iovcnt) { assert(conn); + assert(iov); + assert(iovcnt > 0); ssize_t ret; - struct hostnet_pthread_read_args args = { + struct hostnet_pthread_readv_args args = { .cr_thread = pthread_self(), .cr_coroutine = cr_getcid(), .connfd = conn->fd, - .buf = buf, - .count = count, + .iov = iov, + .iovcnt = iovcnt, .ret = &ret, }; if (conn->read_deadline_ns) { - uint64_t now_ns = VCALL(bootclock, get_time_ns); + uint64_t now_ns = LO_CALL(bootclock, get_time_ns); if (conn->read_deadline_ns < now_ns) return -NET_ERECV_TIMEOUT; args.timeout = ns_to_host_us_time(conn->read_deadline_ns-now_ns); @@ -271,98 +261,97 @@ static ssize_t hostnet_tcp_read(implements_net_stream_conn *_conn, void *buf, si args.timeout = (host_us_time_t){0}; } - if (RUN_PTHREAD(hostnet_pthread_read, &args)) + if (RUN_PTHREAD(hostnet_pthread_readv, &args)) return -NET_ETHREAD; return ret; } /* TCP write() ****************************************************************/ -struct hostnet_pthread_write_args { - pthread_t cr_thread; - cid_t cr_coroutine; +struct hostnet_pthread_writev_args { + pthread_t cr_thread; + cid_t cr_coroutine; - int connfd; - void *buf; - size_t count; + int connfd; + const struct iovec *iov; + int iovcnt; - ssize_t *ret; + ssize_t *ret; }; -static void *hostnet_pthread_write(void *_args) { - struct hostnet_pthread_write_args *args = _args; +static void *hostnet_pthread_writev(void *_args) { + struct hostnet_pthread_writev_args *args = _args; + + size_t count = 0; + struct iovec *iov = alloca(sizeof(struct iovec)*args->iovcnt); + for (int i = 0; i < args->iovcnt; i++) { + iov[i] = args->iov[i]; + count += args->iov[i].iov_len; + } + int iovcnt = args->iovcnt; + size_t done = 0; - while (done < args->count) { - ssize_t r = write(args->connfd, args->buf, args->count); + while (done < count) { + ssize_t r = writev(args->connfd, iov, iovcnt); if (r < 0) { hostnet_map_negerrno(-errno, OP_RECV); break; } done += r; + while (iovcnt && (size_t)r >= iov[0].iov_len) { + r -= iov[0].iov_len; + iov++; + iovcnt--; + } + if (r > 0) { + iov[0].iov_base += r; + iov[0].iov_len -= r; + } } - if (done == args->count) + if (done == count) *(args->ret) = done; WAKE_COROUTINE(args); return NULL; } -static ssize_t hostnet_tcp_write(implements_net_stream_conn *_conn, void *buf, size_t count) { - struct _hostnet_tcp_conn *conn = - VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn); +static ssize_t hostnet_tcp_writev(struct _hostnet_tcp_conn *conn, const struct iovec *iov, int iovcnt) { assert(conn); + assert(iov); + assert(iovcnt > 0); ssize_t ret; - struct hostnet_pthread_write_args args = { + struct hostnet_pthread_writev_args args = { .cr_thread = pthread_self(), .cr_coroutine = cr_getcid(), .connfd = conn->fd, - .buf = buf, - .count = count, + .iov = iov, + .iovcnt = iovcnt, .ret = &ret, }; - if (RUN_PTHREAD(hostnet_pthread_write, &args)) + if (RUN_PTHREAD(hostnet_pthread_writev, &args)) return -NET_ETHREAD; return ret; } /* TCP close() ****************************************************************/ -static int hostnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr) { - struct _hostnet_tcp_conn *conn = - VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn); +static int hostnet_tcp_close(struct _hostnet_tcp_conn *conn) { assert(conn); - - int how; - if (rd && wr) - how = SHUT_RDWR; - else if (rd && !wr) - how = SHUT_RD; - else if (!rd && wr) - how = SHUT_WR; - else - assert_notreached("invalid arguments to stream_conn.close()"); - return hostnet_map_negerrno(shutdown(conn->fd, how) ? -errno : 0, OP_NONE); + return hostnet_map_negerrno(shutdown(conn->fd, SHUT_RDWR) ? -errno : 0, OP_NONE); +} +static int hostnet_tcp_close_read(struct _hostnet_tcp_conn *conn) { + assert(conn); + return hostnet_map_negerrno(shutdown(conn->fd, SHUT_RD) ? -errno : 0, OP_NONE); +} +static int hostnet_tcp_close_write(struct _hostnet_tcp_conn *conn) { + assert(conn); + return hostnet_map_negerrno(shutdown(conn->fd, SHUT_WR) ? -errno : 0, OP_NONE); } /* UDP init() *****************************************************************/ -static void hostnet_udp_set_read_deadline(implements_net_packet_conn *self, - uint64_t ts_ns); -static ssize_t hostnet_udp_sendto(implements_net_packet_conn *self, void *buf, size_t len, - struct net_ip4_addr addr, uint16_t port); -static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *self, void *buf, size_t len, - struct net_ip4_addr *ret_addr, uint16_t *ret_port); -static int hostnet_udp_close(implements_net_packet_conn *self); - -static struct net_packet_conn_vtable hostnet_udp_conn_vtable = { - .set_read_deadline = hostnet_udp_set_read_deadline, - .sendto = hostnet_udp_sendto, - .recvfrom = hostnet_udp_recvfrom, - .close = hostnet_udp_close, -}; - void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) { int fd; union { @@ -381,7 +370,6 @@ void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) { if (bind(fd, &addr.gen, sizeof addr) < 0) error(1, errno, "bind"); - self->vtable = &hostnet_udp_conn_vtable; self->fd = fd; self->read_deadline_ns = 0; } @@ -423,10 +411,8 @@ static void *hostnet_pthread_sendto(void *_args) { return NULL; } -static ssize_t hostnet_udp_sendto(implements_net_packet_conn *_conn, void *buf, size_t count, +static ssize_t hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size_t count, struct net_ip4_addr node, uint16_t port) { - struct hostnet_udp_conn *conn = - VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn); assert(conn); ssize_t ret; @@ -449,10 +435,8 @@ static ssize_t hostnet_udp_sendto(implements_net_packet_conn *_conn, void *buf, /* UDP recvfrom() *************************************************************/ -static void hostnet_udp_set_read_deadline(implements_net_packet_conn *_conn, +static void hostnet_udp_set_recv_deadline(struct hostnet_udp_conn *conn, uint64_t ts_ns) { - struct hostnet_udp_conn *conn = - VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn); assert(conn); conn->read_deadline_ns = ts_ns; @@ -510,10 +494,8 @@ static void *hostnet_pthread_recvfrom(void *_args) { return NULL; } -static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf, size_t count, +static ssize_t hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void *buf, size_t count, struct net_ip4_addr *ret_node, uint16_t *ret_port) { - struct hostnet_udp_conn *conn = - VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn); assert(conn); ssize_t ret; @@ -530,7 +512,7 @@ static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf .ret_port = ret_port, }; if (conn->read_deadline_ns) { - uint64_t now_ns = VCALL(bootclock, get_time_ns); + uint64_t now_ns = LO_CALL(bootclock, get_time_ns); if (conn->read_deadline_ns < now_ns) return -NET_ERECV_TIMEOUT; args.timeout = ns_to_host_us_time(conn->read_deadline_ns-now_ns); @@ -545,9 +527,7 @@ static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf /* UDP close() ****************************************************************/ -static int hostnet_udp_close(implements_net_packet_conn *_conn) { - struct hostnet_udp_conn *conn = - VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn); +static int hostnet_udp_close(struct hostnet_udp_conn *conn) { assert(conn); return hostnet_map_negerrno(close(conn->fd) ? -errno : 0, OP_NONE); diff --git a/libhw/host_util.c b/libhw_cr/host_util.c index b862e39..7b3200c 100644 --- a/libhw/host_util.c +++ b/libhw_cr/host_util.c @@ -1,6 +1,6 @@ -/* libhw/host_util.c - Utilities for GNU/Linux hosts +/* libhw_cr/host_util.c - Utilities for GNU/Linux hosts * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ diff --git a/libhw/host_util.h b/libhw_cr/host_util.h index f3ef6b4..02c04dc 100644 --- a/libhw/host_util.h +++ b/libhw_cr/host_util.h @@ -1,11 +1,11 @@ -/* libhw/host_util.h - Utilities for GNU/Linux hosts +/* libhw_cr/host_util.h - Utilities for GNU/Linux hosts * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#ifndef _LIBHW_HOST_UTIL_H_ -#define _LIBHW_HOST_UTIL_H_ +#ifndef _LIBHW_CR_HOST_UTIL_H_ +#define _LIBHW_CR_HOST_UTIL_H_ #include <time.h> /* for struct timespec */ #include <sys/time.h> /* for struct timeval */ @@ -44,4 +44,4 @@ static inline uint64_t ns_from_host_ns_time(host_ns_time_t host_time) { ((uint64_t)host_time.tv_nsec); } -#endif /* _LIBHW_HOST_UTIL_H_ */ +#endif /* _LIBHW_CR_HOST_UTIL_H_ */ diff --git a/libhw_cr/rp2040_dma.c b/libhw_cr/rp2040_dma.c new file mode 100644 index 0000000..69116bf --- /dev/null +++ b/libhw_cr/rp2040_dma.c @@ -0,0 +1,56 @@ +/* libhw_cr/rp2040_dma.c - Utilities for sharing the DMA IRQs + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdbool.h> + +#include <hardware/irq.h> /* for irq_set_exclusive_handler() */ + +#include "rp2040_dma.h" + +struct dmairq_handler_entry { + dmairq_handler_t fn; + void *arg; +}; +struct dmairq_handler_entry dmairq_handlers[NUM_DMA_CHANNELS] = {0}; + +bool dmairq_initialized[NUM_DMA_IRQS] = {0}; + +static void dmairq_handler(void) { + enum dmairq irq = __get_current_exception() - VTABLE_FIRST_IRQ; + size_t irq_idx = irq - DMAIRQ_0; + assert(irq_idx < NUM_DMA_IRQS); + + uint32_t regval = dma_hw->irq_ctrl[irq_idx].ints; + for (uint channel = 0; channel < NUM_DMA_CHANNELS; channel++) { + if (regval & 1u<<channel) { + struct dmairq_handler_entry *handler = &dmairq_handlers[channel]; + if (handler->fn) + handler->fn(handler->arg, irq, channel); + } + } + /* acknowledge irq */ + dma_hw->intr = regval; +} + +void dmairq_set_and_enable_exclusive_handler(enum dmairq irq, uint channel, dmairq_handler_t fn, void *arg) { + assert(irq == DMAIRQ_0 || irq == DMAIRQ_1); + assert(channel < NUM_DMA_CHANNELS); + assert(fn); + + assert(dmairq_handlers[channel].fn == NULL); + + dmairq_handlers[channel].fn = fn; + dmairq_handlers[channel].arg = arg; + + size_t irq_idx = irq - DMAIRQ_0; + hw_set_bits(&dma_hw->irq_ctrl[irq_idx].inte, 1u<<channel); + + if (!dmairq_initialized[irq_idx]) { + irq_set_exclusive_handler(irq, dmairq_handler); + irq_set_enabled(irq, true); + dmairq_initialized[irq_idx] = true; + } +} diff --git a/libhw_cr/rp2040_dma.h b/libhw_cr/rp2040_dma.h new file mode 100644 index 0000000..c7f5a8f --- /dev/null +++ b/libhw_cr/rp2040_dma.h @@ -0,0 +1,131 @@ +/* libhw_cr/rp2040_dma.h - Utilities for using DMA on the RP2040 (replaces <hardware/dma.h>) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_CR_RP2040_DMA_H_ +#define _LIBHW_CR_RP2040_DMA_H_ + +#include <assert.h> +#include <stddef.h> /* for offsetof() */ +#include <stdint.h> /* for uint32_t */ + +#include <hardware/regs/dreq.h> /* for DREQ_* for use with DMA_CTRL_TREQ_SEL() */ +#include <hardware/structs/dma.h> /* for dma_hw, dma_channel_hw_t, DMA_NUM_CHANNELS */ + +#include <libmisc/macro.h> /* for LM_FLOORLOG2() */ + +/* Borrowed from <hardware/dma.h> *********************************************/ + +static inline dma_channel_hw_t *dma_channel_hw_addr(uint channel) { + assert(channel < NUM_DMA_CHANNELS); + return &dma_hw->ch[channel]; +} + +enum dma_channel_transfer_size { + DMA_SIZE_8 = 0, ///< Byte transfer (8 bits) + DMA_SIZE_16 = 1, ///< Half word transfer (16 bits) + DMA_SIZE_32 = 2 ///< Word transfer (32 bits) +}; + +/* Our own code ***************************************************************/ + +enum dmairq { + DMAIRQ_0 = DMA_IRQ_0, + DMAIRQ_1 = DMA_IRQ_1, +}; + +typedef void (*dmairq_handler_t)(void *arg, enum dmairq irq, uint channel); + +/** + * Register `fn(arg, ...)` to be called when `channel` completes or + * has a NULL trigger (depending on the channel's configuration). + * + * Your handler does not need to acknowledge the IRQ; that will be + * done for you after your handler is called. + * + * It is illegal to enable the same channel on more than one IRQ. + */ +void dmairq_set_and_enable_exclusive_handler(enum dmairq irq, uint channel, dmairq_handler_t fn, void *arg); + +#define DMA_CTRL_ENABLE (1<<0) +#define DMA_CTRL_HI_PRIO (1<<1) +#define DMA_CTRL_DATA_SIZE(sz) ((sz)<<2) +#define DMA_CTRL_INCR_READ (1<<4) +#define DMA_CTRL_INCR_WRITE (1<<5) +#define _DMA_CTRL_RING_BITS(b) ((b)<<6) +#define _DMA_CTRL_RING_RD (0) +#define _DMA_CTRL_RING_WR (1<<10) +#define DMA_CTRL_RING(rdwr, bits) (_DMA_CTRL_RING_##rdwr | _DMA_CTRL_RING_BITS(bits)) +#define DMA_CTRL_CHAIN_TO(ch) ((ch)<<11) +#define DMA_CTRL_TREQ_SEL(dreq) ((dreq)<<15) +#define DMA_CTRL_IRQ_QUIET (1<<21) +#define DMA_CTRL_BSWAP (1<<22) +#define DMA_CTRL_SNIFF_EN (1<<23) + +/* | elem | val | name */ +#define READ_ADDR /*|*/volatile const void/*|*/ * /*|*/read_addr +#define WRITE_ADDR /*|*/volatile void/*|*/ * /*|*/write_addr +#define TRANS_COUNT /*|*/ /*|*/uint32_t/*|*/trans_count +#define CTRL /*|*/ /*|*/uint32_t/*|*/ctrl + +/* { +0x0 ; +0x4 ; +0x8 ; +0xC (Trigger) */ +struct dma_alias0 { READ_ADDR ; WRITE_ADDR ; TRANS_COUNT ; CTRL ; }; +struct dma_alias1 { CTRL ; READ_ADDR ; WRITE_ADDR ; TRANS_COUNT ; }; +struct dma_alias2 { CTRL ; TRANS_COUNT ; READ_ADDR ; WRITE_ADDR ; }; +struct dma_alias3 { CTRL ; WRITE_ADDR ; TRANS_COUNT ; READ_ADDR ; }; +struct dma_alias0_short2 { TRANS_COUNT ; CTRL ; }; +struct dma_alias1_short2 { WRITE_ADDR ; TRANS_COUNT ; }; +struct dma_alias2_short2 { READ_ADDR ; WRITE_ADDR ; }; +struct dma_alias3_short2 { TRANS_COUNT ; READ_ADDR ; }; +struct dma_alias0_short3 { CTRL ; }; +struct dma_alias1_short3 { TRANS_COUNT ; }; +struct dma_alias2_short3 { WRITE_ADDR ; }; +struct dma_alias3_short3 { READ_ADDR ; }; + +#undef CTRL +#undef TRANS_COUNT +#undef WRITE_ADDR +#undef READ_ADDR + +#define DMA_CHAN_ADDR(CH, TYP) ((TYP *volatile)_Generic((TYP){}, \ + struct dma_alias0: &dma_channel_hw_addr(CH)->read_addr, \ + struct dma_alias1: &dma_channel_hw_addr(CH)->al1_ctrl, \ + struct dma_alias2: &dma_channel_hw_addr(CH)->al2_ctrl, \ + struct dma_alias3: &dma_channel_hw_addr(CH)->al3_ctrl, \ + struct dma_alias0_short2: &dma_channel_hw_addr(CH)->transfer_count, \ + struct dma_alias1_short2: &dma_channel_hw_addr(CH)->al1_write_addr, \ + struct dma_alias2_short2: &dma_channel_hw_addr(CH)->al2_read_addr, \ + struct dma_alias3_short2: &dma_channel_hw_addr(CH)->al3_transfer_count, \ + struct dma_alias0_short3: &dma_channel_hw_addr(CH)->ctrl_trig, \ + struct dma_alias1_short3: &dma_channel_hw_addr(CH)->al1_transfer_count_trig, \ + struct dma_alias2_short3: &dma_channel_hw_addr(CH)->al2_write_addr_trig, \ + struct dma_alias3_short3: &dma_channel_hw_addr(CH)->al3_read_addr_trig)) + +#define DMA_IS_TRIGGER(TYP, FIELD) (offsetof(TYP, FIELD) == 0xC) + +#define DMA_CHAN_WR_TRANS_COUNT(TYP) \ + (sizeof(TYP)/4) + +#define DMA_CHAN_WR_CTRL(TYP) ( DMA_CTRL_DATA_SIZE(DMA_SIZE_32) \ + | DMA_CTRL_INCR_WRITE \ + | DMA_CTRL_RING(WR, LM_FLOORLOG2(sizeof(TYP))) \ + ) + +#define DMA_NONTRIGGER(CH, FIELD) (DMA_CHAN_ADDR(CH, _DMA_NONTRIGGER_##FIELD)->FIELD) +#define _DMA_NONTRIGGER_read_addr struct dma_alias0 +#define _DMA_NONTRIGGER_write_addr struct dma_alias0 +#define _DMA_NONTRIGGER_trans_count struct dma_alias0 +#define _DMA_NONTRIGGER_ctrl struct dma_alias1 + +#define DMA_TRIGGER(CH, FIELD) (DMA_CHAN_ADDR(CH, _DMA_TRIGGER_##FIELD)->FIELD) +#define _DMA_TRIGGER_read_addr struct dma_alias3 +#define _DMA_TRIGGER_write_addr struct dma_alias2 +#define _DMA_TRIGGER_trans_count struct dma_alias1 +#define _DMA_TRIGGER_ctrl struct dma_alias0 + +#endif /* _LIBHW_CR_RP2040_DMA_H_ */ diff --git a/libhw_cr/rp2040_gpioirq.c b/libhw_cr/rp2040_gpioirq.c new file mode 100644 index 0000000..1ae74f9 --- /dev/null +++ b/libhw_cr/rp2040_gpioirq.c @@ -0,0 +1,75 @@ +/* libhw_cr/rp2040_gpioirq.c - Utilities for sharing the GPIO IRQ (IO_IRQ_BANK0) + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <hardware/structs/io_bank0.h> /* for io_bank0_hw */ +#include <hardware/irq.h> /* for irq_set_exclusive_handler() */ + +#include <libmisc/macro.h> + +#include "rp2040_gpioirq.h" + +struct gpioirq_handler_entry { + gpioirq_handler_t fn; + void *arg; +}; +struct gpioirq_handler_entry gpioirq_handlers[NUM_BANK0_GPIOS][4] = {0}; + +int gpioirq_core = -1; + +static void gpioirq_handler(void) { + uint core = get_core_num(); + io_bank0_irq_ctrl_hw_t *irq_ctrl_base; + switch (core) { + case 0: irq_ctrl_base = &io_bank0_hw->proc0_irq_ctrl; break; + case 1: irq_ctrl_base = &io_bank0_hw->proc1_irq_ctrl; break; + default: assert_notreached("invalid core number"); + } + for (uint regnum = 0; regnum < LM_ARRAY_LEN(irq_ctrl_base->ints); regnum++) { + uint32_t regval = irq_ctrl_base->ints[regnum]; + for (uint bit = 0; bit < 32 && (regnum*8)+(bit/4) < NUM_BANK0_GPIOS; bit++) { + if (regval & 1u<<bit) { + uint gpio = (regnum*8)+(bit/4); + uint event_idx = bit%4; + struct gpioirq_handler_entry *handler = &gpioirq_handlers[gpio][event_idx]; + if (handler->fn) + handler->fn(handler->arg, gpio, 1u<<event_idx); + } + } + /* acknowledge irq */ + io_bank0_hw->intr[regnum] = regval; + } +} + +void gpioirq_set_and_enable_exclusive_handler(uint gpio, enum gpio_irq_level event, gpioirq_handler_t fn, void *arg) { + assert(gpio < NUM_BANK0_GPIOS); + assert(event == GPIO_IRQ_LEVEL_LOW || + event == GPIO_IRQ_LEVEL_HIGH || + event == GPIO_IRQ_EDGE_FALL || + event == GPIO_IRQ_EDGE_RISE); + assert(fn); + + uint event_idx = LM_FLOORLOG2(event); + assert(gpioirq_handlers[gpio][event_idx].fn == NULL); + + uint core = get_core_num(); + assert(gpioirq_core == -1 || gpioirq_core == (int)core); + + io_bank0_irq_ctrl_hw_t *irq_ctrl_base; + switch (core) { + case 0: irq_ctrl_base = &io_bank0_hw->proc0_irq_ctrl; break; + case 1: irq_ctrl_base = &io_bank0_hw->proc1_irq_ctrl; break; + default: assert_notreached("invalid core number"); + } + + gpioirq_handlers[gpio][event_idx].fn = fn; + gpioirq_handlers[gpio][event_idx].arg = arg; + hw_set_bits(&irq_ctrl_base->inte[gpio/8], 1u<<((4*(gpio%8))+event_idx)); + if (gpioirq_core == -1) { + irq_set_exclusive_handler(IO_IRQ_BANK0, gpioirq_handler); + irq_set_enabled(IO_IRQ_BANK0, true); + gpioirq_core = core; + } +} diff --git a/libhw_cr/rp2040_gpioirq.h b/libhw_cr/rp2040_gpioirq.h new file mode 100644 index 0000000..75a264f --- /dev/null +++ b/libhw_cr/rp2040_gpioirq.h @@ -0,0 +1,33 @@ +/* libhw_cr/rp2040_gpioirq.h - Utilities for sharing the GPIO IRQ (IO_IRQ_BANK0) + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_CR_RP2040_GPIOIRQ_H_ +#define _LIBHW_CR_RP2040_GPIOIRQ_H_ + +#include <hardware/gpio.h> /* for enum gpio_irq_level */ + +typedef void (*gpioirq_handler_t)(void *arg, uint gpio, enum gpio_irq_level event); + +/** + * Register `fn(arg, ...)` to be called when `event` fires on GPIO pin + * `gpio`. + * + * If multiple events happen close together, the handlers will not + * necessarily be called in-order. + * + * Your handler does not need to acknowledge the IRQ; that will be + * done for you after your handler is called. + * + * It is illegal to call gpioirq_set_and_enable_exclusive_handler() + * on multiple cores. + * + * This is better than the Pico-SDK <hardware/gpio.h> IRQ functions + * because their functions call the handlers for every event, and it + * is up to you to de-mux them in your handler. + */ +void gpioirq_set_and_enable_exclusive_handler(uint gpio, enum gpio_irq_level event, gpioirq_handler_t fn, void *arg); + +#endif /* _LIBHW_CR_RP2040_GPIOIRQ_H_ */ diff --git a/libhw_cr/rp2040_hwspi.c b/libhw_cr/rp2040_hwspi.c new file mode 100644 index 0000000..d181650 --- /dev/null +++ b/libhw_cr/rp2040_hwspi.c @@ -0,0 +1,271 @@ +/* libhw_cr/rp2040_hwspi.c - <libhw/generic/spi.h> implementation for the RP2040's ARM Primecell SSP (PL022) + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <alloca.h> +#include <inttypes.h> /* for PRIu{n} */ + +#include <hardware/clocks.h> /* for clock_get_hz() and clk_peri */ +#include <hardware/gpio.h> +#include <hardware/spi.h> + +#include <libcr/coroutine.h> +#include <libmisc/assert.h> + +#define LOG_NAME RP2040_SPI +#include <libmisc/log.h> + +#define IMPLEMENTATION_FOR_LIBHW_RP2040_HWSPI_H YES +#include <libhw/rp2040_hwspi.h> + +#include <libhw/generic/alarmclock.h> + +#include "rp2040_dma.h" + +#include "config.h" + +#ifndef CONFIG_RP2040_SPI_DEBUG + #error config.h must define CONFIG_RP2040_SPI_DEBUG (bool) +#endif + +LO_IMPLEMENTATION_C(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi, static); +LO_IMPLEMENTATION_C(spi, struct rp2040_hwspi, rp2040_hwspi, static); + +static void rp2040_hwspi_intrhandler(void *_self, enum dmairq LM_UNUSED(irq), uint LM_UNUSED(channel)) { + struct rp2040_hwspi *self = _self; + gpio_put(self->pin_cs, 1); + cr_sema_signal_from_intrhandler(&self->sema); +} + +#define assert_4distinct(a, b, c, d) \ + assert(a != b); \ + assert(a != c); \ + assert(a != d); \ + assert(b != c); \ + assert(b != d); \ + assert(c != d); + +void _rp2040_hwspi_init(struct rp2040_hwspi *self, + enum rp2040_hwspi_instance inst_num, + enum spi_mode mode, + uint baudrate_hz, + uint64_t min_delay_ns, + uint8_t bogus_data, + uint pin_miso, + uint pin_mosi, + uint pin_clk, + uint pin_cs, + uint dma1, + uint dma2, + uint dma3, + uint dma4) +{ + /* Be not weary: This is but 12 lines of actual code; and many + * lines of comments and assert()s. */ + spi_inst_t *inst; + uint actual_baudrate_hz; + + assert(self); + assert(baudrate_hz); + uint32_t clk_peri_hz = clock_get_hz(clk_peri); + debugf("clk_peri = %"PRIu32"Hz", clk_peri_hz); + assert(baudrate_hz*2 <= clk_peri_hz); + assert_4distinct(pin_miso, pin_mosi, pin_clk, pin_cs); + assert_4distinct(dma1, dma2, dma3, dma4); + + /* Regarding the constraints on pin assignments: see the + * RP2040 datasheet, table 2, in §1.4.3 "GPIO Functions". */ + switch (inst_num) { + case RP2040_HWSPI_0: + inst = spi0; + assert(pin_miso == 0 || pin_miso == 4 || pin_miso == 16 || pin_miso == 20); + /*assert(pin_cs == 1 || pin_cs == 5 || pin_cs == 17 || pin_cs == 21);*/ + assert(pin_clk == 2 || pin_clk == 6 || pin_clk == 18 || pin_clk == 22); + assert(pin_mosi == 3 || pin_mosi == 7 || pin_mosi == 19 || pin_mosi == 23); + break; + case RP2040_HWSPI_1: + inst = spi1; + assert(pin_miso == 8 || pin_miso == 12 || pin_miso == 24 || pin_miso == 28); + /*assert(pin_cs == 9 || pin_cs == 13 || pin_cs == 25 || pin_cs == 29);*/ + assert(pin_clk == 10 || pin_clk == 14 || pin_clk == 26); + assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27); + break; + default: + assert_notreached("invalid hwspi instance number"); + } + + actual_baudrate_hz = spi_init(inst, baudrate_hz); + debugf("baudrate = %uHz", actual_baudrate_hz); + assert(actual_baudrate_hz == baudrate_hz); + spi_set_format(inst, 8, + (mode & 0b10) ? SPI_CPOL_1 : SPI_CPOL_0, + (mode & 0b01) ? SPI_CPHA_1 : SPI_CPHA_0, + SPI_MSB_FIRST); + + /* Connect the pins to the PL022; set them each to "function + * 1" (again, see the RP2040 datasheet, table 2, in §1.4.3 + * "GPIO Functions"). + * + * ("GPIO_FUNC_SPI" is how the pico-sdk spells "function 1", + * since on the RP2040 all of the "function 1" functions are + * some part of SPI.) */ + gpio_set_function(pin_clk, GPIO_FUNC_SPI); + gpio_set_function(pin_mosi, GPIO_FUNC_SPI); + gpio_set_function(pin_miso, GPIO_FUNC_SPI); + + /* Initialize the CS pin for software control. */ + gpio_init(pin_cs); + gpio_set_dir(pin_cs, GPIO_OUT); + gpio_put(pin_cs, 1); + + /* Initialize self. */ + self->inst = inst; + self->min_delay_ns = min_delay_ns; + self->bogus_data = bogus_data; + self->pin_cs = pin_cs; + self->dma_tx_ctrl = dma1; + self->dma_rx_ctrl = dma2; + self->dma_tx_data = dma3; + self->dma_rx_data = dma4; + self->dead_until_ns = 0; + self->sema = (cr_sema_t){}; + + /* Initialize the interrupt handler. */ + /* We do this on (just) the rx channel, because the way the + * SSP works reads necessarily complete *after* writes. */ + dmairq_set_and_enable_exclusive_handler(DMAIRQ_0, self->dma_rx_data, rp2040_hwspi_intrhandler, self); +} + +static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct duplex_iovec *iov, int iovcnt) { + assert(self); + assert(self->inst); + assert(iov); + assert(iovcnt > 0); + + /* At this time, I have no intention to run SPI faster than + * 80MHz (= 80Mb/s = 10MB/s). If we ran the CPU at just + * 100MHz (we'll be running it faster than that, maybe even + * 200MHz), that means we'd have 10 clock cycles to send each + * byte. + * + * This affords us substantial simplifications, like being + * able to afford 4-cycle changeovers between DMA blocks, and + * not having to worry about alignment because we can just use + * DMA_SIZE_8. + */ + + uint8_t bogus_rx_dst; + + int pruned_iovcnt = 0; + for (int i = 0; i < iovcnt; i++) + if (iov[i].iov_len) + pruned_iovcnt++; + if (!pruned_iovcnt) + return; + + /* It doesn't *really* matter which aliases we choose: + * + * - None of our fields can be NULL (so no + * false-termination). + * + * - Moving const fields first so they don't have to be + * re-programmed each time isn't possible for us; there + * need to be at least 2 const fields, and we only have 1 + * (read_addr for rx_data_blocks, and write_addr for + * tx_data_blocks). + * + * The code following this initial declaration is generic to + * the alias, so changing which alias is used is easy. + * + * Since we have no hard requirements, here are some mild + * preferences: + * + * - I like the aliases being different for each channel, + * because it helps prevent alias-specific code from + * sneaking in. + * + * - I like the rx channel (the channel the interrupt handler + * is wired to) having ctrl be the trigger, so that we + * don't have to worry about DMA_CTRL_IRQ_QUIET being + * cleared before the trigger, and at the end the control + * block is clean and zeroed-out. + * + * - Conversely, I like the tx channel (the non-interrupt + * channel) having ctrl *not* be the trigger, so that + * DMA_CTRL_IRQ_QUIET is cleared by the time the trigger + * happens, so the IRQ machinery doesn't need to be engaged + * at all. + */ + struct dma_alias1 *tx_data_blocks = alloca(sizeof(struct dma_alias1)*(pruned_iovcnt+1)); + struct dma_alias0 *rx_data_blocks = alloca(sizeof(struct dma_alias0)*(pruned_iovcnt+1)); + static_assert(!DMA_IS_TRIGGER(typeof(tx_data_blocks[0]), ctrl)); + static_assert(DMA_IS_TRIGGER(typeof(rx_data_blocks[0]), ctrl)); + + for (int i = 0, j = 0; i < iovcnt; i++) { + if (!iov[i].iov_len) + continue; + tx_data_blocks[j] = (typeof(tx_data_blocks[0])){ + .read_addr = (iov[i].iov_write_from != IOVEC_DISCARD) ? iov[i].iov_write_from : &self->bogus_data, + .write_addr = &spi_get_hw(self->inst)->dr, + .trans_count = iov[i].iov_len, + .ctrl = (DMA_CTRL_ENABLE + | DMA_CTRL_DATA_SIZE(DMA_SIZE_8) + | ((iov[i].iov_write_from != IOVEC_DISCARD) ? DMA_CTRL_INCR_READ : 0) + | DMA_CTRL_CHAIN_TO(self->dma_tx_ctrl) + | DMA_CTRL_TREQ_SEL(SPI_DREQ_NUM(self->inst, true)) + | DMA_CTRL_IRQ_QUIET), + }; + rx_data_blocks[j] = (typeof(rx_data_blocks[0])){ + .read_addr = &spi_get_hw(self->inst)->dr, + .write_addr = (iov[i].iov_read_to != IOVEC_DISCARD) ? iov[i].iov_read_to : &bogus_rx_dst, + .trans_count = iov[i].iov_len, + .ctrl = (DMA_CTRL_ENABLE + | DMA_CTRL_DATA_SIZE(DMA_SIZE_8) + | ((iov[i].iov_read_to != IOVEC_DISCARD) ? DMA_CTRL_INCR_WRITE : 0) + | DMA_CTRL_CHAIN_TO(self->dma_rx_ctrl) + | DMA_CTRL_TREQ_SEL(SPI_DREQ_NUM(self->inst, false)) + | DMA_CTRL_IRQ_QUIET), + }; + j++; + } + tx_data_blocks[pruned_iovcnt] = (typeof(tx_data_blocks[0])){0}; + rx_data_blocks[pruned_iovcnt] = (typeof(rx_data_blocks[0])){0}; + /* If ctrl isn't the trigger then we need to make sure that + * DMA_CTRL_IRQ_QUIET isn't cleared before the trigger + * happens. */ + if (!DMA_IS_TRIGGER(typeof(rx_data_blocks[0]), ctrl)) + rx_data_blocks[pruned_iovcnt].ctrl = DMA_CTRL_IRQ_QUIET; + + /* Set up ctrl. */ + DMA_NONTRIGGER(self->dma_tx_ctrl, read_addr) = tx_data_blocks; + DMA_NONTRIGGER(self->dma_tx_ctrl, write_addr) = DMA_CHAN_ADDR(self->dma_tx_data, typeof(tx_data_blocks[0])); + DMA_NONTRIGGER(self->dma_tx_ctrl, trans_count) = DMA_CHAN_WR_TRANS_COUNT(typeof(tx_data_blocks[0])); + DMA_NONTRIGGER(self->dma_tx_ctrl, ctrl) = (DMA_CTRL_ENABLE + | DMA_CHAN_WR_CTRL(typeof(tx_data_blocks[0])) + | DMA_CTRL_INCR_READ + | DMA_CTRL_CHAIN_TO(self->dma_tx_data) + | DMA_CTRL_TREQ_SEL(DREQ_FORCE) + | DMA_CTRL_IRQ_QUIET); + DMA_NONTRIGGER(self->dma_rx_ctrl, read_addr) = rx_data_blocks; + DMA_NONTRIGGER(self->dma_rx_ctrl, write_addr) = DMA_CHAN_ADDR(self->dma_rx_data, typeof(rx_data_blocks[0])); + DMA_NONTRIGGER(self->dma_rx_ctrl, trans_count) = DMA_CHAN_WR_TRANS_COUNT(typeof(rx_data_blocks[0])); + DMA_NONTRIGGER(self->dma_rx_ctrl, ctrl) = (DMA_CTRL_ENABLE + | DMA_CHAN_WR_CTRL(typeof(rx_data_blocks[0])) + | DMA_CTRL_INCR_READ + | DMA_CTRL_CHAIN_TO(self->dma_rx_data) + | DMA_CTRL_TREQ_SEL(DREQ_FORCE) + | DMA_CTRL_IRQ_QUIET); + + /* Run. */ + uint64_t now = LO_CALL(bootclock, get_time_ns); + if (now < self->dead_until_ns) + sleep_until_ns(self->dead_until_ns); + bool saved = cr_save_and_disable_interrupts(); + gpio_put(self->pin_cs, 0); + dma_hw->multi_channel_trigger = (1u<<self->dma_tx_ctrl) | (1u<<self->dma_rx_ctrl); + cr_restore_interrupts(saved); + cr_sema_wait(&self->sema); + self->dead_until_ns = LO_CALL(bootclock, get_time_ns) + self->min_delay_ns; +} diff --git a/libhw/rp2040_hwtimer.c b/libhw_cr/rp2040_hwtimer.c index 4499642..8227abb 100644 --- a/libhw/rp2040_hwtimer.c +++ b/libhw_cr/rp2040_hwtimer.c @@ -1,6 +1,6 @@ -/* libhw/rp2040_hwtimer.c - <libhw/generic/alarmclock.h> implementation for the RP2040's hardware timer +/* libhw_cr/rp2040_hwtimer.c - <libhw/generic/alarmclock.h> implementation for the RP2040's hardware timer * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -9,10 +9,10 @@ #include <libcr/coroutine.h> #include <libmisc/assert.h> -#include <libmisc/vcall.h> #define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES #include <libhw/generic/alarmclock.h> + #include <libhw/rp2040_hwtimer.h> /******************************************************************************/ @@ -23,52 +23,36 @@ void add_alarm_at(void) {}; /* Types **********************************************************************/ struct rp2040_hwtimer { - implements_alarmclock; enum rp2040_hwalarm_instance alarm_num; bool initialized; struct alarmclock_trigger *queue; }; +LO_IMPLEMENTATION_H(alarmclock, struct rp2040_hwtimer, rp2040_hwtimer); +LO_IMPLEMENTATION_C(alarmclock, struct rp2040_hwtimer, rp2040_hwtimer, static); /* Globals ********************************************************************/ -static uint64_t rp2040_hwtimer_get_time_ns(implements_alarmclock *self); -static bool rp2040_hwtimer_add_trigger(implements_alarmclock *self, - struct alarmclock_trigger *trigger, - uint64_t fire_at_ns, - void (*cb)(void *), - void *cb_arg); -static void rp2040_hwtimer_del_trigger(implements_alarmclock *self, - struct alarmclock_trigger *trigger); - -static struct alarmclock_vtable rp2040_hwtimer_vtable = { - .get_time_ns = rp2040_hwtimer_get_time_ns, - .add_trigger = rp2040_hwtimer_add_trigger, - .del_trigger = rp2040_hwtimer_del_trigger, -}; - static struct rp2040_hwtimer hwtimers[] = { - { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 0 }, - { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 1 }, - { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 2 }, - { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 3 }, + { .alarm_num = 0 }, + { .alarm_num = 1 }, + { .alarm_num = 2 }, + { .alarm_num = 3 }, }; static_assert(sizeof(hwtimers)/sizeof(hwtimers[0]) == _RP2040_HWALARM_NUM); -implements_alarmclock *bootclock = &hwtimers[0]; - /* Main implementation ********************************************************/ -implements_alarmclock *rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num) { +lo_interface alarmclock rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num) { assert(alarm_num < _RP2040_HWALARM_NUM); - return &hwtimers[alarm_num]; + return lo_box_rp2040_hwtimer_as_alarmclock(&hwtimers[alarm_num]); } -static uint64_t rp2040_hwtimer_get_time_ns(implements_alarmclock *) { +static uint64_t rp2040_hwtimer_get_time_ns(struct rp2040_hwtimer *) { return timer_time_us_64(timer_hw) * (NS_PER_S/US_PER_S); } -#define NS_TO_US_ROUNDUP(x) ( ( (x) + (NS_PER_S/US_PER_S)-1) / (NS_PER_S/US_PER_S) ) +#define NS_TO_US_ROUNDUP(x) LM_CEILDIV(x, NS_PER_S/US_PER_S) static void rp2040_hwtimer_intrhandler(void) { uint irq_num = __get_current_exception() - VTABLE_FIRST_IRQ; @@ -93,13 +77,11 @@ static void rp2040_hwtimer_intrhandler(void) { timer_hw->alarm[alarm_num] = (uint32_t)NS_TO_US_ROUNDUP(alarmclock->queue->fire_at_ns); } -static bool rp2040_hwtimer_add_trigger(implements_alarmclock *_alarmclock, +static bool rp2040_hwtimer_add_trigger(struct rp2040_hwtimer *alarmclock, struct alarmclock_trigger *trigger, uint64_t fire_at_ns, void (*cb)(void *), void *cb_arg) { - struct rp2040_hwtimer *alarmclock = - VCALL_SELF(struct rp2040_hwtimer, implements_alarmclock, _alarmclock); assert(alarmclock); assert(trigger); assert(fire_at_ns); @@ -149,10 +131,8 @@ static bool rp2040_hwtimer_add_trigger(implements_alarmclock *_alarmclock, return false; } -static void rp2040_hwtimer_del_trigger(implements_alarmclock *_alarmclock, +static void rp2040_hwtimer_del_trigger(struct rp2040_hwtimer *alarmclock, struct alarmclock_trigger *trigger) { - struct rp2040_hwtimer *alarmclock = - VCALL_SELF(struct rp2040_hwtimer, implements_alarmclock, _alarmclock); assert(alarmclock); assert(trigger); diff --git a/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h b/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h new file mode 100644 index 0000000..4951136 --- /dev/null +++ b/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h @@ -0,0 +1,118 @@ +/* libhw/rp2040_hwspi.h - <libhw/generic/spi.h> implementation for the RP2040's ARM Primecell SSP (PL022) + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_RP2040_HWSPI_H_ +#define _LIBHW_RP2040_HWSPI_H_ + +#include <pico/binary_info.h> /* for bi_* */ + +#include <libcr_ipc/sema.h> +#include <libmisc/private.h> + +#include <libhw/generic/spi.h> + +enum rp2040_hwspi_instance { + RP2040_HWSPI_0 = 0, + RP2040_HWSPI_1 = 1, +}; + +struct rp2040_hwspi { + BEGIN_PRIVATE(LIBHW_RP2040_HWSPI_H); + /* const */ + LM_IF(IS_IMPLEMENTATION_FOR(LIBHW_RP2040_HWSPI_H))(spi_inst_t)(void) *inst; + uint64_t min_delay_ns; + uint8_t bogus_data; + uint pin_cs; + uint dma_tx_data; + uint dma_tx_ctrl; + uint dma_rx_data; + uint dma_rx_ctrl; + + /* mutable */ + uint64_t dead_until_ns; + cr_sema_t sema; + END_PRIVATE(LIBHW_RP2040_HWSPI_H); +}; +LO_IMPLEMENTATION_H(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi); +LO_IMPLEMENTATION_H(spi, struct rp2040_hwspi, rp2040_hwspi); + +/** + * Initialize an instance of `struct rp2040_hwspi`. + * + * @param self : struct rp2040_hwspi : the structure to initialize + * @param name : char * : a name for the SPI port; to include in the bininfo + * @param inst_num : enum rp2040_hwspi_instance : the PL220 instance number; RP2040_HWSPI_{0,1} + * @param mode : enum spi_mode : the SPI mode; SPI_MODE_{0..3} + * @param baudrate_hz : uint : baudrate in Hz + * @param min_delay_ns: uint64_t : minimum time for pin_cs to be high between messages + * @param bogus_data : uint8_t : bogus data to write when .iov_write_from is IOVEC_DISCARD + * @param pin_miso : uint : pin number; 0, 4, 16, or 20 for _HWSPI_0; 8, 12, 24, or 28 for _HWSPI_1 + * @param pin_mosi : uint : pin number; 3, 7, 19, or 23 for _HWSPI_0; 11, 15, or 27 for _HWSPI_1 + * @param pin_clk : uint : pin number; 2, 6, 18, or 22 for _HWSPI_0; 10, 14, or 26 for _HWSPI_1 + * @param pin_cs : uint : pin number; any unused GPIO pin + * @param dma{1-4} : uint : DMA channel; any unused channel + * + * There is no bit-order argument; the RP2040's hardware SPI always + * uses MSB-first bit order. + * + * I know we called this "hwspi", but we're actually going to + * disconnect the CS pin from the PL022 SSP and manually GPIO it from + * the CPU. This is because the PL022 has a maximum of 16-bit frames, + * but we need to be able to do *at least* 32-bit frames (and ideally, + * much larger). By managing it ourselves, we can just keep CS pulled + * low extra-long, making the frame extra-long. + * + * Restrictions on baudrate: + * + * - The PL022 requires that the baudrate is an even-number fraction + * of clk_peri. + * + This implies that the maximum baudrate is clk_peri/2. + * + Pico-SDK' default clk_peri is 125MHz, max is 200MHz. + * - The CS-from-GPIO hack above means that that we can't go so fast + * that the CPU can't do things in time. + * + Experimentally: + * | clk_sys=125MHz | baud=31.25MHz | works OK | + * | clk_sys=160MHz | baud=40 MHz | works OK | + * | clk_sys=170MHz | baud=42.5 MHz | works OK | + * | clk_sys=180MHz | baud=45 MHz | mangled in funny ways? | + * | clk_sys=200MHz | baud=50 MHz | messages get shifted right a bit | + * | clk_sys=125MHz | baud=62.5 MHz | messages get shifted right a bit | + * + * Both of these restrictions aught to be avoidable by using a + * PIO-based SPI driver instead of this PLL02-based driver. + */ +#define rp2040_hwspi_init(self, name, \ + inst_num, mode, baudrate_hz, \ + min_delay_ns, bogus_data, \ + pin_miso, pin_mosi, pin_clk, pin_cs, \ + dma1, dma2, dma3, dma4) \ + do { \ + bi_decl(bi_4pins_with_names(pin_miso, name" SPI MISO", \ + pin_mosi, name" SPI MOSI", \ + pin_mosi, name" SPI CLK", \ + pin_mosi, name" SPI CS")); \ + _rp2040_hwspi_init(self, \ + inst_num, mode, baudrate_hz, \ + min_delay_ns, bogus_data, \ + pin_miso, pin_mosi, pin_clk, pin_cs, \ + dma1, dma2, dma3, dma4); \ + } while(0) +void _rp2040_hwspi_init(struct rp2040_hwspi *self, + enum rp2040_hwspi_instance inst_num, + enum spi_mode mode, + uint baudrate_hz, + uint64_t min_delay_ns, + uint8_t bogus_data, + uint pin_miso, + uint pin_mosi, + uint pin_clk, + uint pin_cs, + uint dma1, + uint dma2, + uint dma3, + uint dma4); + +#endif /* _LIBHW_RP2040_HWSPI_H_ */ diff --git a/libhw/rp2040_include/libhw/rp2040_hwtimer.h b/libhw_cr/rp2040_include/libhw/rp2040_hwtimer.h index 6710ab1..40e4172 100644 --- a/libhw/rp2040_include/libhw/rp2040_hwtimer.h +++ b/libhw_cr/rp2040_include/libhw/rp2040_hwtimer.h @@ -1,6 +1,6 @@ /* libhw/rp2040_hwtimer.h - <libhw/generic/alarmclock.h> implementation for the RP2040's hardware timer * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -10,8 +10,7 @@ #include <libhw/generic/alarmclock.h> /** - * The RP2040 has one system "timer" (which we also use for - * ./rp2040_bootclock.c) with 4 alarm interrupts. + * The RP2040 has one system "timer" with 4 alarm interrupts. */ enum rp2040_hwalarm_instance { RP2040_HWALARM_0 = 0, @@ -21,6 +20,6 @@ enum rp2040_hwalarm_instance { _RP2040_HWALARM_NUM, }; -implements_alarmclock *rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num); +lo_interface alarmclock rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num); #endif /* _LIBHW_RP2040_HWTIMER_H_ */ diff --git a/libhw/rp2040_include/libhw/w5500.h b/libhw_cr/rp2040_include/libhw/w5500.h index 80366a0..8dda1a1 100644 --- a/libhw/rp2040_include/libhw/w5500.h +++ b/libhw_cr/rp2040_include/libhw/w5500.h @@ -1,6 +1,6 @@ /* libhw/w5500.h - <libhw/generic/net.h> implementation for the WIZnet W5500 chip * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -17,14 +17,11 @@ #include <libhw/generic/net.h> #include <libhw/generic/spi.h> -CR_CHAN_DECLARE(_w5500_sockintr_ch, uint8_t) +CR_CHAN_DECLARE(_w5500_sockintr_ch, uint8_t); struct _w5500_socket { + BEGIN_PRIVATE(LIBHW_W5500_H); /* const-after-init */ - implements_net_stream_listener implements_net_stream_listener; - implements_net_stream_conn implements_net_stream_conn; - implements_net_packet_conn implements_net_packet_conn; - BEGIN_PRIVATE(LIBHW_W5500_H) uint8_t socknum; /* mutable */ @@ -41,15 +38,26 @@ struct _w5500_socket { _w5500_sockintr_ch_t write_ch; /* MODE_{TCP,UDP} */ bool list_open, read_open, write_open; /* MODE_TCP */ - cr_mutex_t cmd_mu; - END_PRIVATE(LIBHW_W5500_H) + END_PRIVATE(LIBHW_W5500_H); }; +LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_tcplist); +LO_IMPLEMENTATION_H(net_stream_listener, struct _w5500_socket, w5500_tcplist); + +LO_IMPLEMENTATION_H(io_reader, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_H(io_writer, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_H(io_readwriter, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_H(io_bidi_closer, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_H(net_stream_conn, struct _w5500_socket, w5500_tcp); + +LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_udp); +LO_IMPLEMENTATION_H(net_packet_conn, struct _w5500_socket, w5500_udp); + struct w5500 { + BEGIN_PRIVATE(LIBHW_W5500_H); /* const-after-init */ - implements_net_iface; - BEGIN_PRIVATE(LIBHW_W5500_H) - implements_spi *spidev; + lo_interface spi spidev; uint pin_intr; uint pin_reset; struct net_eth_addr hwaddr; @@ -59,8 +67,10 @@ struct w5500 { struct _w5500_socket sockets[8]; struct _w5500_socket *free; cr_sema_t intr; - END_PRIVATE(LIBHW_W5500_H) + cr_mutex_t mu; + END_PRIVATE(LIBHW_W5500_H); }; +LO_IMPLEMENTATION_H(net_iface, struct w5500, w5500_if); /** * Initialize a WIZnet W5500 Ethernet-and-TCP/IP-offload chip. @@ -82,16 +92,22 @@ struct w5500 { _w5500_init(self, spi, pin_intr, pin_reset, eth_addr); \ } while (0) void _w5500_init(struct w5500 *self, - implements_spi *spi, uint pin_intr, uint pin_reset, + lo_interface spi spi, uint pin_intr, uint pin_reset, struct net_eth_addr addr); /** - * TODO. + * Perform a hard reset on the chip (pull the reset line low). + * + * If you have any in-use sockets when you call this, you're going to + * have a bad time. */ void w5500_hard_reset(struct w5500 *self); /** - * TODO. + * Perform a soft reset on the chip (send the RST command). + * + * If you have any in-use sockets when you call this, you're going to + * have a bad time. */ void w5500_soft_reset(struct w5500 *self); diff --git a/libhw/w5500.c b/libhw_cr/w5500.c index c039226..fa427d9 100644 --- a/libhw/w5500.c +++ b/libhw_cr/w5500.c @@ -1,6 +1,6 @@ -/* libhw/w5500.c - <libhw/generic/net.h> implementation for the WIZnet W5500 chip +/* libhw_cr/w5500.c - <libhw/generic/net.h> implementation for the WIZnet W5500 chip * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -67,17 +67,19 @@ * SPDX-License-Identifier: MIT */ +#include <inttypes.h> /* for PRIu{n} */ + /* TODO: Write a <libhw/generic/gpio.h> to avoid w5500.c being * pico-sdk-specific. */ #include <hardware/gpio.h> /* pico-sdk:hardware_gpio */ +#include "rp2040_gpioirq.h" #include <libcr/coroutine.h> /* for cr_yield() */ -#include <libmisc/vcall.h> /* for VCALL_SELF() */ #include <libhw/generic/alarmclock.h> /* for sleep_*() */ #define LOG_NAME W5500 -#include <libmisc/log.h> /* for errorf() */ +#include <libmisc/log.h> /* for errorf(), debugf(), const_byte_str() */ #define IMPLEMENTATION_FOR_LIBHW_W5500_H YES #include <libhw/w5500.h> @@ -88,81 +90,56 @@ #include "config.h" -/* These are the default values of the Linux kernel's - * net.ipv4.ip_local_port_range, so I figure they're probably good - * values to use. */ #ifndef CONFIG_W5500_LOCAL_PORT_MIN #error config.h must define CONFIG_W5500_LOCAL_PORT_MIN #endif - #ifndef CONFIG_W5500_LOCAL_PORT_MAX #error config.h must define CONFIG_W5500_LOCAL_PORT_MAX #endif - -#ifndef CONFIG_W5500_NUM - #error config.h must define CONFIG_W5500_NUM +#ifndef CONFIG_W5500_VALIDATE_SPI + #error config.h must define CONFIG_W5500_VALIDATE_SPI +#endif +#ifndef CONFIG_W5500_DEBUG + #error config.h must define CONFIG_W5500_DEBUG #endif /* C language *****************************************************************/ -#define UNUSED(name) -#define ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0])) - -/* vtables ********************************************************************/ - -/* iface */ -static struct net_eth_addr w5500_if_hwaddr (implements_net_iface *); -static void w5500_if_up (implements_net_iface *, struct net_iface_config); -static void w5500_if_down (implements_net_iface *); -static implements_net_stream_listener *w5500_if_tcp_listen (implements_net_iface *, uint16_t local_port); -static implements_net_stream_conn *w5500_if_tcp_dial (implements_net_iface *, struct net_ip4_addr, uint16_t remote_port); -static implements_net_packet_conn *w5500_if_udp_conn (implements_net_iface *, uint16_t local_port); - -/* stream_listener */ -static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *); -static int w5500_tcplist_close (implements_net_stream_listener *); - -/* stream_conn */ -static void w5500_tcp_set_read_deadline (implements_net_stream_conn *, uint64_t ns); -static ssize_t w5500_tcp_read (implements_net_stream_conn *, void *, size_t); -static ssize_t w5500_tcp_write (implements_net_stream_conn *, void *, size_t); -static int w5500_tcp_close (implements_net_stream_conn *, bool rd, bool wr); - -/* packet_conn */ -static void w5500_udp_set_read_deadline (implements_net_packet_conn *, uint64_t ns); -static ssize_t w5500_udp_recvfrom (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr *, uint16_t *); -static ssize_t w5500_udp_sendto (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr, uint16_t); -static int w5500_udp_close (implements_net_packet_conn *); - -/* tables */ - -static struct net_iface_vtable w5500_iface_vtable = { - .hwaddr = w5500_if_hwaddr, - .ifup = w5500_if_up, - .ifdown = w5500_if_down, - .tcp_listen = w5500_if_tcp_listen, - .tcp_dial = w5500_if_tcp_dial, - .udp_conn = w5500_if_udp_conn, -}; - -static struct net_stream_listener_vtable w5500_tcp_listener_vtable = { - .accept = w5500_tcplist_accept, - .close = w5500_tcplist_close, -}; - -static struct net_stream_conn_vtable w5500_tcp_conn_vtable = { - .set_read_deadline = w5500_tcp_set_read_deadline, - .read = w5500_tcp_read, - .write = w5500_tcp_write, - .close = w5500_tcp_close, -}; - -static struct net_packet_conn_vtable w5500_udp_conn_vtable = { - .set_read_deadline = w5500_udp_set_read_deadline, - .recvfrom = w5500_udp_recvfrom, - .sendto = w5500_udp_sendto, - .close = w5500_udp_close, -}; +static const char *w5500_state_str(uint8_t state) { + switch (state) { + case STATE_CLOSED: return "STATE_CLOSED"; + case STATE_TCP_INIT: return "STATE_TCP_INIT"; + case STATE_TCP_LISTEN: return "STATE_TCP_LISTEN"; + case STATE_TCP_SYNSENT: return "STATE_TCP_SYNSENT"; + case STATE_TCP_SYNRECV: return "STATE_TCP_SYNRECV"; + case STATE_TCP_ESTABLISHED: return "STATE_TCP_ESTABLISHED"; + case STATE_TCP_FIN_WAIT: return "STATE_TCP_FIN_WAIT"; + case STATE_TCP_CLOSING: return "STATE_TCP_CLOSING"; + case STATE_TCP_TIME_WAIT: return "STATE_TCP_TIME_WAIT"; + case STATE_TCP_CLOSE_WAIT: return "STATE_TCP_CLOSE_WAIT"; + case STATE_TCP_LAST_ACK: return "STATE_TCP_LAST_ACK"; + case STATE_UDP: return "STATE_UDP"; + case STATE_MACRAW: return "STATE_MACRAW"; + default: return const_byte_str(state); + } +} + +/* libobj *********************************************************************/ + +LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_tcplist, static); +LO_IMPLEMENTATION_C(net_stream_listener, struct _w5500_socket, w5500_tcplist, static); + +LO_IMPLEMENTATION_C(io_reader, struct _w5500_socket, w5500_tcp, static); +LO_IMPLEMENTATION_C(io_writer, struct _w5500_socket, w5500_tcp, static); +LO_IMPLEMENTATION_C(io_readwriter, struct _w5500_socket, w5500_tcp, static); +LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_tcp, static); +LO_IMPLEMENTATION_C(io_bidi_closer, struct _w5500_socket, w5500_tcp, static); +LO_IMPLEMENTATION_C(net_stream_conn, struct _w5500_socket, w5500_tcp, static); + +LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_udp, static); +LO_IMPLEMENTATION_C(net_packet_conn, struct _w5500_socket, w5500_udp, static); + +LO_IMPLEMENTATION_C(net_iface, struct w5500, w5500_if, static); /* mid-level utilities ********************************************************/ @@ -206,17 +183,21 @@ static COROUTINE w5500_irq_cr(void *_chip) { cr_begin(); for (;;) { - cr_sema_wait(&chip->intr); - if (w5500ll_read_common_reg(chip->spidev, chip_interrupt)) + cr_mutex_lock(&chip->mu); + bool had_intr = false; + + uint8_t chipintr = w5500ll_read_common_reg(chip->spidev, chip_interrupt); + n_debugf(W5500_LL, "w5500_irq_cr(): chipintr=%"PRIu8, chipintr); + had_intr = had_intr || (chipintr != 0); + if (chipintr) w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF); - uint8_t sockmask = w5500ll_read_common_reg(chip->spidev, sock_interrupt); for (uint8_t socknum = 0; socknum < 8; socknum++) { - if (!(sockmask & (1<<socknum))) - continue; struct _w5500_socket *socket = &chip->sockets[socknum]; uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); + n_debugf(W5500_LL, "w5500_irq_cr(): sockintr[%"PRIu8"]=%"PRIu8, socknum, sockintr); + had_intr = had_intr || (sockintr != 0); switch (socket->mode) { case W5500_MODE_NONE: @@ -226,17 +207,31 @@ static COROUTINE w5500_irq_cr(void *_chip) { send_bits = sockintr & (SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT), recv_bits = sockintr & (SOCKINTR_RECV_DAT|SOCKINTR_RECV_FIN); - if (listen_bits) + if (listen_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->listen_sema", socknum); cr_sema_signal(&socket->listen_sema); - if (recv_bits) + } + if (recv_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->read_sema", socknum); cr_sema_signal(&socket->read_sema); - if (send_bits) + } + if (send_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->write_ch", socknum); _w5500_sockintr_ch_send(&socket->write_ch, send_bits); + } break; } w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); } + + cr_mutex_unlock(&chip->mu); + + if (!had_intr && gpio_get(chip->pin_intr)) { + debugf("w5500_irq_cr(): looks like all interrupts have been processed, sleeping..."); + cr_sema_wait(&chip->intr); + } else + cr_yield(); } cr_end(); @@ -259,11 +254,9 @@ static inline void w5500_socket_cmd(struct _w5500_socket *socket, uint8_t cmd) { struct w5500 *chip = w5500_socket_chip(socket); uint8_t socknum = socket->socknum; - cr_mutex_lock(&socket->cmd_mu); w5500ll_write_sock_reg(chip->spidev, socknum, command, cmd); while (w5500ll_read_sock_reg(chip->spidev, socknum, command) != 0x00) cr_yield(); - cr_mutex_unlock(&socket->cmd_mu); } static inline void w5500_socket_close(struct _w5500_socket *socket) { @@ -279,9 +272,6 @@ static inline void w5500_socket_close(struct _w5500_socket *socket) { } #define ASSERT_SELF(_iface, _mode) \ - struct _w5500_socket *socket = \ - VCALL_SELF(struct _w5500_socket, \ - implements_net_##_iface, _socket); \ assert(socket); \ uint8_t socknum = socket->socknum; \ assert(socknum < 8); \ @@ -291,24 +281,21 @@ static inline void w5500_socket_close(struct _w5500_socket *socket) { /* init() *********************************************************************/ -static struct w5500 *w5500_chips[CONFIG_W5500_NUM] = {0}; - -static void w5500_intrhandler(uint gpio, uint32_t UNUSED(event_mask)) { - for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++) - if (w5500_chips[i] && w5500_chips[i]->pin_intr == gpio) - cr_sema_signal_from_intrhandler(&w5500_chips[i]->intr); +static void w5500_intrhandler(void *_chip, uint LM_UNUSED(gpio), enum gpio_irq_level LM_UNUSED(event)) { + struct w5500 *chip = _chip; + debugf("w5500_intrhandler()"); + cr_sema_signal_from_intrhandler(&chip->intr); } void _w5500_init(struct w5500 *chip, - implements_spi *spi, uint pin_intr, uint pin_reset, + lo_interface spi spi, uint pin_intr, uint pin_reset, struct net_eth_addr addr) { assert(chip); - assert(spi); + assert(!LO_IS_NULL(spi)); /* Initialize the data structures. */ *chip = (struct w5500){ /* const-after-init */ - .implements_net_iface = { .vtable = &w5500_iface_vtable }, .spidev = spi, .pin_intr = pin_intr, .pin_reset = pin_reset, @@ -320,54 +307,50 @@ void _w5500_init(struct w5500 *chip, for (uint8_t i = 0; i < 8; i++) { chip->sockets[i] = (struct _w5500_socket){ /* const-after-init */ - .implements_net_stream_listener = { .vtable = &w5500_tcp_listener_vtable }, - .implements_net_stream_conn = { .vtable = &w5500_tcp_conn_vtable }, - .implements_net_packet_conn = { .vtable = &w5500_udp_conn_vtable }, .socknum = i, /* mutable */ .next_free = (i + 1 < 8) ? &chip->sockets[i+1] : NULL, - /* the rest of the mutable members get - * initialized to the zero values */ + /* The rest of the mutable members get + * initialized to the zero values. */ }; } - /* Initialize the hardware. */ - gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, w5500_intrhandler); - gpio_set_dir(chip->pin_reset, GPIO_OUT); - w5500_hard_reset(chip); - +#if CONFIG_W5500_VALIDATE_SPI /* Validate that SPI works correctly. */ + bool spi_ok = true; for (uint16_t a = 0; a < 0x100; a++) { w5500ll_write_sock_reg(chip->spidev, 0, mode, a); uint8_t b = w5500ll_read_sock_reg(chip->spidev, 0, mode); - if (b != a) - errorf("SPI to W5500 does not appear to be functional: wrote:%d != read:%d", a, b); + if (b != a) { + errorf("SPI to W5500 does not appear to be functional: wrote:0x%02"PRIx16" != read:0x%02"PRIx8, a, b); + spi_ok = false; + } } + if (!spi_ok) + __lm_abort(); w5500ll_write_sock_reg(chip->spidev, 0, mode, 0); +#endif + + /* Initialize the hardware. */ + gpioirq_set_and_enable_exclusive_handler(pin_intr, GPIO_IRQ_EDGE_FALL, w5500_intrhandler, chip); + gpio_set_dir(chip->pin_reset, GPIO_OUT); + w5500_hard_reset(chip); /* Finally, wire in the interrupt handler. */ - bool saved = cr_save_and_disable_interrupts(); - for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++) { - if (w5500_chips[i] == NULL) { - w5500_chips[i] = chip; - break; - } - } - cr_restore_interrupts(saved); coroutine_add("w5500_irq", w5500_irq_cr, chip); } /* chip methods ***************************************************************/ -static inline void w5500_post_reset(struct w5500 *chip) { +static void w5500_post_reset(struct w5500 *chip) { /* The W5500 does not have a built-in MAC address, we must * provide one. */ w5500ll_write_common_reg(chip->spidev, eth_addr, chip->hwaddr); /* The RP2040 needs a 1/sys_clk hysteresis between interrupts - * for us to notice them. At the maximum-rated clock-rate of - * 133MHz, that means 7.5ns (but the sbc-harness overclocks - * the RP2040, so we could get away with even shorter). + * for us to notice them. At the default clock-rate of + * 125MHz, that means 8ns; and at the maximum-rated clock-rate + * of 200MHz, that means 5ns. * * If intlevel is non-zero, then the hysteresis is * (intlevel+1)*4/(150MHz), or (intlevel+1)*26.7ns; so even @@ -401,49 +384,70 @@ static inline void w5500_post_reset(struct w5500 *chip) { } void w5500_hard_reset(struct w5500 *chip) { + cr_mutex_lock(&chip->mu); + gpio_put(chip->pin_reset, 0); sleep_for_ms(1); /* minimum of 500us */ gpio_put(chip->pin_reset, 1); sleep_for_ms(2); /* minimum of 1ms */ w5500_post_reset(chip); + + cr_mutex_unlock(&chip->mu); } void w5500_soft_reset(struct w5500 *chip) { + cr_mutex_lock(&chip->mu); + w5500ll_write_common_reg(chip->spidev, mode, CHIPMODE_RST); while (w5500ll_read_common_reg(chip->spidev, mode) & CHIPMODE_RST) cr_yield(); w5500_post_reset(chip); + + cr_mutex_unlock(&chip->mu); } -static struct net_eth_addr w5500_if_hwaddr(implements_net_iface *_chip) { - struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); +static struct net_eth_addr w5500_if_hwaddr(struct w5500 *chip) { assert(chip); return chip->hwaddr; } -static void w5500_if_up(implements_net_iface *_chip, struct net_iface_config cfg) { - struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); +static void _w5500_if_up(struct w5500 *chip, struct net_iface_config cfg) { assert(chip); + cr_mutex_lock(&chip->mu); + w5500ll_write_common_reg(chip->spidev, ip_gateway_addr, cfg.gateway_addr); w5500ll_write_common_reg(chip->spidev, ip_subnet_mask, cfg.subnet_mask); w5500ll_write_common_reg(chip->spidev, ip_addr, cfg.addr); + + cr_mutex_unlock(&chip->mu); +} + +static void w5500_if_ifup(struct w5500 *chip, struct net_iface_config cfg) { + debugf("if_up()"); + debugf(":: addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.addr)); + debugf(":: gateway_addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.gateway_addr)); + debugf(":: subnet_mask = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.subnet_mask)); + _w5500_if_up(chip, cfg); } -static void w5500_if_down(implements_net_iface *_chip) { - w5500_if_up(_chip, (struct net_iface_config){0}); +static void w5500_if_ifdown(struct w5500 *chip) { + debugf("if_down()"); + _w5500_if_up(chip, (struct net_iface_config){0}); } -static implements_net_stream_listener *w5500_if_tcp_listen(implements_net_iface *_chip, uint16_t local_port) { - struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); +static lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, uint16_t local_port) { assert(chip); struct _w5500_socket *sock = w5500_alloc_socket(chip); - if (!sock) - return NULL; + if (!sock) { + debugf("tcp_listen() => no sock"); + return LO_NULL(net_stream_listener); + } + debugf("tcp_listen() => sock[%"PRIu8"]", sock->socknum); if (!local_port) local_port = w5500_alloc_local_port(chip); @@ -454,21 +458,23 @@ static implements_net_stream_listener *w5500_if_tcp_listen(implements_net_iface sock->read_deadline_ns = 0; sock->list_open = true; - return &sock->implements_net_stream_listener; + return lo_box_w5500_tcplist_as_net_stream_listener(sock); } -static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip, - struct net_ip4_addr node, uint16_t port) { - struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); +static lo_interface net_stream_conn w5500_if_tcp_dial(struct w5500 *chip, + struct net_ip4_addr node, uint16_t port) { assert(chip); assert(memcmp(node.octets, net_ip4_addr_zero.octets, 4)); assert(memcmp(node.octets, net_ip4_addr_broadcast.octets, 4)); assert(port); struct _w5500_socket *socket = w5500_alloc_socket(chip); - if (!socket) - return NULL; + if (!socket) { + debugf("tcp_dial() => no sock"); + return LO_NULL(net_stream_conn); + } uint8_t socknum = socket->socknum; + debugf("tcp_dial() => sock[%"PRIu8"]", socknum); uint16_t local_port = w5500_alloc_local_port(chip); @@ -479,6 +485,8 @@ static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip socket->read_open = socket->write_open = true; restart: + cr_mutex_lock(&chip->mu); + /* Mimics socket.c:socket(). */ w5500_socket_close(socket); w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); @@ -491,28 +499,32 @@ static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip w5500ll_write_sock_reg(chip->spidev, socknum, remote_ip_addr, node); w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port)); w5500_socket_cmd(socket, CMD_CONNECT); + cr_mutex_unlock(&chip->mu); for (;;) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + debugf("tcp_dial(): state=%s", w5500_state_str(state)); switch (state) { case STATE_TCP_SYNSENT: cr_yield(); break; case STATE_TCP_ESTABLISHED: - return &socket->implements_net_stream_conn; + return lo_box_w5500_tcp_as_net_stream_conn(socket); default: goto restart; } } } -implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip, uint16_t local_port) { - struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); +static lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16_t local_port) { assert(chip); struct _w5500_socket *socket = w5500_alloc_socket(chip); - if (!socket) - return NULL; + if (!socket) { + debugf("udp_conn() => no sock"); + return LO_NULL(net_packet_conn); + } uint8_t socknum = socket->socknum; + debugf("udp_conn() => sock[%"PRIu8"]", socknum); if (!local_port) local_port = w5500_alloc_local_port(chip); @@ -523,24 +535,30 @@ implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip, uint1 socket->read_deadline_ns = 0; /* Mimics socket.c:socket(). */ + cr_mutex_lock(&chip->mu); w5500_socket_close(socket); w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_UDP); w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(socket->port)); w5500_socket_cmd(socket, CMD_OPEN); while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_UDP) cr_yield(); + cr_mutex_unlock(&chip->mu); - return &socket->implements_net_packet_conn; + return lo_box_w5500_udp_as_net_packet_conn(socket); } /* tcp_listener methods *******************************************************/ -static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *_socket) { +static lo_interface net_stream_conn w5500_tcplist_accept(struct _w5500_socket *socket) { ASSERT_SELF(stream_listener, TCP); restart: - if (!socket->list_open) - return NULL; + if (!socket->list_open) { + debugf("tcp_listener.accept() => already closed"); + return LO_NULL(net_stream_conn); + } + + cr_mutex_lock(&chip->mu); /* Mimics socket.c:socket(). */ w5500_socket_close(socket); @@ -552,8 +570,10 @@ static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_li /* Mimics socket.c:listen(). */ w5500_socket_cmd(socket, CMD_LISTEN); + cr_mutex_unlock(&chip->mu); for (;;) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + debugf("tcp_listener.accept() => state=%s", w5500_state_str(state)); switch (state) { case STATE_TCP_LISTEN: case STATE_TCP_SYNRECV: @@ -564,14 +584,15 @@ static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_li /* fall-through */ case STATE_TCP_CLOSE_WAIT: socket->write_open = true; - return &socket->implements_net_stream_conn; + return lo_box_w5500_tcp_as_net_stream_conn(socket); default: goto restart; } } } -static int w5500_tcplist_close(implements_net_stream_listener *_socket) { +static int w5500_tcplist_close(struct _w5500_socket *socket) { + debugf("tcp_listener.close()"); ASSERT_SELF(stream_listener, TCP); socket->list_open = false; @@ -581,10 +602,16 @@ static int w5500_tcplist_close(implements_net_stream_listener *_socket) { /* tcp_conn methods ***********************************************************/ -static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, size_t count) { +static ssize_t w5500_tcp_writev(struct _w5500_socket *socket, const struct iovec *iov, int iovcnt) { + assert(iov); + assert(iovcnt > 0); + size_t count = 0; + for (int i = 0; i < iovcnt; i++) + count += iov[i].iov_len; + debugf("tcp_conn.write(%zu)", count); ASSERT_SELF(stream_conn, TCP); - assert(buf); - assert(count); + if (count == 0) + return 0; /* What we really want is to pause until we receive an ACK for * some data we just queued, so that we can line up some new @@ -608,15 +635,22 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, s size_t done = 0; while (done < count) { - if (!socket->write_open) + if (!socket->write_open) { + debugf(" => soft closed"); return -NET_ECLOSED; + } + cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) + if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) { + cr_mutex_unlock(&chip->mu); + debugf(" => hard closed"); return -NET_ECLOSED; + } uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); if (freesize < count-done && freesize < min_free_space) { /* Wait for more buffer space. */ + cr_mutex_unlock(&chip->mu); cr_yield(); continue; } @@ -625,16 +659,20 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, s if ((size_t)freesize > count-done) freesize = count-done; uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer)); - w5500ll_write(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((char *)buf)[done], freesize); + w5500ll_writev(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), iov, iovcnt, done, freesize); w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+freesize)); /* Submit the queue. */ w5500_socket_cmd(socket, CMD_SEND); + + cr_mutex_unlock(&chip->mu); switch (_w5500_sockintr_ch_recv(&socket->write_ch)) { case SOCKINTR_SEND_OK: + debugf(" => sent %zu", freesize); done += freesize; break; case SOCKINTR_SEND_TIMEOUT: + debugf(" => ACK timeout"); return -NET_EACK_TIMEOUT; case SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT: assert_notreached("send both OK and timed out?"); @@ -642,10 +680,12 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, s assert_notreached("invalid write_ch bits"); } } + debugf(" => send finished"); return done; } -static void w5500_tcp_set_read_deadline(implements_net_stream_conn *_socket, uint64_t ns) { +static void w5500_tcp_set_read_deadline(struct _w5500_socket *socket, uint64_t ns) { + debugf("tcp_conn.set_read_deadline(%"PRIu64")", ns); ASSERT_SELF(stream_conn, TCP); socket->read_deadline_ns = ns; } @@ -655,29 +695,38 @@ static void w5500_tcp_alarm_handler(void *_arg) { cr_sema_signal_from_intrhandler(&socket->read_sema); } -static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, size_t count) { +static ssize_t w5500_tcp_readv(struct _w5500_socket *socket, const struct iovec *iov, int iovcnt) { + assert(iov); + assert(iovcnt > 0); + size_t count = 0; + for (int i = 0; i < iovcnt; i++) + count += iov[i].iov_len; + debugf("tcp_conn.read(%zu)", count); ASSERT_SELF(stream_conn, TCP); - assert(buf); - assert(count); + if (count == 0) + return 0; - struct alarmclock_trigger trigger = {0}; + struct alarmclock_trigger trigger = {}; if (socket->read_deadline_ns) - VCALL(bootclock, add_trigger, &trigger, - socket->read_deadline_ns, - w5500_tcp_alarm_handler, - socket); + LO_CALL(bootclock, add_trigger, &trigger, + socket->read_deadline_ns, + w5500_tcp_alarm_handler, + socket); /* Wait until there is data to read. */ uint16_t avail = 0; for (;;) { if (!socket->read_open) { - VCALL(bootclock, del_trigger, &trigger); + LO_CALL(bootclock, del_trigger, &trigger); + debugf(" => soft closed"); return -NET_ECLOSED; } - if (socket->read_deadline_ns && socket->read_deadline_ns <= VCALL(bootclock, get_time_ns)) { - VCALL(bootclock, del_trigger, &trigger); + if (socket->read_deadline_ns && socket->read_deadline_ns <= LO_CALL(bootclock, get_time_ns)) { + LO_CALL(bootclock, del_trigger, &trigger); + debugf(" => recv timeout"); return -NET_ERECV_TIMEOUT; } + cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_CLOSE_WAIT: @@ -685,7 +734,9 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si case STATE_TCP_FIN_WAIT: break; /* OK */ default: - VCALL(bootclock, del_trigger, &trigger); + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); + debugf(" => hard closed"); return -NET_ECLOSED; } @@ -694,33 +745,40 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si /* We have data to read. */ break; if (state == STATE_TCP_CLOSE_WAIT) { - VCALL(bootclock, del_trigger, &trigger); - return 0; /* EOF */ + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); + debugf(" => EOF"); + return 0; } + cr_mutex_unlock(&chip->mu); cr_sema_wait(&socket->read_sema); } assert(avail); + debugf(" => received %"PRIu16" bytes", avail); uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); /* Read the data. */ if ((size_t)avail > count) avail = count; - w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), buf, avail); + w5500ll_readv(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), iov, iovcnt, avail); /* Tell the chip that we read the data. */ w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail)); w5500_socket_cmd(socket, CMD_RECV); /* Return. */ - VCALL(bootclock, del_trigger, &trigger); + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); return avail; } -static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr) { +static int w5500_tcp_close_inner(struct _w5500_socket *socket, bool rd, bool wr) { + debugf("tcp_conn.close(rd=%s, wr=%s)", rd ? "true" : "false", wr ? "true" : "false"); ASSERT_SELF(stream_conn, TCP); if (rd) socket->read_open = false; if (wr && socket->write_open) { + cr_mutex_lock(&chip->mu); w5500_socket_cmd(socket, CMD_DISCON); while (socket->write_open) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); @@ -736,28 +794,40 @@ static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr break; } } + cr_mutex_unlock(&chip->mu); } w5500_tcp_maybe_free(chip, socket); return 0; } +static int w5500_tcp_close(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, true, true); } +static int w5500_tcp_close_read(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, true, false); } +static int w5500_tcp_close_write(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, false, true); } + /* udp_conn methods ***********************************************************/ -static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, size_t count, +static ssize_t w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t count, struct net_ip4_addr node, uint16_t port) { + debugf("udp_conn.sendto()"); ASSERT_SELF(packet_conn, UDP); assert(buf); assert(count); uint16_t bufsize = ((uint16_t)w5500ll_read_sock_reg(chip->spidev, socknum, tx_buf_size))*1024; - if (count > bufsize) + if (count > bufsize) { + debugf(" => msg too large"); return -NET_EMSGSIZE; + } for (;;) { + cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - if (state != STATE_UDP) + if (state != STATE_UDP) { + cr_mutex_unlock(&chip->mu); + debugf(" => closed"); return -NET_ECLOSED; + } uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); if (freesize >= count) @@ -765,6 +835,7 @@ static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, break; /* Wait for more buffer space. */ + cr_mutex_unlock(&chip->mu); cr_yield(); } @@ -773,15 +844,21 @@ static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port)); /* Queue data to be sent. */ uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer)); - w5500ll_write(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), buf, count); + w5500ll_writev(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((struct iovec){ + .iov_base = buf, + .iov_len = count, + }), 1, 0, 0); w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+count)); /* Submit the queue. */ w5500_socket_cmd(socket, CMD_SEND); + cr_mutex_unlock(&chip->mu); switch (_w5500_sockintr_ch_recv(&socket->write_ch)) { case SOCKINTR_SEND_OK: + debugf(" => sent"); return count; case SOCKINTR_SEND_TIMEOUT: + debugf(" => ARP timeout"); return -NET_EARP_TIMEOUT; case SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT: assert_notreached("send both OK and timed out?"); @@ -790,7 +867,8 @@ static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, } } -static void w5500_udp_set_read_deadline(implements_net_packet_conn *_socket, uint64_t ns) { +static void w5500_udp_set_recv_deadline(struct _w5500_socket *socket, uint64_t ns) { + debugf("udp_conn.set_recv_deadline(%"PRIu64")", ns); ASSERT_SELF(packet_conn, UDP); socket->read_deadline_ns = ns; } @@ -800,29 +878,33 @@ static void w5500_udp_alarm_handler(void *_arg) { cr_sema_signal_from_intrhandler(&socket->read_sema); } -static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf, size_t count, +static ssize_t w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_t count, struct net_ip4_addr *ret_node, uint16_t *ret_port) { + debugf("udp_conn.recvfrom()"); ASSERT_SELF(packet_conn, UDP); assert(buf); assert(count); - struct alarmclock_trigger trigger = {0}; + struct alarmclock_trigger trigger = {}; if (socket->read_deadline_ns) - VCALL(bootclock, add_trigger, &trigger, - socket->read_deadline_ns, - w5500_udp_alarm_handler, - socket); + LO_CALL(bootclock, add_trigger, &trigger, + socket->read_deadline_ns, + w5500_udp_alarm_handler, + socket); /* Wait until there is data to read. */ uint16_t avail = 0; for (;;) { - if (socket->read_deadline_ns && socket->read_deadline_ns <= VCALL(bootclock, get_time_ns)) { - VCALL(bootclock, del_trigger, &trigger); + if (socket->read_deadline_ns && socket->read_deadline_ns <= LO_CALL(bootclock, get_time_ns)) { + LO_CALL(bootclock, del_trigger, &trigger); + debugf(" => recv timeout"); return -NET_ERECV_TIMEOUT; } + cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_UDP) { - VCALL(bootclock, del_trigger, &trigger); + LO_CALL(bootclock, del_trigger, &trigger); + debugf(" => hard closed"); return -NET_ECLOSED; } @@ -831,6 +913,7 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf /* We have data to read. */ break; + cr_mutex_unlock(&chip->mu); cr_sema_wait(&socket->read_sema); } assert(avail >= 8); @@ -839,7 +922,10 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf * can't find in the datasheet where it describes * this; this is based off of socket.c:recvfrom(). */ uint8_t hdr[8]; - w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), hdr, sizeof(hdr)); + w5500ll_readv(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((struct iovec){ + .iov_base = hdr, + .iov_len = sizeof(hdr), + }), 1, 0); if (ret_node) { ret_node->octets[0] = hdr[0]; ret_node->octets[1] = hdr[1]; @@ -849,19 +935,25 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf if (ret_port) *ret_port = uint16be_decode(&hdr[4]); uint16_t len = uint16be_decode(&hdr[6]); + debugf(" => received %"PRIu16" bytes%s", len, len < avail-8 ? " (plus more messages)" : ""); /* Now read the actual data. */ if (count > len) count = len; - w5500ll_read(chip->spidev, ptr+8, CTL_BLOCK_SOCK(socknum, RX), buf, len); + w5500ll_readv(chip->spidev, ptr+8, CTL_BLOCK_SOCK(socknum, RX), &((struct iovec){ + .iov_base = buf, + .iov_len = len, + }), 1, 0); /* Tell the chip that we read the data. */ w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+8+len)); w5500_socket_cmd(socket, CMD_RECV); /* Return. */ - VCALL(bootclock, del_trigger, &trigger); + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); return len; } -static int w5500_udp_close(implements_net_packet_conn *_socket) { +static int w5500_udp_close(struct _w5500_socket *socket) { + debugf("udp_conn.close()"); ASSERT_SELF(packet_conn, UDP); w5500_socket_close(socket); diff --git a/libhw/w5500_ll.h b/libhw_cr/w5500_ll.h index c70da0d..2506cd2 100644 --- a/libhw/w5500_ll.h +++ b/libhw_cr/w5500_ll.h @@ -1,37 +1,45 @@ -/* libhw/w5500_ll.h - Low-level header library for the WIZnet W5500 chip +/* libhw_cr/w5500_ll.h - Low-level header library for the WIZnet W5500 chip * * Based entirely on the W5500 datasheet, v1.1.0. * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#ifndef _LIBHW_W5500_LL_H_ -#define _LIBHW_W5500_LL_H_ +#ifndef _LIBHW_CR_W5500_LL_H_ +#define _LIBHW_CR_W5500_LL_H_ +#include <alloca.h> /* for alloca() */ #include <stdint.h> /* for uint{n}_t */ #include <string.h> /* for memcmp() */ #include <libmisc/assert.h> /* for assert(), static_assert() */ #include <libmisc/endian.h> /* for uint16be_t */ -#include <libmisc/vcall.h> /* for VCALL() */ #include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */ -#include <libhw/generic/spi.h> /* for implements_spi */ +#include <libhw/generic/spi.h> /* for lo_interface spi */ + +/* Config *********************************************************************/ + +#include "config.h" + +#ifndef CONFIG_W5500_LL_DEBUG + #error config.h must define CONFIG_W5500_LL_DEBUG +#endif /* Low-level protocol built on SPI frames. ***********************************/ /* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */ /* Part 1: Block ID. */ -#define CTL_MASK_BLOCK 0b11111000 -#define _CTL_BLOCK_RES 0b00000 -#define _CTL_BLOCK_REG 0b01000 -#define _CTL_BLOCK_TX 0b10000 -#define _CTL_BLOCK_RX 0b11000 +#define CTL_MASK_BLOCK 0b11111000 +#define _CTL_BLOCK_RES 0b00000 /* chip-wide registers on socknum=0, REServed on socknum>=1 */ +#define _CTL_BLOCK_REG 0b01000 /* socknum-specific registers */ +#define _CTL_BLOCK_TX 0b10000 /* socknum-specific transmit buffer */ +#define _CTL_BLOCK_RX 0b11000 /* socknum-specific receive buffer */ #define CTL_BLOCK_SOCK(n,part) (((n)<<5)|(_CTL_BLOCK_##part)) -#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES) +#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES) /* Part 2: R/W. */ #define CTL_MASK_RW 0b100 @@ -40,50 +48,97 @@ /* Part 3: Operating mode. */ #define CTL_MASK_OM 0b11 -#define CTL_OM_VDM 0b00 -#define CTL_OM_FDM1 0b01 -#define CTL_OM_FDM2 0b10 -#define CTL_OM_FDM4 0b11 +#define CTL_OM_VDM 0b00 /* variable-length data mode */ +#define CTL_OM_FDM1 0b01 /* fixed-length data mode: 1 byte data length */ +#define CTL_OM_FDM2 0b10 /* fixed-length data mode: 2 byte data length */ +#define CTL_OM_FDM4 0b11 /* fixed-length data mode: 4 byte data length */ + +#if CONFIG_W5500_LL_DEBUG +static char *_ctl_block_part_strs[] = { + "RES", + "REG", + "TX", + "RX", +}; +#define PRI_ctl_block "CTL_BLOCK_SOCK(%d, %s)" +#define ARG_ctl_block(b) (((b)>>5) & 0b111), _ctl_block_part_strs[((b)>>3)&0b11] +#endif /* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex. * Lame. */ static inline void -w5500ll_write(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { - assert(spidev); +#if CONFIG_W5500_LL_DEBUG +#define w5500ll_writev(...) _w5500ll_writev(__func__, __VA_ARGS__) +_w5500ll_writev(const char *func, +#else +w5500ll_writev( +#endif + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct iovec *iov, int iovcnt, + size_t skip, size_t max) +{ + assert(!LO_IS_NULL(spidev)); assert((block & ~CTL_MASK_BLOCK) == 0); - assert(data); - assert(data_len); - - uint8_t header[3] = { + assert(iov); + assert(iovcnt > 0); +#if CONFIG_W5500_LL_DEBUG + n_debugf(W5500_LL, + "%s(): w5500ll_write(spidev, addr=%#04x, block="PRI_ctl_block", iov, iovcnt=%d)", + func, addr, ARG_ctl_block(block), iovcnt); +#endif + + uint8_t header[] = { (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF), (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, }; - struct bidi_iovec iov[] = { - {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)}, - {.iov_read_dst = NULL, .iov_write_src = data, .iov_len = data_len}, + int inner_cnt = 1+io_slice_cnt(iov, iovcnt, skip, max); + struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*inner_cnt); + inner[0] = (struct duplex_iovec){ + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = header, + .iov_len = sizeof(header), }; - VCALL(spidev, readwritev, iov, 2); + io_slice_wr_to_duplex(&inner[1], iov, iovcnt, skip, max); + LO_CALL(spidev, readwritev, inner, inner_cnt); } static inline void -w5500ll_read(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { - assert(spidev); +#if CONFIG_W5500_LL_DEBUG +#define w5500ll_readv(...) _w5500ll_read(__func__, __VA_ARGS__) +_w5500ll_readv(const char *func, +#else +w5500ll_readv( +#endif + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct iovec *iov, int iovcnt, + size_t max) +{ + assert(!LO_IS_NULL(spidev)); assert((block & ~CTL_MASK_BLOCK) == 0); - assert(data); - assert(data_len); - - uint8_t header[3] = { + assert(iov); + assert(iovcnt > 0); +#if CONFIG_W5500_LL_DEBUG + n_debugf(W5500_LL, + "%s(): w5500ll_read(spidev, addr=%#04x, block="PRI_ctl_block", iov, iovcnt=%d)", + func, addr, ARG_ctl_block(block), iovcnt); +#endif + + uint8_t header[] = { (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF), (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, }; - struct bidi_iovec iov[] = { - {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)}, - {.iov_read_dst = data, .iov_write_src = NULL, .iov_len = data_len}, + int inner_cnt = 1+io_slice_cnt(iov, iovcnt, 0, max); + struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*inner_cnt); + inner[0] = (struct duplex_iovec){ + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = header, + .iov_len = sizeof(header), }; - VCALL(spidev, readwritev, iov, 2); + io_slice_rd_to_duplex(&inner[1], iov, iovcnt, 0, max); + LO_CALL(spidev, readwritev, inner, inner_cnt); } /* Common chip-wide registers. ***********************************************/ @@ -222,7 +277,7 @@ static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); #define SOCKINTR_SEND_TIMEOUT ((uint8_t)1<<3) /* ARP or TCP */ #define SOCKINTR_RECV_DAT ((uint8_t)1<<2) /* received data */ #define SOCKINTR_RECV_FIN ((uint8_t)1<<1) /* received FIN */ -#define SOCKINTR_CONN ((uint8_t)1<<1) /* first for SYN, then when SOCKMODE_ESTABLISHED */ +#define SOCKINTR_CONN ((uint8_t)1<<0) /* first for SYN, then when SOCKMODE_ESTABLISHED */ #define STATE_CLOSED ((uint8_t)0x00) @@ -327,11 +382,14 @@ static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); #define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \ typeof((blocktyp){}.field) lval = val; \ - w5500ll_write(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &lval, \ - sizeof(lval)); \ + w5500ll_writev(spidev, \ + offsetof(blocktyp, field), \ + blockid, \ + &((struct iovec){ \ + &lval, \ + sizeof(lval), \ + }), \ + 1, 0, 0); \ } while (0) /* The datasheet tells us that multi-byte reads are non-atomic and @@ -339,19 +397,25 @@ static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); * until getting the same value". */ #define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \ typeof((blocktyp){}.field) val; \ - w5500ll_read(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &val, \ - sizeof(val)); \ + w5500ll_readv(spidev, \ + offsetof(blocktyp, field), \ + blockid, \ + &((struct iovec){ \ + .iov_base = &val, \ + .iov_len = sizeof(val), \ + }), \ + 1, 0); \ if (sizeof(val) > 1) \ for (;;) { \ typeof(val) val2; \ - w5500ll_read(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &val2, \ - sizeof(val)); \ + w5500ll_readv(spidev, \ + offsetof(blocktyp, field), \ + blockid, \ + &((struct iovec){ \ + .iov_base = &val2, \ + .iov_len = sizeof(val), \ + }), \ + 1, 0); \ if (memcmp(&val2, &val, sizeof(val)) == 0) \ break; \ val = val2; \ @@ -359,4 +423,4 @@ static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); val; \ }) -#endif /* _LIBHW_W5500_LL_H_ */ +#endif /* _LIBHW_CR_W5500_LL_H_ */ diff --git a/libhw_generic/CMakeLists.txt b/libhw_generic/CMakeLists.txt index 9c88937..603f30b 100644 --- a/libhw_generic/CMakeLists.txt +++ b/libhw_generic/CMakeLists.txt @@ -1,15 +1,19 @@ # libhw_generic/CMakeLists.txt - UAPI device interfaces # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libhw_generic INTERFACE) -target_include_directories(libhw_generic SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(libhw_generic PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_link_libraries(libhw_generic INTERFACE libmisc - libcr + libobj ) target_sources(libhw_generic INTERFACE alarmclock.c + io.c + net.c ) + +add_lib_test(libhw_generic test_io) diff --git a/libhw_generic/alarmclock.c b/libhw_generic/alarmclock.c index a16f2f6..31fbbaf 100644 --- a/libhw_generic/alarmclock.c +++ b/libhw_generic/alarmclock.c @@ -1,24 +1,9 @@ -/* libhw_generic/alarmclock.c - Device-independent <libhw/generic/alarmclock.h> utilities +/* libhw_generic/alarmclock.c - Device-independent <libhw/generic/alarmclock.h> storage * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <libcr/coroutine.h> -#include <libmisc/vcall.h> - #include <libhw/generic/alarmclock.h> -static void alarmclock_sleep_intrhandler(void *_arg) { - cid_t cid = *(cid_t *)_arg; - cr_unpause_from_intrhandler(cid); -} - -void alarmclock_sleep_until_ns(implements_alarmclock *clock, uint64_t abstime_ns) { - bool saved = cr_save_and_disable_interrupts(); - cid_t cid = cr_getcid(); - struct alarmclock_trigger trigger; - VCALL(clock, add_trigger, &trigger, abstime_ns, alarmclock_sleep_intrhandler, &cid); - cr_pause_and_yield(); - cr_restore_interrupts(saved); -} +lo_interface alarmclock bootclock = {0}; diff --git a/libhw_generic/include/libhw/generic/alarmclock.h b/libhw_generic/include/libhw/generic/alarmclock.h index a9d816b..02789f5 100644 --- a/libhw_generic/include/libhw/generic/alarmclock.h +++ b/libhw_generic/include/libhw/generic/alarmclock.h @@ -1,6 +1,6 @@ /* libhw/generic/alarmclock.h - Device-independent alarmclock definitions * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -11,7 +11,7 @@ #include <stdint.h> /* for uint{n}_t and UINT{n}_C */ #include <libmisc/private.h> -#include <libmisc/vcall.h> +#include <libobj/obj.h> /* Useful constants ***********************************************************/ @@ -23,71 +23,65 @@ struct alarmclock_trigger; struct alarmclock_trigger { - BEGIN_PRIVATE(LIBHW_GENERIC_ALARMCLOCK_H) + BEGIN_PRIVATE(LIBHW_GENERIC_ALARMCLOCK_H); void *alarmclock; struct alarmclock_trigger *prev, *next; uint64_t fire_at_ns; void (*cb)(void *); void *cb_arg; - END_PRIVATE(LIBHW_GENERIC_ALARMCLOCK_H) + END_PRIVATE(LIBHW_GENERIC_ALARMCLOCK_H); }; /* Interface ******************************************************************/ -struct alarmclock_vtable; - -typedef struct { - struct alarmclock_vtable *vtable; -} implements_alarmclock; - -struct alarmclock_vtable { - /** - * (2⁶⁴-1 nanoseconds is more than 500 years; there is little - * risk of this overflowing) - */ - uint64_t (*get_time_ns)(implements_alarmclock *self); - - /** - * Returns true on error. - * - * Implementations may return an error if fire_at_ns is more - * than UINT32_MAX µs (72 minutes) in the future. - * - * If fire_at_ns is in the past, then it will fire - * immediately. - */ - bool (*add_trigger)(implements_alarmclock *self, struct alarmclock_trigger *trigger, - uint64_t fire_at_ns, - void (*cb)(void *), - void *cb_arg); - - void (*del_trigger)(implements_alarmclock *self, struct alarmclock_trigger *trigger); -}; +#define alarmclock_LO_IFACE \ + /** \ + * (2⁶⁴-1 nanoseconds is more than 500 years; there is little \ + * risk of this overflowing) \ + */ \ + LO_FUNC(uint64_t, get_time_ns) \ + \ + /** \ + * Returns true on error. \ + * \ + * Implementations may return an error if fire_at_ns is more \ + * than UINT32_MAX µs (72 minutes) in the future. \ + * \ + * If fire_at_ns is in the past, then it will fire \ + * immediately. \ + */ \ + LO_FUNC(bool, add_trigger, struct alarmclock_trigger *trigger, \ + uint64_t fire_at_ns, \ + void (*cb)(void *), \ + void *cb_arg) \ + \ + LO_FUNC(void, del_trigger, struct alarmclock_trigger *trigger) +LO_INTERFACE(alarmclock); /* Utilities ******************************************************************/ -void alarmclock_sleep_until_ns(implements_alarmclock *clock, uint64_t abstime_ns); +void alarmclock_sleep_until_ns(lo_interface alarmclock clock, uint64_t abstime_ns); -static inline void alarmclock_sleep_for_ns(implements_alarmclock *clock, uint64_t delta_ns) { - alarmclock_sleep_until_ns(clock, VCALL(clock, get_time_ns) + delta_ns); +static inline void alarmclock_sleep_for_ns(lo_interface alarmclock clock, uint64_t delta_ns) { + alarmclock_sleep_until_ns(clock, LO_CALL(clock, get_time_ns) + delta_ns); } -static inline void alarmclock_sleep_for_us(implements_alarmclock *clock, uint64_t delta_us) { +static inline void alarmclock_sleep_for_us(lo_interface alarmclock clock, uint64_t delta_us) { alarmclock_sleep_for_ns(clock, delta_us * (NS_PER_S/US_PER_S)); } -static inline void alarmclock_sleep_for_ms(implements_alarmclock *clock, uint64_t delta_ms) { +static inline void alarmclock_sleep_for_ms(lo_interface alarmclock clock, uint64_t delta_ms) { alarmclock_sleep_for_ns(clock, delta_ms * (NS_PER_S/MS_PER_S)); } -static inline void alarmclock_sleep_for_s(implements_alarmclock *clock, uint64_t delta_s) { +static inline void alarmclock_sleep_for_s(lo_interface alarmclock clock, uint64_t delta_s) { alarmclock_sleep_for_ns(clock, delta_s * NS_PER_S); } /* Globals ********************************************************************/ -extern implements_alarmclock *bootclock; +extern lo_interface alarmclock bootclock; #define sleep_until_ns(t) alarmclock_sleep_until_ns(bootclock, t) #define sleep_for_ns(t) alarmclock_sleep_for_ns(bootclock, t) diff --git a/libhw_generic/include/libhw/generic/io.h b/libhw_generic/include/libhw/generic/io.h new file mode 100644 index 0000000..62ddbb3 --- /dev/null +++ b/libhw_generic/include/libhw/generic/io.h @@ -0,0 +1,118 @@ +/* libhw/generic/io.h - Device-independent I/O definitions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_GENERIC_IO_H_ +#define _LIBHW_GENERIC_IO_H_ + +#include <stddef.h> /* for size_t */ +#include <stdint.h> /* for uintptr_t */ +#include <sys/types.h> /* for ssize_t */ + +#include <libobj/obj.h> + +/* structs ********************************************************************/ + +#if __unix__ +#include <sys/uio.h> +#else +struct iovec { + void *iov_base; + size_t iov_len; +}; +#endif + +#define IOVEC_DISCARD ((void*)(~((uintptr_t)0))) + +struct duplex_iovec { + /** + * NULL is a valid pointer value in iov_read_to and + * iov_write_from. To skip a read or write, use the special + * value IOVEC_DISCARD. + */ + void *iov_read_to; + void *iov_write_from; + size_t iov_len; +}; + +/* utilities ******************************************************************/ + +/* slice iovec lists */ +int io_slice_cnt ( const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +void io_slice (struct iovec *dst, const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +int io_duplex_slice_cnt( const struct duplex_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +void io_duplex_slice (struct duplex_iovec *dst, const struct duplex_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); + +/* convert iovec lists */ +void io_rd_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int iovcnt); +void io_wr_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int iovcnt); + +/* slice and convert in one go */ +void io_slice_rd_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +void io_slice_wr_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); + +/* basic interfaces ***********************************************************/ + +/** + * Return bytes-read on success, 0 on EOF, -errno on error; a short + * read is *not* an error. + */ +#define io_reader_LO_IFACE \ + LO_FUNC(ssize_t, readv, const struct iovec *iov, int iovcnt) +LO_INTERFACE(io_reader); +#define io_readv(r, iov, iovcnt) LO_CALL(r, readv, iov, iovcnt) +#define io_read(r, buf, count) LO_CALL(r, readv, &((struct iovec){.iov_base = buf, .iov_len = count}), 1) + +/** + * Return `count` on success, -errno on error; a short write *is* an + * error. + * + * Writes are *not* guaranteed to be atomic, so if you have concurrent + * writers then you should arrange for a mutex to protect the writer. + */ +#define io_writer_LO_IFACE \ + LO_FUNC(ssize_t, writev, const struct iovec *iov, int iovcnt) +LO_INTERFACE(io_writer); +#define io_writev(w, iov, iovcnt) LO_CALL(w, writev, iov, iovcnt) +#define io_write(w, buf, count) LO_CALL(w, writev, &((struct iovec){.iov_base = buf, .iov_len = count}), 1) + +/** + * Return 0 on success, -errno on error. + */ +#define io_closer_LO_IFACE \ + LO_FUNC(int, close) +LO_INTERFACE(io_closer); +#define io_close(c) LO_CALL(c, close) + +/** + * All methods return 0 on success, -errno on error. + */ +#define io_bidi_closer_LO_IFACE \ + LO_NEST(io_closer) \ + LO_FUNC(int, close_read) \ + LO_FUNC(int, close_write) +LO_INTERFACE(io_bidi_closer); +#define io_close_read(c) LO_CALL(c, close_read) +#define io_close_write(c) LO_CALL(c, close_write) + +/** + * Return bytes-read and bytes-written on success, -errno on error; a + * short read/write *is* an error. + */ +#define io_duplex_readwriter_LO_IFACE \ + LO_FUNC(void, readwritev, const struct duplex_iovec *iov, int iovcnt) +LO_INTERFACE(io_duplex_readwriter); + +#define io_readwritev(rw, iov, iovcnt) \ + LO_CALL(rw, readwritev, iov, iovcnt) + +/* aggregate interfaces *******************************************************/ + +#define io_readwriter_LO_IFACE \ + LO_NEST(io_reader) \ + LO_NEST(io_writer) +LO_INTERFACE(io_readwriter); + +#endif /* _LIBHW_GENERIC_IO_H_ */ diff --git a/libhw_generic/include/libhw/generic/net.h b/libhw_generic/include/libhw/generic/net.h index 150d199..4af574b 100644 --- a/libhw_generic/include/libhw/generic/net.h +++ b/libhw_generic/include/libhw/generic/net.h @@ -1,17 +1,20 @@ /* libhw/generic/net.h - Device-independent network definitions * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _LIBHW_GENERIC_NET_H_ #define _LIBHW_GENERIC_NET_H_ +#include <inttypes.h> /* for PRI{u,x}{n} */ #include <stdbool.h> /* for bool */ #include <stddef.h> /* for size_t */ #include <stdint.h> /* for uint{n}_t} */ #include <sys/types.h> /* for ssize_t */ +#include <libhw/generic/io.h> + /* Errnos *********************************************************************/ #define NET_EOTHER 1 @@ -22,6 +25,8 @@ #define NET_ECLOSED 6 #define NET_EMSGSIZE 7 +const char *net_strerror(int net_errno); + /* Address types **************************************************************/ struct net_ip4_addr { @@ -31,103 +36,92 @@ struct net_ip4_addr { static const struct net_ip4_addr net_ip4_addr_broadcast = {{255, 255, 255, 255}}; static const struct net_ip4_addr net_ip4_addr_zero = {{0, 0, 0, 0}}; +#define PRI_net_ip4_addr "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8 +#define ARG_net_ip4_addr(addr) (addr).octets[0], \ + (addr).octets[1], \ + (addr).octets[2], \ + (addr).octets[3] + struct net_eth_addr { unsigned char octets[6]; }; -/* Streams (e.g. TCP) *********************************************************/ +#define PRI_net_eth_addr "%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8 +#define ARG_net_eth_addr(addr) (addr).octets[0], \ + (addr).octets[1], \ + (addr).octets[2], \ + (addr).octets[3], \ + (addr).octets[4], \ + (addr).octets[5] -struct net_stream_listener_vtable; -struct net_stream_conn_vtable; - -typedef struct { - struct net_stream_listener_vtable *vtable; -} implements_net_stream_listener; - -typedef struct { - struct net_stream_conn_vtable *vtable; -} implements_net_stream_conn; - -struct net_stream_listener_vtable { - /** - * It is invalid to accept() a new connection if an existing - * connection is still open. - */ - implements_net_stream_conn *(*accept)(implements_net_stream_listener *self); - - /** - * The net_stream_conn returned from accept() may still be - * valid after the listener is closed. - * - * Return 0 on success, -errno on error. - */ - int (*close)(implements_net_stream_listener *self); -}; +/* Streams (e.g. TCP) *********************************************************/ -struct net_stream_conn_vtable { - /** - * Return bytes-read on success, 0 on EOF, -errno on error; a - * short read is *not* an error. - */ - ssize_t (*read)(implements_net_stream_conn *self, - void *buf, size_t count); - - /** - * Set a timestamp after which calls to read() will return - * NET_ETIMEDOUT. The timestamp is in nanoseconds on the - * system monotonic clock, which is usually (on pico-sdk and - * on the Linux kernel) nanoseconds-since-boot. - * - * A zero value disables the deadline. - * - * (2⁶⁴-1 nanoseconds is more than 500 years; there is little - * risk of this overflowing) - */ - void (*set_read_deadline)(implements_net_stream_conn *self, - uint64_t ns_since_boot); - - /** - * Return `count` on success, -errno on error; a short write *is* an - * error. - * - * Writes are *not* guaranteed to be atomic (as this would be - * expensive to implement), so if you have concurrent writers then you - * should arrange for a mutex to protect the connection. - */ - ssize_t (*write)(implements_net_stream_conn *self, - void *buf, size_t count); - - /** - * Return 0 on success, -errno on error. - */ - int (*close)(implements_net_stream_conn *self, - bool rd, bool wr); -}; +lo_interface net_stream_conn; + +#define net_stream_listener_LO_IFACE \ + /** \ + * It is invalid to accept() a new connection if an existing \ + * connection is still open. \ + */ \ + LO_FUNC(lo_interface net_stream_conn, accept) \ + \ + /** \ + * The net_stream_conn returned from accept() may still be \ + * valid after the listener is closed. \ + */ \ + LO_NEST(io_closer) +LO_INTERFACE(net_stream_listener); + +#define net_stream_conn_LO_IFACE \ + LO_NEST(io_readwriter) \ + LO_NEST(io_bidi_closer) \ + \ + /** \ + * Set a timestamp after which calls to read() will return \ + * NET_ETIMEDOUT. The timestamp is in nanoseconds on the \ + * system monotonic clock, which is usually (on pico-sdk and \ + * on the Linux kernel) nanoseconds-since-boot. \ + * \ + * A zero value disables the deadline. \ + * \ + * (2⁶⁴-1 nanoseconds is more than 500 years; there is little \ + * risk of this overflowing) \ + */ \ + LO_FUNC(void, set_read_deadline, uint64_t ns_since_boot) +LO_INTERFACE(net_stream_conn); /* Packets (e.g. UDP) *********************************************************/ -struct net_packet_conn_vtable; - -typedef struct { - struct net_packet_conn_vtable *vtable; -} implements_net_packet_conn; - -struct net_packet_conn_vtable { - ssize_t (*sendto )(implements_net_packet_conn *self, - void *buf, size_t len, - struct net_ip4_addr node, uint16_t port); - /** - * @return The full length of the message, which may be more - * than the given `len` (as if the Linux MSG_TRUNC flag were - * given). - */ - ssize_t (*recvfrom)(implements_net_packet_conn *self, - void *buf, size_t len, - struct net_ip4_addr *ret_node, uint16_t *ret_port); - void (*set_read_deadline)(implements_net_packet_conn *self, - uint64_t ns_since_boot); - int (*close )(implements_net_packet_conn *self); -}; +#define net_packet_conn_LO_IFACE \ + LO_FUNC(ssize_t, sendto, \ + void *buf, size_t len, \ + struct net_ip4_addr node, uint16_t port) \ + \ + /** \ + * @return The full length of the message, which may be more \ + * than the given `len` (as if the Linux MSG_TRUNC flag were \ + * given). \ + */ \ + LO_FUNC(ssize_t, recvfrom, \ + void *buf, size_t len, \ + struct net_ip4_addr *ret_node, uint16_t *ret_port) \ + \ + /** \ + * Set a timestamp after which calls to recvfrom() will return \ + * NET_ETIMEDOUT. The timestamp is in nanoseconds on the \ + * system monotonic clock, which is usually (on pico-sdk and \ + * on the Linux kernel) nanoseconds-since-boot. \ + * \ + * A zero value disables the deadline. \ + * \ + * (2⁶⁴-1 nanoseconds is more than 500 years; there is little \ + * risk of this overflowing) \ + */ \ + LO_FUNC(void, set_recv_deadline, \ + uint64_t ns_since_boot) \ + \ + LO_NEST(io_closer) +LO_INTERFACE(net_packet_conn); /* Interfaces *****************************************************************/ @@ -137,20 +131,14 @@ struct net_iface_config { struct net_ip4_addr subnet_mask; }; -struct net_iface_vtable; - -typedef struct { - struct net_iface_vtable *vtable; -} implements_net_iface; - -struct net_iface_vtable { - struct net_eth_addr (*hwaddr )(implements_net_iface *); - void (*ifup )(implements_net_iface *, struct net_iface_config); - void (*ifdown )(implements_net_iface *); - - implements_net_stream_listener *(*tcp_listen)(implements_net_iface *, uint16_t local_port); - implements_net_stream_conn *(*tcp_dial )(implements_net_iface *, struct net_ip4_addr remote_node, uint16_t remote_port); - implements_net_packet_conn *(*udp_conn )(implements_net_iface *, uint16_t local_port); -}; +#define net_iface_LO_IFACE \ + LO_FUNC(struct net_eth_addr , hwaddr ) \ + LO_FUNC(void , ifup , struct net_iface_config) \ + LO_FUNC(void , ifdown ) \ + \ + LO_FUNC(lo_interface net_stream_listener, tcp_listen, uint16_t local_port) \ + LO_FUNC(lo_interface net_stream_conn , tcp_dial , struct net_ip4_addr remote_node, uint16_t remote_port) \ + LO_FUNC(lo_interface net_packet_conn , udp_conn , uint16_t local_port) +LO_INTERFACE(net_iface); #endif /* _LIBHW_GENERIC_NET_H_ */ diff --git a/libhw_generic/include/libhw/generic/spi.h b/libhw_generic/include/libhw/generic/spi.h index 2207a2c..4bbf649 100644 --- a/libhw_generic/include/libhw/generic/spi.h +++ b/libhw_generic/include/libhw/generic/spi.h @@ -1,6 +1,6 @@ /* libhw/generic/spi.h - Device-independent SPI definitions * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -9,6 +9,8 @@ #include <stddef.h> /* for size_t */ +#include <libhw/generic/io.h> + enum spi_mode { SPI_MODE_0 = 0, /* clk_polarity=0 (idle low), clk_phase=0 (sample on rise) */ SPI_MODE_1 = 1, /* clk_polarity=0 (idle low), clk_phase=1 (sample on fall) */ @@ -16,18 +18,6 @@ enum spi_mode { SPI_MODE_3 = 3, /* clk_polarity=1 (idle high), clk_phase=1 (sample on fall) */ }; -struct bidi_iovec { - void *iov_read_dst; - void *iov_write_src; - size_t iov_len; -}; - -struct spi_vtable; - -typedef struct { - struct spi_vtable *vtable; -} implements_spi; - /* This API assumes that an SPI frame is a multiple of 8-bits. * * It is my understanding that this is a common constraint of SPI @@ -40,8 +30,8 @@ typedef struct { * octets; so we have no need for an API that allows a * non-multiple-of-8 number of bits. */ -struct spi_vtable { - void (*readwritev)(implements_spi *, const struct bidi_iovec *iov, int iovcnt); -}; +#define spi_LO_IFACE \ + LO_NEST(io_duplex_readwriter) +LO_INTERFACE(spi); #endif /* _LIBHW_GENERIC_SPI_H_ */ diff --git a/libhw_generic/io.c b/libhw_generic/io.c new file mode 100644 index 0000000..4ebff10 --- /dev/null +++ b/libhw_generic/io.c @@ -0,0 +1,98 @@ +/* libhw_generic/io.c - Utilities for device-independent I/O definitions + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/assert.h> + +#include <libhw/generic/io.h> + +#define IOV_ITER(ACTION) \ + assert(src_cnt >= 0); \ + assert(src_cnt == 0 || src); \ + \ + const size_t byte_end = byte_beg + byte_max_cnt; \ + int j = 0; \ + size_t byte_pos = 0; \ + for (int i = 0; \ + i < src_cnt && (byte_max_cnt == 0 || byte_pos < byte_end); \ + i++) { \ + size_t off = 0, len; \ + if (byte_pos < byte_beg) { \ + if (byte_beg - byte_pos >= src[i].iov_len) { \ + byte_pos += src[i].iov_len; \ + continue; \ + } \ + off = byte_beg-byte_pos; \ + len = src[i].iov_len-off; \ + byte_pos = byte_beg; \ + } else { \ + len = src[i].iov_len; \ + } \ + if (byte_max_cnt && byte_end - byte_pos < len) \ + len = byte_end - byte_pos; \ + do { ACTION } while (0); \ + byte_pos += len; \ + j++; \ + } + +int io_slice_cnt(const struct iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + IOV_ITER(); + return j; +} + +int io_duplex_slice_cnt(const struct duplex_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + IOV_ITER(); + return j; +} + +void io_slice(struct iovec *dst, const struct iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct iovec){ + .iov_base = src[i].iov_base+off, + .iov_len = len, + }); + ); +} +void io_slice_duplex(struct duplex_iovec *dst, const struct duplex_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct duplex_iovec){ + .iov_read_to = src[i].iov_read_to+off, + .iov_write_from = src[i].iov_write_from+off, + .iov_len = len, + }); + ); +} + +void io_slice_rd_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct duplex_iovec){ + .iov_read_to = src[i].iov_base+off, + .iov_write_from = IOVEC_DISCARD, + .iov_len = len, + }); + ); +} + +void io_slice_wr_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct duplex_iovec){ + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = src[i].iov_base+off, + .iov_len = len, + }); + ); +} + +void io_rd_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int iovcnt) { + io_slice_rd_to_duplex(dst, src, iovcnt, 0, 0); +} + +void io_wr_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int iovcnt) { + io_slice_wr_to_duplex(dst, src, iovcnt, 0, 0); +} diff --git a/libhw_generic/net.c b/libhw_generic/net.c new file mode 100644 index 0000000..e2785ae --- /dev/null +++ b/libhw_generic/net.c @@ -0,0 +1,31 @@ +/* libhw_generic/net.c - Device-independent <libhw/generic/net.h> utilities + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/assert.h> + +#include <libhw/generic/net.h> + +#define NET_EOTHER 1 +#define NET_EARP_TIMEOUT 2 +#define NET_EACK_TIMEOUT 3 +#define NET_ERECV_TIMEOUT 4 +#define NET_ETHREAD 5 +#define NET_ECLOSED 6 +#define NET_EMSGSIZE 7 + +const char *net_strerror(int net_errno) { + switch (net_errno) { + case NET_EOTHER: return "unknown error"; + case NET_EARP_TIMEOUT: return "ARP timeout"; + case NET_EACK_TIMEOUT: return "TCP ACK timeout"; + case NET_ERECV_TIMEOUT: return "receive timeout"; + case NET_ETHREAD: return "thread error"; + case NET_ECLOSED: return "already closed"; + case NET_EMSGSIZE: return "message too long"; + default: + assert_notreached("invalid net_errno"); + } +} diff --git a/libhw_generic/tests/test.h b/libhw_generic/tests/test.h new file mode 120000 index 0000000..2fb1bd5 --- /dev/null +++ b/libhw_generic/tests/test.h @@ -0,0 +1 @@ +../../libmisc/tests/test.h
\ No newline at end of file diff --git a/libhw_generic/tests/test_io.c b/libhw_generic/tests/test_io.c new file mode 100644 index 0000000..0d6df11 --- /dev/null +++ b/libhw_generic/tests/test_io.c @@ -0,0 +1,57 @@ +/* libhw_generic/tests/test_io.c - Tests for <libmisc/io.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> + +#include <libhw/generic/io.h> + +#include "test.h" + +int main() { + char data[] = "abcdefghijklmnopqrstuvwxyz"; + struct iovec src[] = { + {.iov_base = &data[1], .iov_len=3}, /* "bcd" */ + {.iov_base = &data[20], .iov_len=4}, /* "uvwx" */ + }; + const int src_cnt = sizeof(src)/sizeof(src[0]); + + struct duplex_iovec dst[2]; + +#define TC(start, max, ...) do { \ + char *exp[] = {__VA_ARGS__}; \ + int exp_cnt = sizeof(exp)/sizeof(exp[0]); \ + int act_cnt = io_slice_cnt(src, src_cnt, start, max); \ + test_assert(act_cnt == exp_cnt); \ + memset(dst, 0, sizeof(dst)); \ + io_slice_wr_to_duplex(dst, src, src_cnt, start, max); \ + for (int i = 0; i < act_cnt; i++) { \ + test_assert(dst[i].iov_read_to == IOVEC_DISCARD); \ + test_assert(dst[i].iov_write_from != IOVEC_DISCARD); \ + test_assert(dst[i].iov_len == strlen(exp[i])); \ + test_assert(memcmp(dst[i].iov_write_from, exp[i], dst[i].iov_len) == 0); \ + } \ + } while (0) + + TC(0, 0, /* => */ "bcd", "uvwx"); + TC(1, 0, /* => */ "cd", "uvwx"); + TC(2, 0, /* => */ "d", "uvwx"); + TC(3, 0, /* => */ "uvwx"); + TC(4, 0, /* => */ "vwx"); + TC(5, 0, /* => */ "wx"); + TC(6, 0, /* => */ "x"); + TC(7, 0, /* => */ ); + + TC(0, 2, /* => */ "bc"); + TC(1, 2, /* => */ "cd"); + TC(2, 2, /* => */ "d", "u"); + TC(3, 2, /* => */ "uv"); + TC(4, 2, /* => */ "vw"); + TC(5, 2, /* => */ "wx"); + TC(6, 2, /* => */ "x"); + TC(7, 2, /* => */ ); + + return 0; +} diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt index 02b19d5..4599ead 100644 --- a/libmisc/CMakeLists.txt +++ b/libmisc/CMakeLists.txt @@ -1,20 +1,22 @@ -# libmisc/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions +# libmisc/CMakeLists.txt - Low-level C programming utilities; sort of an augmented "libc" # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libmisc INTERFACE) -target_include_directories(libmisc SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(libmisc PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_sources(libmisc INTERFACE assert.c + intercept.c log.c ) target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>") add_lib_test(libmisc test_assert) +add_lib_test(libmisc test_assert_min) add_lib_test(libmisc test_endian) add_lib_test(libmisc test_hash) add_lib_test(libmisc test_log) +add_lib_test(libmisc test_macro) add_lib_test(libmisc test_private) add_lib_test(libmisc test_rand) -add_lib_test(libmisc test_vcall) diff --git a/libmisc/assert.c b/libmisc/assert.c index 8231c85..fdd8154 100644 --- a/libmisc/assert.c +++ b/libmisc/assert.c @@ -1,11 +1,10 @@ /* libmisc/assert.c - More assertions * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #include <stdbool.h> /* for bool, true, false */ -#include <stdlib.h> /* for abort() */ #define LOG_NAME ASSERT #include <libmisc/log.h> /* for errorf() */ @@ -13,7 +12,7 @@ #include <libmisc/assert.h> #ifndef NDEBUG -[[noreturn, gnu::weak]] +#define __lm_printf __lm_light_printf void __assert_msg_fail(const char *expr, const char *file, unsigned int line, const char *func, const char *msg) { @@ -26,6 +25,6 @@ void __assert_msg_fail(const char *expr, msg ? ": " : "", msg ?: ""); in_fail = false; } - abort(); + __lm_abort(); } #endif diff --git a/libmisc/include/libmisc/_intercept.h b/libmisc/include/libmisc/_intercept.h new file mode 100644 index 0000000..a264144 --- /dev/null +++ b/libmisc/include/libmisc/_intercept.h @@ -0,0 +1,27 @@ +/* libmisc/_intercept.h - Interceptable ("weak") functions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC__INTERCEPT_H_ +#define _LIBMISC__INTERCEPT_H_ + +/* pico-sdk/newlib define these to be [[gnu:weak]] already, but + * depending on optimization options glibc might not, and GCC might + * assume it knows what they do and optimize them out. So define our + * own `__lm_` wrappers that GCC/glibc won't interfere with. + */ + +[[gnu::format(printf, 1, 2)]] +int __lm_printf(const char *format, ...); + +[[noreturn]] void __lm_abort(void); + +/* __lm_light_printf is expected to have less stack use than regular + * __lm_printf, if possible. */ + +[[gnu::format(printf, 1, 2)]] +int __lm_light_printf(const char *format, ...); + +#endif /* _LIBMISC__INTERCEPT_H_ */ diff --git a/libmisc/include/libmisc/assert.h b/libmisc/include/libmisc/assert.h index da2ba2b..8cf0735 100644 --- a/libmisc/include/libmisc/assert.h +++ b/libmisc/include/libmisc/assert.h @@ -17,7 +17,7 @@ #endif #define assert_msg(expr, msg) __assert_msg(expr, #expr, msg) /* libmisc */ -#define assert(expr) __assert_msg(expr, #expr, NULL) /* C89, POSIX-2001 */ +#define assert(expr) __assert_msg(expr, #expr, 0) /* C89, POSIX-2001 */ #define assert_notreached(msg) do { __assert_msg(0, "notreached", msg); __builtin_unreachable(); } while (0) /* libmisc */ #define static_assert _Static_assert /* C11 */ diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h index b1bc55c..75240fe 100644 --- a/libmisc/include/libmisc/endian.h +++ b/libmisc/include/libmisc/endian.h @@ -1,12 +1,13 @@ /* libmisc/endian.h - Endian-conversion helpers * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _LIBMISC_ENDIAN_H_ #define _LIBMISC_ENDIAN_H_ +#include <stddef.h> /* for size_t */ #include <stdint.h> /* for uint{n}_t */ #include <libmisc/assert.h> @@ -71,6 +72,45 @@ static inline uint32_t uint32be_unmarshal(uint32be_t in) { return uint32be_decode(in.octets); } +typedef struct { + uint8_t octets[8]; +} uint64be_t; +static_assert(sizeof(uint64be_t) == 8); + +static inline size_t uint64be_encode(uint8_t *out, uint64_t in) { + out[0] = (uint8_t)((in >> 56) & 0xFF); + out[1] = (uint8_t)((in >> 48) & 0xFF); + out[2] = (uint8_t)((in >> 40) & 0xFF); + out[3] = (uint8_t)((in >> 32) & 0xFF); + out[4] = (uint8_t)((in >> 24) & 0xFF); + out[5] = (uint8_t)((in >> 16) & 0xFF); + out[6] = (uint8_t)((in >> 8) & 0xFF); + out[7] = (uint8_t)((in >> 0) & 0xFF); + return 8; +} + +static inline uint64_t uint64be_decode(uint8_t *in) { + return (((uint64_t)(in[0])) << 56) + | (((uint64_t)(in[1])) << 48) + | (((uint64_t)(in[2])) << 40) + | (((uint64_t)(in[3])) << 32) + | (((uint64_t)(in[4])) << 24) + | (((uint64_t)(in[5])) << 16) + | (((uint64_t)(in[6])) << 8) + | (((uint64_t)(in[7])) << 0) + ; +} + +static inline uint64be_t uint64be_marshal(uint64_t in) { + uint64be_t out; + uint64be_encode(out.octets, in); + return out; +} + +static inline uint64_t uint64be_unmarshal(uint64be_t in) { + return uint64be_decode(in.octets); +} + /* Little endian **************************************************************/ typedef struct { @@ -131,4 +171,43 @@ static inline uint32_t uint32le_unmarshal(uint32le_t in) { return uint32le_decode(in.octets); } +typedef struct { + uint8_t octets[8]; +} uint64le_t; +static_assert(sizeof(uint64le_t) == 8); + +static inline size_t uint64le_encode(uint8_t *out, uint64_t in) { + out[0] = (uint8_t)((in >> 0) & 0xFF); + out[1] = (uint8_t)((in >> 8) & 0xFF); + out[2] = (uint8_t)((in >> 16) & 0xFF); + out[3] = (uint8_t)((in >> 24) & 0xFF); + out[4] = (uint8_t)((in >> 32) & 0xFF); + out[5] = (uint8_t)((in >> 40) & 0xFF); + out[6] = (uint8_t)((in >> 48) & 0xFF); + out[7] = (uint8_t)((in >> 56) & 0xFF); + return 8; +} + +static inline uint64_t uint64le_decode(uint8_t *in) { + return (((uint64_t)(in[0])) << 0) + | (((uint64_t)(in[1])) << 8) + | (((uint64_t)(in[2])) << 16) + | (((uint64_t)(in[3])) << 24) + | (((uint64_t)(in[4])) << 32) + | (((uint64_t)(in[5])) << 40) + | (((uint64_t)(in[6])) << 48) + | (((uint64_t)(in[7])) << 56) + ; +} + +static inline uint64le_t uint64le_marshal(uint64_t in) { + uint64le_t out; + uint64le_encode(out.octets, in); + return out; +} + +static inline uint64_t uint64le_unmarshal(uint64le_t in) { + return uint64le_decode(in.octets); +} + #endif /* _LIBMISC_ENDIAN_H_ */ diff --git a/libmisc/include/libmisc/log.h b/libmisc/include/libmisc/log.h index 37da20b..79c0ab6 100644 --- a/libmisc/include/libmisc/log.h +++ b/libmisc/include/libmisc/log.h @@ -1,31 +1,34 @@ /* libmisc/log.h - stdio logging * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _LIBMISC_LOG_H_ #define _LIBMISC_LOG_H_ -#ifndef LOG_NAME - #error "each compilation unit that includes <libmisc/log.h> must define LOG_NAME" -#endif +#include <stdint.h> /* for uint8_t */ + +#include <libmisc/macro.h> +#include <libmisc/_intercept.h> #ifdef NDEBUG #define _LOG_NDEBUG 1 #else #define _LOG_NDEBUG 0 #endif -#define __LOG_STR(x) #x -#define _LOG_STR(x) __LOG_STR(x) -#define __LOG_CAT3(a, b, c) a ## b ## c -#define _LOG_CAT3(a, b, c) __LOG_CAT3(a, b, c) -[[format(printf, 1, 2)]] int _log_printf(const char *format, ...); +const char *const_byte_str(uint8_t b); -#define errorf(fmt, ...) do { _log_printf("error: " _LOG_STR(LOG_NAME) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) -#define infof(fmt, ...) do { _log_printf("info : " _LOG_STR(LOG_NAME) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) -#define debugf(fmt, ...) do { if (_LOG_CAT3(CONFIG_, LOG_NAME, _DEBUG) && !_LOG_NDEBUG) \ - _log_printf("debug: " _LOG_STR(LOG_NAME) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) +#define n_errorf(nam, fmt, ...) do { __lm_printf("error: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) +#define n_infof(nam, fmt, ...) do { __lm_printf("info : " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) +#define n_debugf(nam, fmt, ...) do { if (LM_CAT3_(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \ + __lm_printf("debug: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) #endif /* _LIBMISC_LOG_H_ */ + +#if defined(LOG_NAME) && !defined(errorf) +#define errorf(fmt, ...) n_errorf(LOG_NAME, fmt, __VA_ARGS__) +#define infof(fmt, ...) n_infof(LOG_NAME, fmt, __VA_ARGS__) +#define debugf(fmt, ...) n_debugf(LOG_NAME, fmt, __VA_ARGS__) +#endif diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h new file mode 100644 index 0000000..b3e235c --- /dev/null +++ b/libmisc/include/libmisc/macro.h @@ -0,0 +1,96 @@ +/* libmisc/macro.h - Useful C preprocessor macros + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_MACRO_H_ +#define _LIBMISC_MACRO_H_ + +/* for function definitions */ + +#define LM_UNUSED(argname) +#define LM_ALWAYS_INLINE [[gnu::always_inline]] inline +#define LM_NEVER_INLINE [[gnu::noinline]] +#define LM_FLATTEN [[gnu::flatten]] + +/* numeric */ + +#define LM_ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0])) +#define LM_CEILDIV(n, d) ( ((n)+(d)-1) / (d) ) /** Return ceil(n/d) */ +#define LM_ROUND_UP(n, d) ( LM_CEILDIV(n, d) * (d) ) /** Return `n` rounded up to the nearest multiple of `d` */ +#define LM_ROUND_DOWN(n, d) ( ((n)/(d)) * (d) ) /** Return `n` rounded down to the nearest multiple of `d` */ +#define LM_NEXT_POWER_OF_2(x) ( (x) ? 1ULL<<((sizeof(unsigned long long)*8)-__builtin_clzll(x)) : 1) /** Return the lowest power of 2 that is > x */ +#define LM_FLOORLOG2(x) ((sizeof(unsigned long long)*8)-__builtin_clzll(x)-1) /** Return floor(log_2(x) */ + +/* strings */ + +#define LM_STR(x) #x +#define LM_STR_(x) LM_STR(x) + +/* token pasting */ + +#define LM_CAT2(a, b) a ## b +#define LM_CAT3(a, b, c) a ## b ## c + +#define LM_CAT2_(a, b) LM_CAT2(a, b) +#define LM_CAT3_(a, b, c) LM_CAT3(a, b, c) + +/* macro arguments */ + +#define LM_FIRST(a, ...) a +#define LM_SECOND(a, b, ...) b +#define LM_EAT(...) +#define LM_EXPAND(...) __VA_ARGS__ + +/* conditionals */ + +#define LM_T xxTxx +#define LM_F xxFxx + +#define LM_SENTINEL() bogus, LM_T /* a magic sentinel value */ +#define LM_IS_SENTINEL(...) LM_SECOND(__VA_ARGS__, LM_F) + +#define LM_IF(cond) LM_CAT2(_LM_IF__, cond) /* LM_IF(cond)(then)(else) */ +#define _LM_IF__xxTxx(...) __VA_ARGS__ LM_EAT +#define _LM_IF__xxFxx(...) LM_EXPAND + +/* tuples */ + +#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x) +#define _LM_IS_TUPLE(...) LM_SENTINEL() + +/* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */ +#define _LM_TUPLES_COMMA(tuple...) (tuple), +#define LM_TUPLES_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples) +#define LM_TUPLES_HEAD(tuples) LM_EXPAND(LM_FIRST LM_EAT() (_LM_TUPLES_COMMA tuples)) +#define LM_TUPLES_TAIL(tuples) LM_EAT tuples + +/* iteration */ + +/* BUG: LM_FOREACH_TUPLE maxes out at 1024 tuples. */ +#define LM_FOREACH_TUPLE(tuples, func, ...) \ + _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__)) +#define _LM_FOREACH_TUPLE(tuples, func, ...) \ + LM_IF(LM_TUPLES_NONEMPTY(tuples))( \ + _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \ + _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \ + )() +#define _LM_FOREACH_TUPLE_indirect() _LM_FOREACH_TUPLE + +#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()() + +#define _LM_EVAL(...) _LM_EVAL__1024(__VA_ARGS__) /* 1024 iterations aught to be enough for anybody */ +#define _LM_EVAL__1024(...) _LM_EVAL__512(_LM_EVAL__512(__VA_ARGS__)) +#define _LM_EVAL__512(...) _LM_EVAL__256(_LM_EVAL__256(__VA_ARGS__)) +#define _LM_EVAL__256(...) _LM_EVAL__128(_LM_EVAL__128(__VA_ARGS__)) +#define _LM_EVAL__128(...) _LM_EVAL__64(_LM_EVAL__64(__VA_ARGS__)) +#define _LM_EVAL__64(...) _LM_EVAL__32(_LM_EVAL__32(__VA_ARGS__)) +#define _LM_EVAL__32(...) _LM_EVAL__16(_LM_EVAL__16(__VA_ARGS__)) +#define _LM_EVAL__16(...) _LM_EVAL__8(_LM_EVAL__8(__VA_ARGS__)) +#define _LM_EVAL__8(...) _LM_EVAL__4(_LM_EVAL__4(__VA_ARGS__)) +#define _LM_EVAL__4(...) _LM_EVAL__2(_LM_EVAL__2(__VA_ARGS__)) +#define _LM_EVAL__2(...) _LM_EVAL__1(_LM_EVAL__1(__VA_ARGS__)) +#define _LM_EVAL__1(...) __VA_ARGS__ + +#endif /* _LIBMISC_MACRO_H_ */ diff --git a/libmisc/include/libmisc/private.h b/libmisc/include/libmisc/private.h index bc5e7ad..5518d1f 100644 --- a/libmisc/include/libmisc/private.h +++ b/libmisc/include/libmisc/private.h @@ -1,37 +1,17 @@ /* libmisc/private.h - Utilities to hide struct fields * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _LIBMISC_PRIVATE_H_ #define _LIBMISC_PRIVATE_H_ -/* primitive utilities */ +#include <libmisc/macro.h> -#define _SECOND(a, b, ...) b - -#define _CAT(a, b) a ## b -#define _CAT2(a, b) _CAT(a, b) -#define _EAT(...) -#define _EXPAND(...) __VA_ARGS__ - -/* conditionals */ - -#define _T xxTxx -#define _F xxFxx - -#define _SENTINEL() bogus, _T /* a magic sentinel value */ -#define _IS_SENTINEL(...) _SECOND(__VA_ARGS__, _F) - -#define _IF(cond) _CAT(_IF__, cond) /* _IF(cond)(then)(else) */ -#define _IF__xxTxx(...) __VA_ARGS__ _EAT -#define _IF__xxFxx(...) _EXPAND - -/* high-level functionality */ - -#define YES _SENTINEL() -#define BEGIN_PRIVATE(name) _IF(_IS_SENTINEL(IMPLEMENTATION_FOR_##name))()(struct {) -#define END_PRIVATE(name) _IF(_IS_SENTINEL(IMPLEMENTATION_FOR_##name))()(} _CAT2(_HIDDEN_, __COUNTER__);) +#define YES LM_SENTINEL() +#define IS_IMPLEMENTATION_FOR(name) LM_IS_SENTINEL(IMPLEMENTATION_FOR_##name) +#define BEGIN_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))()(struct {) struct {} LM_CAT2_(_PRIVATE_FORCE_SEMICOLON_, __COUNTER__) +#define END_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))(struct {} LM_CAT2_(_PRIVATE_FORCE_SEMICOLON_, __COUNTER__))(} LM_CAT2_(_PRIVATE_, __COUNTER__)) #endif /* _LIBMISC_PRIVATE_H_ */ diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h index 8072841..7ef238b 100644 --- a/libmisc/include/libmisc/rand.h +++ b/libmisc/include/libmisc/rand.h @@ -1,6 +1,6 @@ /* libmisc/rand.h - Non-crytpographic random-number utilities * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -29,14 +29,14 @@ static inline uint64_t rand_uint63n(uint64_t cnt) { uint64_t fair_cnt = ((UINT64_C(1)<<62) / cnt) * cnt; uint64_t rnd; do { - rnd = (random() << 31) | random(); + rnd = (((uint64_t)random()) << 31) | random(); } while (rnd >= fair_cnt); return rnd % cnt; } else if (cnt <= UINT64_C(1)<<63) { uint64_t fair_cnt = ((UINT64_C(1)<<63) / cnt) * cnt; uint64_t rnd; do { - rnd = (random() << 62) | (random() << 31) | random(); + rnd = (((uint64_t)random()) << 62) | (((uint64_t)random()) << 31) | random(); } while (rnd >= fair_cnt); return rnd % cnt; } diff --git a/libmisc/include/libmisc/vcall.h b/libmisc/include/libmisc/vcall.h deleted file mode 100644 index 9b54c06..0000000 --- a/libmisc/include/libmisc/vcall.h +++ /dev/null @@ -1,28 +0,0 @@ -/* libmisc/vcall.h - A simple Go-ish object system built on GCC -fplan9-extensions - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _LIBMISC_VCALL_H_ -#define _LIBMISC_VCALL_H_ - -#include <stddef.h> /* for offsetof() */ - -#include <libmisc/assert.h> - -#define VCALL(o, m, ...) \ - ({ \ - assert(o); \ - (o)->vtable->m(o __VA_OPT__(,) __VA_ARGS__); \ - }) - -#define VCALL_SELF(obj_typ, iface_typ, iface_ptr) \ - ({ \ - static_assert(_Generic(iface_ptr, iface_typ *: 1, default: 0), \ - "typeof("#iface_ptr") != "#iface_typ); \ - assert(iface_ptr); \ - ((obj_typ*)(((void*)iface_ptr)-offsetof(obj_typ,iface_typ))); \ - }) - -#endif /* _LIBMISC_VCALL_H_ */ diff --git a/libmisc/intercept.c b/libmisc/intercept.c new file mode 100644 index 0000000..85a3801 --- /dev/null +++ b/libmisc/intercept.c @@ -0,0 +1,34 @@ +/* libmisc/intercept.c - Interceptable ("weak") functions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdarg.h> /* for va_list, va_start(), va_end() */ +#include <stdio.h> /* for vprintf() */ +#include <stdlib.h> /* for abort() */ + +#include <libmisc/_intercept.h> + +[[gnu::weak]] +int __lm_printf(const char *format, ...) { + va_list va; + va_start(va, format); + int ret = vprintf(format, va); + va_end(va); + return ret; +} + +[[gnu::weak]] +int __lm_light_printf(const char *format, ...) { + va_list va; + va_start(va, format); + int ret = vprintf(format, va); + va_end(va); + return ret; +} + +[[gnu::weak]] +void __lm_abort(void) { + abort(); +} diff --git a/libmisc/log.c b/libmisc/log.c index a1ec10f..be87de6 100644 --- a/libmisc/log.c +++ b/libmisc/log.c @@ -1,19 +1,273 @@ /* libmisc/log.c - stdio logging * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <stdio.h> /* for vprintf() */ -#include <stdarg.h> /* for va_list, va_start(), va_end() */ +#include <stdint.h> /* for uint{n}_t */ -#define LOG_NAME -#include <libmisc/log.h> +#include <libmisc/assert.h> /* for static_assert() */ -int _log_printf(const char *format, ...) { - va_list va; - va_start(va, format); - int ret = vprintf(format, va); - va_end(va); - return ret; +static const char *byte_strs[] = { + "0x00", + "0x01", + "0x02", + "0x03", + "0x04", + "0x05", + "0x06", + "0x07", + "0x08", + "0x09", + "0x0A", + "0x0B", + "0x0C", + "0x0D", + "0x0E", + "0x0F", + "0x10", + "0x11", + "0x12", + "0x13", + "0x14", + "0x15", + "0x16", + "0x17", + "0x18", + "0x19", + "0x1A", + "0x1B", + "0x1C", + "0x1D", + "0x1E", + "0x1F", + "0x20", + "0x21", + "0x22", + "0x23", + "0x24", + "0x25", + "0x26", + "0x27", + "0x28", + "0x29", + "0x2A", + "0x2B", + "0x2C", + "0x2D", + "0x2E", + "0x2F", + "0x30", + "0x31", + "0x32", + "0x33", + "0x34", + "0x35", + "0x36", + "0x37", + "0x38", + "0x39", + "0x3A", + "0x3B", + "0x3C", + "0x3D", + "0x3E", + "0x3F", + "0x40", + "0x41", + "0x42", + "0x43", + "0x44", + "0x45", + "0x46", + "0x47", + "0x48", + "0x49", + "0x4A", + "0x4B", + "0x4C", + "0x4D", + "0x4E", + "0x4F", + "0x50", + "0x51", + "0x52", + "0x53", + "0x54", + "0x55", + "0x56", + "0x57", + "0x58", + "0x59", + "0x5A", + "0x5B", + "0x5C", + "0x5D", + "0x5E", + "0x5F", + "0x60", + "0x61", + "0x62", + "0x63", + "0x64", + "0x65", + "0x66", + "0x67", + "0x68", + "0x69", + "0x6A", + "0x6B", + "0x6C", + "0x6D", + "0x6E", + "0x6F", + "0x70", + "0x71", + "0x72", + "0x73", + "0x74", + "0x75", + "0x76", + "0x77", + "0x78", + "0x79", + "0x7A", + "0x7B", + "0x7C", + "0x7D", + "0x7E", + "0x7F", + "0x80", + "0x81", + "0x82", + "0x83", + "0x84", + "0x85", + "0x86", + "0x87", + "0x88", + "0x89", + "0x8A", + "0x8B", + "0x8C", + "0x8D", + "0x8E", + "0x8F", + "0x90", + "0x91", + "0x92", + "0x93", + "0x94", + "0x95", + "0x96", + "0x97", + "0x98", + "0x99", + "0x9A", + "0x9B", + "0x9C", + "0x9D", + "0x9E", + "0x9F", + "0xA0", + "0xA1", + "0xA2", + "0xA3", + "0xA4", + "0xA5", + "0xA6", + "0xA7", + "0xA8", + "0xA9", + "0xAA", + "0xAB", + "0xAC", + "0xAD", + "0xAE", + "0xAF", + "0xB0", + "0xB1", + "0xB2", + "0xB3", + "0xB4", + "0xB5", + "0xB6", + "0xB7", + "0xB8", + "0xB9", + "0xBA", + "0xBB", + "0xBC", + "0xBD", + "0xBE", + "0xBF", + "0xC0", + "0xC1", + "0xC2", + "0xC3", + "0xC4", + "0xC5", + "0xC6", + "0xC7", + "0xC8", + "0xC9", + "0xCA", + "0xCB", + "0xCC", + "0xCD", + "0xCE", + "0xCF", + "0xD0", + "0xD1", + "0xD2", + "0xD3", + "0xD4", + "0xD5", + "0xD6", + "0xD7", + "0xD8", + "0xD9", + "0xDA", + "0xDB", + "0xDC", + "0xDD", + "0xDE", + "0xDF", + "0xE0", + "0xE1", + "0xE2", + "0xE3", + "0xE4", + "0xE5", + "0xE6", + "0xE7", + "0xE8", + "0xE9", + "0xEA", + "0xEB", + "0xEC", + "0xED", + "0xEE", + "0xEF", + "0xF0", + "0xF1", + "0xF2", + "0xF3", + "0xF4", + "0xF5", + "0xF6", + "0xF7", + "0xF8", + "0xF9", + "0xFA", + "0xFB", + "0xFC", + "0xFD", + "0xFE", + "0xFF", +}; +static_assert(sizeof(byte_strs)/sizeof(byte_strs[0]) == 0x100); + +const char *const_byte_str(uint8_t b) { + return byte_strs[b]; } diff --git a/libmisc/tests/test_assert.c b/libmisc/tests/test_assert.c index 5b28561..15f9446 100644 --- a/libmisc/tests/test_assert.c +++ b/libmisc/tests/test_assert.c @@ -1,20 +1,21 @@ /* libmisc/tests/test_assert.c - Tests for <libmisc/assert.h> * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #include <setjmp.h> +#include <stdarg.h> /* for va_list, va_start(), va_end() */ #include <stdbool.h> -#include <string.h> #include <stdlib.h> +#include <string.h> +#include <libmisc/macro.h> #include <libmisc/assert.h> +#include <libmisc/_intercept.h> #include "test.h" -#define UNUSED(name) - /* Intercept failures and logging *********************************************/ bool global_failed; @@ -29,17 +30,21 @@ jmp_buf global_env; setjmp(global_env) == 0; \ }) -[[noreturn]] void abort(void) { +void __lm_abort(void) { global_failed = true; longjmp(global_env, 1); } -#define __builtin_unreachable() test_assert(0) - -int vprintf(const char *format, va_list ap) { - return vasprintf(&global_log, format, ap); +int __lm_light_printf(const char *format, ...) { + va_list va; + va_start(va, format); + int ret = vasprintf(&global_log, format, va); + va_end(va); + return ret; } +#define __builtin_unreachable() test_assert(0) + /* Utilities ******************************************************************/ #define test_should_succeed(test) do { \ @@ -64,27 +69,26 @@ int vprintf(const char *format, va_list ap) { } \ } while (0) -#define _STR(x) #x -#define STR(x) _STR(x) - /* Actual tests ***************************************************************/ static_assert(sizeof(char) == 1); int main() { +#ifndef NDEBUG test_should_succeed(assert(true)); - test_should_fail(assert(false), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"false\" failed\n"); + test_should_fail(assert(false), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed\n"); test_should_succeed(assert_msg(true, "foo")); - test_should_fail(assert_msg(false, "foo"), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"false\" failed: foo\n"); + test_should_fail(assert_msg(false, "foo"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed: foo\n"); test_should_succeed(assert_msg(true, NULL)); - test_should_fail(assert_msg(false, NULL), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"false\" failed\n"); + test_should_fail(assert_msg(false, NULL), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed\n"); - test_should_fail(assert_notreached("xxx"), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"notreached\" failed: xxx\n"); + test_should_fail(assert_notreached("xxx"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"notreached\" failed: xxx\n"); if (global_log) { free(global_log); global_log = NULL; } +#endif return 0; } diff --git a/libmisc/tests/test_assert_min.c b/libmisc/tests/test_assert_min.c new file mode 100644 index 0000000..9c0394b --- /dev/null +++ b/libmisc/tests/test_assert_min.c @@ -0,0 +1,17 @@ +/* libmisc/tests/test_assert_min.c - Tests for minimal <libmisc/assert.h> + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/* Don't include *anything* else. */ +#include <libmisc/assert.h> + +static_assert(1 == 1); + +int main() { + assert_msg(1, "foo"); + assert(1); + return 0; + assert_notreached("ret"); +} diff --git a/libmisc/tests/test_endian.c b/libmisc/tests/test_endian.c index d0b547c..dcb3cc2 100644 --- a/libmisc/tests/test_endian.c +++ b/libmisc/tests/test_endian.c @@ -1,6 +1,6 @@ /* libmisc/tests/test_endian.c - Tests for <libmisc/endian.h> * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -11,22 +11,31 @@ #include "test.h" int main() { - uint8_t act[12] = {0}; - uint16be_encode(&act[0], UINT16_C(0x1234)); - uint32be_encode(&act[2], UINT32_C(0x56789ABC)); - uint16le_encode(&act[6], UINT16_C(0x1234)); - uint32le_encode(&act[8], UINT32_C(0x56789ABC)); + uint8_t act[(2+4+8)*2] = {0}; + size_t pos = 0; + pos += uint16be_encode(&act[pos], UINT16_C(0x1234)); + pos += uint32be_encode(&act[pos], UINT32_C(0x56789ABC)); + pos += uint64be_encode(&act[pos], UINT64_C(0xAC589A93278CB30A)); + pos += uint16le_encode(&act[pos], UINT16_C(0x1234)); + pos += uint32le_encode(&act[pos], UINT32_C(0x56789ABC)); + pos += uint64le_encode(&act[pos], UINT64_C(0xAC589A93278CB30A)); - uint8_t exp[12] = { 0x12, 0x34, - 0x56, 0x78, 0x9A, 0xBC, - 0x34, 0x12, - 0xBC, 0x9A, 0x78, 0x56 }; - test_assert(memcmp(act, exp, 12) == 0); + test_assert(pos == sizeof(act)); + uint8_t exp[(2+4+8)*2] = { 0x12, 0x34, + 0x56, 0x78, 0x9A, 0xBC, + 0xAC, 0x58, 0x9A, 0x93, 0x27, 0x8C, 0xB3, 0x0A, + 0x34, 0x12, + 0xBC, 0x9A, 0x78, 0x56, + 0x0A, 0xB3, 0x8C, 0x27, 0x93, 0x9A, 0x58, 0xAC}; + test_assert(memcmp(act, exp, sizeof(act)) == 0); - test_assert(uint16be_decode(&act[0]) == UINT16_C(0x1234)); - test_assert(uint32be_decode(&act[2]) == UINT32_C(0x56789ABC)); - test_assert(uint16le_decode(&act[6]) == UINT16_C(0x1234)); - test_assert(uint32le_decode(&act[8]) == UINT32_C(0x56789ABC)); + pos = 0; + test_assert(uint16be_decode(&act[pos]) == UINT16_C(0x1234)); pos += 2; + test_assert(uint32be_decode(&act[pos]) == UINT32_C(0x56789ABC)); pos += 4; + test_assert(uint64be_decode(&act[pos]) == UINT64_C(0xAC589A93278CB30A)); pos += 8; + test_assert(uint16le_decode(&act[pos]) == UINT16_C(0x1234)); pos += 2; + test_assert(uint32le_decode(&act[pos]) == UINT32_C(0x56789ABC)); pos += 4; + test_assert(uint64le_decode(&act[pos]) == UINT64_C(0xAC589A93278CB30A)); pos += 8; return 0; } diff --git a/libmisc/tests/test_log.c b/libmisc/tests/test_log.c index 286738d..49a76ca 100644 --- a/libmisc/tests/test_log.c +++ b/libmisc/tests/test_log.c @@ -13,36 +13,43 @@ #define LOG_NAME FROBNICATE #include <libmisc/log.h> +#include <libmisc/_intercept.h> + #include "test.h" /* Intercept output ***********************************************************/ static char *log_output = NULL; -int vprintf(const char *format, va_list ap) { - return vasprintf(&log_output, format, ap); +int __lm_printf(const char *format, ...) { + va_list va; + va_start(va, format); + int ret = vasprintf(&log_output, format, va); + va_end(va); + return ret; } /* Actual tests ***************************************************************/ -#define should_print(_exp, cmd) do { \ - char *exp = _exp; \ - test_assert(!log_output); \ - cmd; \ - if (!exp) \ - test_assert(!log_output); \ - else \ - if (!(log_output != NULL && \ - strcmp(log_output, exp) == 0)) { \ - printf("exp = \"%s\"\n" \ - "act = \"%s\"\n", \ - exp, log_output); \ - test_assert(0); \ - } \ - if (log_output) { \ - free(log_output); \ - log_output = NULL; \ - } \ +#define should_print(_exp, cmd) do { \ + char *exp = _exp; \ + test_assert(!log_output); \ + cmd; \ + if (!exp) \ + test_assert(!log_output); \ + else { \ + test_assert(log_output); \ + if (strcmp(log_output, exp)) { \ + printf("exp = \"%s\"\n" \ + "act = \"%s\"\n", \ + exp, log_output); \ + test_assert(0); \ + } \ + } \ + if (log_output) { \ + free(log_output); \ + log_output = NULL; \ + } \ } while (0) int main() { @@ -50,6 +57,7 @@ int main() { errorf("val=%d", 42)); should_print("info : FROBNICATE: val=0\n", infof("val=%d", 0)); +#ifndef NDEBUG #define CONFIG_FROBNICATE_DEBUG 1 should_print("debug: FROBNICATE: val=-2\n", debugf("val=%d", -2)); @@ -57,5 +65,6 @@ int main() { #define CONFIG_FROBNICATE_DEBUG 0 should_print(NULL, debugf("val=%d", -2)); +#endif return 0; } diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c new file mode 100644 index 0000000..1320eb3 --- /dev/null +++ b/libmisc/tests/test_macro.c @@ -0,0 +1,54 @@ +/* libmisc/tests/test_macro.c - Tests for <libmisc/macro.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/macro.h> + +#include "test.h" + +int main() { + printf("== LM_NEXT_POWER_OF_2 =====================================\n"); + /* valid down to 0. */ + test_assert(LM_NEXT_POWER_OF_2(0) == 1); + test_assert(LM_NEXT_POWER_OF_2(1) == 2); + test_assert(LM_NEXT_POWER_OF_2(2) == 4); + test_assert(LM_NEXT_POWER_OF_2(3) == 4); + test_assert(LM_NEXT_POWER_OF_2(4) == 8); + test_assert(LM_NEXT_POWER_OF_2(5) == 8); + test_assert(LM_NEXT_POWER_OF_2(6) == 8); + test_assert(LM_NEXT_POWER_OF_2(7) == 8); + test_assert(LM_NEXT_POWER_OF_2(8) == 16); + /* ... */ + test_assert(LM_NEXT_POWER_OF_2(16) == 32); + /* ... */ + test_assert(LM_NEXT_POWER_OF_2(0x7000000000000000) == 0x8000000000000000); + /* ... */ + test_assert(LM_NEXT_POWER_OF_2(0x8000000000000000-1) == 0x8000000000000000); + /* Valid up to 0x8000000000000000-1 = (1<<63)-1 */ + + printf("== LM_FLOORLOG2 ===========================================\n"); + /* valid down to 1. */ + test_assert(LM_FLOORLOG2(1) == 0); + test_assert(LM_FLOORLOG2(2) == 1); + test_assert(LM_FLOORLOG2(3) == 1); + test_assert(LM_FLOORLOG2(4) == 2); + test_assert(LM_FLOORLOG2(5) == 2); + test_assert(LM_FLOORLOG2(6) == 2); + test_assert(LM_FLOORLOG2(7) == 2); + test_assert(LM_FLOORLOG2(8) == 3); + /* ... */ + test_assert(LM_FLOORLOG2(16) == 4); + /* ... */ + test_assert(LM_FLOORLOG2(0x80000000) == 31); + /* ... */ + test_assert(LM_FLOORLOG2(0xFFFFFFFF) == 31); + test_assert(LM_FLOORLOG2(0x100000000) == 32); + /* ... */ + test_assert(LM_FLOORLOG2(0x8000000000000000) == 63); + /* ... */ + test_assert(LM_FLOORLOG2(0xFFFFFFFFFFFFFFFF) == 63); + + return 0; +} diff --git a/libmisc/tests/test_private.c b/libmisc/tests/test_private.c index 7aaf1ee..024dddb 100644 --- a/libmisc/tests/test_private.c +++ b/libmisc/tests/test_private.c @@ -1,6 +1,6 @@ /* libmisc/tests/test_private.c - Tests for <libmisc/private.h> * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -8,18 +8,18 @@ struct a { int foo; - BEGIN_PRIVATE(A) + BEGIN_PRIVATE(A); int bar; - END_PRIVATE(A) + END_PRIVATE(A); }; #define IMPLEMENTATION_FOR_B YES struct b { int foo; - BEGIN_PRIVATE(B) + BEGIN_PRIVATE(B); int bar; - END_PRIVATE(B) + END_PRIVATE(B); }; int main() { diff --git a/libmisc/tests/test_rand.c b/libmisc/tests/test_rand.c index fff1b27..8076155 100644 --- a/libmisc/tests/test_rand.c +++ b/libmisc/tests/test_rand.c @@ -8,26 +8,18 @@ #include <setjmp.h> #include <libmisc/rand.h> +#include <libmisc/_intercept.h> #include "test.h" /* Intercept failures *********************************************************/ +#ifndef NDEBUG jmp_buf *__catch; -void __assert_msg_fail(const char *expr, - const char *file, unsigned int line, const char *func, - const char *msg) { - static bool in_fail = false; +void __lm_abort(void) { if (__catch) longjmp(*__catch, 1); - if (!in_fail) { - in_fail = true; - printf("error: %s:%u:%s(): assertion \"%s\" failed%s%s\n", - file, line, func, - expr, - msg ? ": " : "", msg); - } abort(); } @@ -43,6 +35,7 @@ void __assert_msg_fail(const char *expr, __catch = old_catch; \ } \ } while (0); +#endif /* Actual tests ***************************************************************/ @@ -51,20 +44,24 @@ void __assert_msg_fail(const char *expr, static void test_n(uint64_t cnt) { if (cnt == 0 || cnt > UINT64_C(1)<<63) { +#ifndef NDEBUG should_abort(rand_uint63n(cnt)); +#else + return; +#endif } else { double sum = 0; bool seen[MAX_SEE_ALL] = {0}; for (int i = 0; i < ROUNDS; i++) { uint64_t val = rand_uint63n(cnt); - sum += ((double)val)/(cnt-1); + sum += val; test_assert(val < cnt); if (cnt < MAX_SEE_ALL) seen[val] = true; } if (cnt > 1) { - test_assert(sum/ROUNDS > 0.45); - test_assert(sum/ROUNDS < 0.55); + test_assert(sum/ROUNDS > 0.45*(cnt-1)); + test_assert(sum/ROUNDS < 0.55*(cnt-1)); } if (cnt < MAX_SEE_ALL) { for (uint64_t i = 0; i < cnt; i++) diff --git a/libmisc/tests/test_vcall.c b/libmisc/tests/test_vcall.c deleted file mode 100644 index f36fc4b..0000000 --- a/libmisc/tests/test_vcall.c +++ /dev/null @@ -1,74 +0,0 @@ -/* libmisc/tests/test_vcall.c - Tests for <libmisc/vcall.h> - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <libmisc/assert.h> -#include <libmisc/vcall.h> - -#include "test.h" - -/******************************************************************************/ - -struct frobber_vtable; - -typedef struct { - struct frobber_vtable *vtable; -} implements_frobber; - -struct frobber_vtable { - int (*frob)(implements_frobber *); - int (*frob1)(implements_frobber *, int); - void (*frob0)(implements_frobber *); -}; - -/******************************************************************************/ - -struct myclass { - int a; - implements_frobber; -}; -static_assert(offsetof(struct myclass, implements_frobber) != 0); - -static int myclass_frob(implements_frobber *_self) { - struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self); - test_assert(self); - test_assert((void*)self != (void*)_self); - return self->a; -} - -static int myclass_frob1(implements_frobber *_self, int arg) { - struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self); - test_assert(self); - test_assert((void*)self != (void*)_self); - return arg; -} - -static void myclass_frob0(implements_frobber *_self) { - struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self); - test_assert(self); - test_assert((void*)self != (void*)_self); -} - -struct frobber_vtable myclass_vtable = { - .frob = myclass_frob, - .frob1 = myclass_frob1, - .frob0 = myclass_frob0, -}; - -/******************************************************************************/ - -#define MAGIC1 909837 -#define MAGIC2 657441 - -int main() { - struct myclass obj = { - .implements_frobber = { .vtable = &myclass_vtable }, - .a = MAGIC1, - }; - test_assert(VCALL(&obj, frob) == MAGIC1); - test_assert(VCALL(&obj, frob1, MAGIC2) == MAGIC2); - VCALL(&obj, frob0); - return 0; -} diff --git a/libobj/CMakeLists.txt b/libobj/CMakeLists.txt new file mode 100644 index 0000000..e4d8095 --- /dev/null +++ b/libobj/CMakeLists.txt @@ -0,0 +1,14 @@ +# libobj/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +add_library(libobj INTERFACE) +target_include_directories(libobj PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(libobj INTERFACE + libmisc +) +target_compile_options(libobj INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>") + +add_lib_test(libobj test_obj) +add_lib_test(libobj test_nest) diff --git a/libobj/include/libobj/obj.h b/libobj/include/libobj/obj.h new file mode 100644 index 0000000..7a9041e --- /dev/null +++ b/libobj/include/libobj/obj.h @@ -0,0 +1,156 @@ +/* libobj/obj.h - A simple Go-ish object system + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBOBJ_OBJ_H_ +#define _LIBOBJ_OBJ_H_ + +#include <libmisc/macro.h> + +/** + * Use `lo_interface` similarly to how you would use + * `struct`/`enum`/`union` when writing the type of an interface + * value. + */ +#define lo_interface struct + +/** + * Use `LO_INTERFACE` in a .h file to define an interface. + * + * First define a macro named `{iface_name}_LO_IFACE` consisting of a + * series of calls to LO_NEST and/or LO_FUNC, then call + * `LO_INTERFACE({iface_name})`: + * + * #define myiface_LO_IFACE \ + * LO_NEST(wrapped_iface_name) \ + * LO_FUNC(ret_type, func_name, args...) + * LO_INTERFACE(myiface) + * + * Use `lo_interface {iface_name}` as the type of this interface; it + * should not be a pointer type. + * + * If there are any LO_NEST interfaces, this will define a + * `lo_box_{iface_name}_as_{wrapped_iface_name}(obj)` function for + * each. + */ +#define LO_NEST(_ARG_child_iface_name) \ + (lo_nest, _ARG_child_iface_name) +#define LO_FUNC(_ARG_ret_type, _ARG_func_name, ...) \ + (lo_func, _ARG_ret_type, _ARG_func_name __VA_OPT__(,) __VA_ARGS__) +#define LO_INTERFACE(_ARG_iface_name) \ + typedef struct { \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IFACE_VTABLE) \ + } _lo_##_ARG_iface_name##_vtable; \ + struct _ARG_iface_name { \ + void *self; \ + const _lo_##_ARG_iface_name##_vtable *vtable; \ + }; \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IFACE_PROTO, _ARG_iface_name) \ + extern int LM_CAT2_(_HIDDEN_BOGUS_, __COUNTER__) +#define _LO_IFACE_VTABLE(_tuple_typ, ...) \ + _LO_IFACE_VTABLE_##_tuple_typ(__VA_ARGS__) +#define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) \ + _lo_##_ARG_child_iface_name##_vtable; +#define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) \ + _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); +#define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) \ + _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__) +#define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \ + LM_ALWAYS_INLINE static lo_interface _ARG_child_iface_name \ + box_##_ARG_iface_name##_as_##_ARG_child_iface_name(lo_interface _ARG_iface_name obj) { \ + return (lo_interface _ARG_child_iface_name){ \ + .self = obj.self, \ + .vtable = &obj.vtable->_lo_##_ARG_child_iface_name##_vtable, \ + }; \ + } +#define _LO_IFACE_PROTO_lo_func(_ARG_iface_name, _ARG_ret_type, _ARG_func_name, ...) \ + /* empty */ + +/** + * `LO_NULL(iface_name)` is the null/nil/zero value for `lo_interface {iface_name}`. + */ +#define LO_NULL(_ARG_iface_name) ((lo_interface _ARG_iface_name){0}) + +/** + * `LO_IS_NULL(iface_val)` returns whether `iface_val` is LO_NULL. + */ +#define LO_IS_NULL(_ARG_iface_val) ((_ARG_iface_val).vtable == NULL) + +/** + * `LO_IFACE_EQ(a, b)` returns whether the interface values `a` and + * `b` are the same object. + */ +#define LO_EQ(_ARG_iface_val_a, _ARG_iface_val_b) \ + ((_ARG_iface_val_a).self == (_ARG_iface_val_b).self) + +/** + * Use LO_CALL(obj, method_name, args...) to call a method on an `lo_interface`. + */ +#define LO_CALL(_ARG_obj, _ARG_meth, ...) \ + (_ARG_obj).vtable->_ARG_meth((_ARG_obj).self __VA_OPT__(,) __VA_ARGS__) + +/** + * Use `LO_IMPLEMENTATION_H(iface_name, impl_type, impl_name)` in a .h + * file to declare that `{impl_type}` implements the `{iface_name}` + * interface with functions named `{impl_name}_{method_name}`. + * + * This will also define a `lo_box_{impl_name}_as_{iface_name}(obj)` + * function. + * + * You must also call the LO_IMPLEMENTATION_C in a single .c file. + */ +#define LO_IMPLEMENTATION_H(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \ + /* Vtable. */ \ + extern const _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \ + /* Boxing. */ \ + LM_ALWAYS_INLINE static lo_interface _ARG_iface_name \ + lo_box_##_ARG_impl_name##_as_##_ARG_iface_name(_ARG_impl_type *self) { \ + return (lo_interface _ARG_iface_name){ \ + .self = self, \ + .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable, \ + }; \ + } \ + extern int LM_CAT2_(_LO_FORCE_SEMICOLON_, __COUNTER__) + +/** + * Use `LO_IMPLEMENTATION_C(iface_name, impl_type, impl_name[, static])` in a .c + * file to declare that `{impl_type}` implements the `{iface_name}` interface + * with functions named `{impl_name}_{method_name}`. + * + * You must also call the LO_IMPLEMENTATION_H in the corresponding .h file. + * + * If `iface_name` contains a nested interface, then the + * implementation of the nested interfaces must be declared with + * `LO_IMPLEMENTATION_C` first. + */ +#define LO_IMPLEMENTATION_C(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name, ...) \ + /* Method prototypes. */ \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IMPL_PROTO, _ARG_impl_type, _ARG_impl_name, __VA_ARGS__) \ + /* Vtable. */ \ + const _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable = { \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IMPL_VTABLE, _ARG_impl_name) \ + } + +#define _LO_IMPL_PROTO(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _tuple_typ, ...) \ + _LO_IMPL_PROTO_##_tuple_typ(_ARG_impl_type, _ARG_impl_name, _ARG_quals, __VA_ARGS__) +#define _LO_IMPL_PROTO_lo_nest(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_child_iface_name) \ + /* empty */ +#define _LO_IMPL_PROTO_lo_func(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_ret_type, _ARG_func_name, ...) \ + _ARG_quals _ARG_ret_type _ARG_impl_name##_##_ARG_func_name(_ARG_impl_type * __VA_OPT__(,) __VA_ARGS__); + +#define _LO_IMPL_VTABLE(_ARG_impl_name, _tuple_typ, ...) \ + _LO_IMPL_VTABLE_##_tuple_typ(_ARG_impl_name, __VA_ARGS__) +#define _LO_IMPL_VTABLE_lo_nest(_ARG_impl_name, _ARG_child_iface_name) \ + ._lo_##_ARG_child_iface_name##_vtable = _lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, +#define _LO_IMPL_VTABLE_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) \ + ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name, + +#endif /* _LIBOBJ_OBJ_H_ */ diff --git a/libobj/tests/test.h b/libobj/tests/test.h new file mode 120000 index 0000000..2fb1bd5 --- /dev/null +++ b/libobj/tests/test.h @@ -0,0 +1 @@ +../../libmisc/tests/test.h
\ No newline at end of file diff --git a/libobj/tests/test_nest.c b/libobj/tests/test_nest.c new file mode 100644 index 0000000..f18b018 --- /dev/null +++ b/libobj/tests/test_nest.c @@ -0,0 +1,73 @@ +/* libobj/tests/test_nest.c - Tests for <libobj/obj.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcpy() */ + +#include <libobj/obj.h> + +#include "test.h" + +/* interfaces *****************************************************************/ + +#define reader_LO_IFACE \ + LO_FUNC(ssize_t, read, void *, size_t) +LO_INTERFACE(reader); + +#define writer_LO_IFACE \ + LO_FUNC(ssize_t, write, void *, size_t) +LO_INTERFACE(writer); + +#define read_writer_LO_IFACE \ + LO_NEST(reader) \ + LO_NEST(writer) +LO_INTERFACE(read_writer); + +/* implementation header ******************************************************/ + +struct myclass { + size_t len; + char buf[512]; +}; +LO_IMPLEMENTATION_H(reader, struct myclass, myclass); +LO_IMPLEMENTATION_H(writer, struct myclass, myclass); +LO_IMPLEMENTATION_H(read_writer, struct myclass, myclass); + +/* implementation main ********************************************************/ + +LO_IMPLEMENTATION_C(reader, struct myclass, myclass, static); +LO_IMPLEMENTATION_C(writer, struct myclass, myclass, static); +LO_IMPLEMENTATION_C(read_writer, struct myclass, myclass, static); + +static ssize_t myclass_read(struct myclass *self, void *buf, size_t count) { + test_assert(self); + if (count > self->len) + count = self->len; + memcpy(buf, self->buf, count); + return count; +} + +static ssize_t myclass_write(struct myclass *self, void *buf, size_t count) { + test_assert(self); + if (self->len) + return -1; + if (count > sizeof(self->buf)) + count = sizeof(self->buf); + memcpy(self->buf, buf, count); + self->len = count; + return count; +} + +/* main test body *************************************************************/ + +int main() { + struct myclass _obj = {0}; + lo_interface read_writer obj = lo_box_myclass_as_read_writer(&_obj); + test_assert(LO_CALL(obj, write, "Hello", 6) == 6); + char buf[6] = {0}; + test_assert(LO_CALL(obj, read, buf, 3) == 3); + test_assert(memcmp(buf, "Hel\0\0\0", 6) == 0); + return 0; +} diff --git a/libobj/tests/test_obj.c b/libobj/tests/test_obj.c new file mode 100644 index 0000000..d6861dc --- /dev/null +++ b/libobj/tests/test_obj.c @@ -0,0 +1,61 @@ +/* libobj/tests/test_obj.c - Tests for <libobj/obj.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libobj/obj.h> + +#include "test.h" + +/* `lo_inteface frobber` header ***********************************************/ + +#define frobber_LO_IFACE \ + /** Basic function. */ \ + LO_FUNC(int, frob) \ + /** Function that takes 1 argument. */ \ + LO_FUNC(int, frob1, int) \ + /** Function that returns nothing. */ \ + LO_FUNC(void, frob0) +LO_INTERFACE(frobber); + +/* `struct myclass` header ****************************************************/ + +struct myclass { + int a; +}; +LO_IMPLEMENTATION_H(frobber, struct myclass, myclass); + +/* `struct myclass` implementation ********************************************/ + +LO_IMPLEMENTATION_C(frobber, struct myclass, myclass, static); + +static int myclass_frob(struct myclass *self) { + test_assert(self); + return self->a; +} + +static int myclass_frob1(struct myclass *self, int arg) { + test_assert(self); + return arg; +} + +static void myclass_frob0(struct myclass *self) { + test_assert(self); +} + +/* main test body *************************************************************/ + +#define MAGIC1 909837 +#define MAGIC2 657441 + +int main() { + struct myclass obj = { + .a = MAGIC1, + }; + lo_interface frobber iface = lo_box_myclass_as_frobber(&obj); + test_assert(LO_CALL(iface, frob) == MAGIC1); + test_assert(LO_CALL(iface, frob1, MAGIC2) == MAGIC2); + LO_CALL(iface, frob0); + return 0; +} diff --git a/libusb/CMakeLists.txt b/libusb/CMakeLists.txt index c23f30e..b11e798 100644 --- a/libusb/CMakeLists.txt +++ b/libusb/CMakeLists.txt @@ -1,17 +1,15 @@ # libusb/CMakeLists.txt - Build script for libusb support library # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libusb INTERFACE) -target_include_directories(libusb SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_include_directories(libusb PUBLIC INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) target_sources(libusb INTERFACE usb_common.c ) target_link_libraries(libusb INTERFACE - pico_unique_id tinyusb_device - tinyusb_board libcr_ipc libmisc diff --git a/libusb/include/libusb/tusb_helpers.h b/libusb/include/libusb/tusb_helpers.h index 1b4e48e..0c35f62 100644 --- a/libusb/include/libusb/tusb_helpers.h +++ b/libusb/include/libusb/tusb_helpers.h @@ -1,7 +1,7 @@ /* Generated by `libusb/include/libusb/tusb_helpers.h.gen `. DO NOT EDIT! */ /* libusb/tusb_helpers.h - Preprocessor macros that I think should be included in TinyUSB * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * * SPDX-License-Identifier: MIT * diff --git a/libusb/include/libusb/tusb_helpers.h.gen b/libusb/include/libusb/tusb_helpers.h.gen index 5a5d1ac..1de1d09 100755 --- a/libusb/include/libusb/tusb_helpers.h.gen +++ b/libusb/include/libusb/tusb_helpers.h.gen @@ -7,7 +7,7 @@ echo "/* Generated by \`$0 $*\`. DO NOT EDIT! */" cat <<'EOT' /* libusb/tusb_helpers.h - Preprocessor macros that I think should be included in TinyUSB * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * * SPDX-License-Identifier: MIT * @@ -70,12 +70,19 @@ fi if [[ ! -f 3rd-party/MS-LCID.txt || 3rd-party/MS-LCID.txt -ot 3rd-party/MS-LCID.pdf ]]; then pdftotext -layout 3rd-party/MS-LCID.pdf fi -<3rd-party/MS-LCID.txt \ - grep -E '^\s*0x[0-9A-F]{4}\s+[a-z]' | sed 's/,.*//' | grep -v reserved | # find the lines we're interested in - sed -E 's/^\s*0x(..)(..)\s+(\S.*)/\2 \1 \3/p' | tr '[:lower:]-' '[:upper:]_' | # format them as 'PRIhex SUBhex UPPER_STR' - sort | - sed -E 's/(..) (..) (.*)/#define LANGID_\3 0x\2\1/' | # format them as '#define LANGID_UPPER_STR 0xSUBPRI' - column --table --output-separator=' ' +{ + { + # find the lines we're interested in + grep -E '^\s*0x[0-9A-F]{4}\s+[a-z]' | sed 's/,.*//' | grep -v reserved + } | { + # format them as 'PRIhex SUBhex UPPER_STR' + sed -E 's/^\s*0x(..)(..)\s+(\S.*)/\2 \1 \3/p' | tr '[:lower:]-' '[:upper:]_' + } | sort | { + # format them as '#define LANGID_UPPER_STR 0xSUBPRI' + sed -E 's/(..) (..) (.*)/#define LANGID_\3 0x\2\1/' | + column --table --output-separator=' ' + } +} <3rd-party/MS-LCID.txt cat <<'EOT' /** USB 2.0 §9.6.6 "Endpoint", field bEndpointAddress, bit 7 */ diff --git a/libusb/include/libusb/usb_common.h b/libusb/include/libusb/usb_common.h index e31b3ab..9fe0794 100644 --- a/libusb/include/libusb/usb_common.h +++ b/libusb/include/libusb/usb_common.h @@ -27,7 +27,7 @@ enum { /* Globals ********************************************************************/ extern uint8_t cfgnum_std; -void usb_common_earlyinit(void); +void usb_common_earlyinit(uint16_t *serial, uint8_t serial_bytelen); void usb_common_lateinit(void); COROUTINE usb_common_cr(void *arg); diff --git a/libusb/usb_common.c b/libusb/usb_common.c index c9429e7..29dec42 100644 --- a/libusb/usb_common.c +++ b/libusb/usb_common.c @@ -6,10 +6,10 @@ #include <stdint.h> /* for uint{n}_t types */ #include <stddef.h> /* for size_t */ -#include <string.h> /* memcpy(newlib) */ -#include <stdlib.h> /* for malloc(pico_malloc), realloc(pico_malloc), reallocarray(pico_malloc) */ -#include "bsp/board_api.h" /* for board_init(), board_init_after_usb(), board_usb_get_serial(TinyUSB) */ -#include "tusb.h" /* for various tusb_*_t types */ +#include <string.h> /* memcpy() */ +#include <stdlib.h> /* for malloc(), realloc(), reallocarray() */ + +#include <tusb.h> /* for various tusb_*_t types */ #include <libmisc/assert.h> @@ -21,6 +21,17 @@ #include "config.h" +/* Globals ********************************************************************/ + +uint8_t cfgnum_std = 0; + +static struct { + uint8_t configc; + uint8_t **configv; + uint16_t *serial; + uint8_t serial_bytelen; +} globals = {0}; + /* Strings ********************************************************************/ /** @@ -53,10 +64,8 @@ uint16_t const *tud_descriptor_string_cb(uint8_t strid, uint16_t langid) { case STRID_CFG: CONST_STR(TU_UTF16("Standard Configuration")); break; case STRID_KBD_IFC: CONST_STR(TU_UTF16("Keyboard Interface")); break; case STRID_SERIAL: -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Waddress-of-packed-member" - bytelen = 2 * board_usb_get_serial(desc.bString, TU_ARRAY_SIZE(desc.bString)); -#pragma GCC diagnostic pop + bytelen = globals.serial_bytelen; + memcpy(desc.bString, globals.serial, bytelen); break; default: debugf("GET STRING: unknown string id=%"PRIu8, strid); @@ -74,11 +83,11 @@ uint16_t const *tud_descriptor_string_cb(uint8_t strid, uint16_t langid) { return (uint16_t *)&desc; } -/* Globals ********************************************************************/ +/* Main entrypoints ***********************************************************/ -uint8_t cfgnum_std = 0; - -void usb_common_earlyinit(void) { +void usb_common_earlyinit(uint16_t *serial, uint8_t serial_bytelen) { + globals.serial = serial; + globals.serial_bytelen = serial_bytelen; if (cfgnum_std) return; cfgnum_std = usb_add_config( @@ -88,10 +97,7 @@ void usb_common_earlyinit(void) { } void usb_common_lateinit(void) { - board_init(); - tud_init(BOARD_TUD_RHPORT); - if (board_init_after_tusb) - board_init_after_tusb(); + tud_init(CONFIG_USB_COMMON_RHPORT); } COROUTINE usb_common_cr(void *_arg) { @@ -108,19 +114,16 @@ COROUTINE usb_common_cr(void *_arg) { /* Main utilities *************************************************************/ -static uint8_t configc = 0; -static uint8_t **configv = NULL; - uint8_t usb_add_config(uint8_t iConfiguration, uint8_t bmAttributes, uint8_t bMaxPower_mA) { uint8_t *desc; - configv = reallocarray(configv, ++configc, sizeof configv[0]); - desc = configv[configc-1] = malloc(TUD_CONFIG_DESC_LEN); + globals.configv = reallocarray(globals.configv, ++globals.configc, sizeof globals.configv[0]); + desc = globals.configv[globals.configc-1] = malloc(TUD_CONFIG_DESC_LEN); memcpy(desc, (uint8_t[]){ /* USB configuration descriptor header (USB 2.0 §9.6.4 "Configuration") */ TUD_CONFIG_DESCRIPTOR( - configc, /* bConfigurationValue */ + globals.configc, /* bConfigurationValue */ 0, /* bNumInterfaces (will be incremented by usb_add_interface() */ iConfiguration, /* iConfiguration */ TUD_CONFIG_DESC_LEN, /* wTotalLength (will be incremented by usb_add_interface() */ @@ -128,15 +131,15 @@ uint8_t usb_add_config(uint8_t iConfiguration, uint8_t bmAttributes, uint8_t bMa bMaxPower_mA+1), /* bMaxPower (+1 because tusb just does n/2 instead of (n+1)/2) */ }, TUD_CONFIG_DESC_LEN); - return configc; + return globals.configc; } uint8_t usb_add_interface(uint8_t cfg_num, uint16_t ifc_len, uint8_t *ifc_dat) { - assert(cfg_num > 0 && cfg_num <= configc); + assert(cfg_num > 0 && cfg_num <= globals.configc); assert(ifc_len >= 3); assert(ifc_dat); - uint8_t *desc = configv[cfg_num-1]; + uint8_t *desc = globals.configv[cfg_num-1]; // wTotalLength uint16_t total_len = TU_U16(desc[3], desc[2]) + ifc_len; desc[3] = TU_U16_HIGH(total_len); @@ -144,7 +147,7 @@ uint8_t usb_add_interface(uint8_t cfg_num, uint16_t ifc_len, uint8_t *ifc_dat) { // bNumInterfaces ifc_dat[3] = desc[4]++; - desc = configv[cfg_num-1] = realloc(desc, total_len); + desc = globals.configv[cfg_num-1] = realloc(desc, total_len); memcpy(&desc[total_len-ifc_len], ifc_dat, ifc_len); return ifc_dat[3]; @@ -181,7 +184,7 @@ uint8_t const *tud_descriptor_device_cb(void) { .bNumConfigurations = 0, /* Number of possible configurations */ }; - desc.bNumConfigurations = configc; + desc.bNumConfigurations = globals.configc; return (uint8_t const *) &desc; } @@ -190,7 +193,7 @@ uint8_t const *tud_descriptor_device_cb(void) { * USB 2.0 §9.6.4 "Configuration") for the given index. */ uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { - if (index >= configc) + if (index >= globals.configc) return NULL; - return configv[index]; + return globals.configv[index]; } @@ -1,7 +1,7 @@ <!-- notes.md - Misc notes - Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> SPDX-License-Identifier: AGPL-3.0-or-later --> @@ -50,7 +50,7 @@ Which file to include: | `blksize_t` | `<sys/types.h>` | signed | | `pid_t` | `<sys/types.h>` | signed | | `ssize_t` | `<sys/types.h>` | signed | -| `SSIZE_MAX` | `<limits.h>` | | +| `SSIZE_MAX` | `<limits.h>` | not in newlib | | `suseconds_t` | `<sys/types.h>` | signed | | `clock_t` | `<sys/types.h>` | could be float | | `time_t` | `<sys/types.h>` | signed | @@ -140,6 +140,19 @@ sections" wut does that mean 8 16-bit values in both the TX buffer and the RX buffer +| Ver. 1.0.0 | 2013-08-01 | https://www.alldatasheet.com/datasheet-pdf/view/554784/ETC2/W5500.html https://www.alldatasheet.com/pdfjsview/web/viewer.html?file=//www.alldatasheet.com/datasheet-pdf/view/554784/ETC2/W5500/+_44J97VwSw9bZYvAB+/datasheet.pdf | +| Ver. 1.0.1 | 2013-09-13 | | +| Ver. 1.0.2 | 2013-11-14 | https://cdn.sparkfun.com/datasheets/Dev/Arduino/Shields/W5500_datasheet_v1.0.2_1.pdf | +| Ver. 1.0.3 | 2014-05-29 | | +| Ver. 1.0.4 | 2014-06-13 | | +| Ver. 1.0.5 | 2014-11-10 | | +| Ver. 1.0.6 | 2014-12-30 | | +| Ver. 1.0.7 | 2016-02-24 | | +| Ver. 1.0.8 | 2017-05-19 | https://docs.wiznet.io/img/products/w5500/w5500_ds_v108e.pdf (on-page version and date are wrong) | +| Ver. 1.0.9 | 2019-05-22 | https://docs.wiznet.io/img/products/w5500/w5500_ds_v109e.pdf | +| Ver. 1.1.0 | 2022-12-17 | https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf | + +https://github.com/Bodmer/TFT_eSPI/discussions/2432 ---- @@ -149,3 +162,32 @@ IDK about HDMI compression yet, but naively uncompressed we're looking at wanting to shove ~60 MB/s (480 Mb/s). Compression is an optional feature introduced in HDMI 2.1 :( + +---- + +PIO-based USB needs the system clock to be a multiple of 120mhz + +---- + +https://electronics.stackexchange.com/questions/684221/usb-c-on-off-switch-design-that-is-pd-compatible-off-similar-to-cold-plugging + +---- + +OpenBMC + +~$300 price range: + - Motherboards with a BMC + - Old proprietary IP-KVM + - TinyPilot KVM + - PiKVM +~$60 price range: + - DIY PiKVM + - JetKVM +~$30 price range + - Sipeed NanoKVM + - https://github.com/stefanklug/picoKVM (~$20 Arduino, ~$5 HDMI + capture), non-IP + +---- + +https://hackaday.com/2022/08/26/bit-banged-ethernet-on-the-raspberry-pi-pico/ |